Rewrite styles to scss and use i respective components
This commit is contained in:
@@ -1,42 +1,40 @@
|
||||
import React, { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Header from './Header';
|
||||
import Footer from './Footer';
|
||||
import Header from './Header/Header';
|
||||
import Footer from './Footer/Footer';
|
||||
import Router from './Router';
|
||||
import { BrowserRouter, Switch, Route, Redirect, useParams } from 'react-router-dom';
|
||||
|
||||
import radardata from '../rd.json'
|
||||
import radardata from '../rd.json';
|
||||
import { Item } from '../model';
|
||||
|
||||
const A = () => {
|
||||
const {page} = useParams()
|
||||
return <Router pageName={page} items={radardata.items as Item[]} releases={radardata.releases as string[]}></Router>
|
||||
}
|
||||
const { page } = useParams();
|
||||
return <Router pageName={page} items={radardata.items as Item[]} releases={radardata.releases as string[]}></Router>;
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
const [isFaded] = useState(false)
|
||||
const [isFaded] = useState(false);
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div>
|
||||
<div className="page">
|
||||
<div className="page__header">
|
||||
<Header pageName="a" />
|
||||
<div className='page'>
|
||||
<div className='page__header'>
|
||||
<Header pageName='a' />
|
||||
</div>
|
||||
<div
|
||||
className={classNames('page__content', { 'is-faded': isFaded })}
|
||||
>
|
||||
<div className={classNames('page__content', { 'is-faded': isFaded })}>
|
||||
<Switch>
|
||||
<Route path={"/techradar/:page(.+).html"}>
|
||||
<A/>
|
||||
<Route path={'/techradar/:page(.+).html'}>
|
||||
<A />
|
||||
</Route>
|
||||
<Route path={"/"}>
|
||||
<Redirect to={"/techradar/index.html"}/>
|
||||
<Route path={'/'}>
|
||||
<Redirect to={'/techradar/index.html'} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
<div className="page__footer">
|
||||
<Footer items={radardata.items as Item[]} pageName="a" />
|
||||
<div className='page__footer'>
|
||||
<Footer items={radardata.items as Item[]} pageName='a' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
import React, { MouseEventHandler } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import './badge.scss';
|
||||
type BadgeProps = {
|
||||
onClick?: MouseEventHandler
|
||||
big?: boolean
|
||||
type: "big" | "all" | "adopt" | "trial" | "assess" | "hold" | "empty"
|
||||
}
|
||||
onClick?: MouseEventHandler;
|
||||
big?: boolean;
|
||||
type: 'big' | 'all' | 'adopt' | 'trial' | 'assess' | 'hold' | 'empty';
|
||||
};
|
||||
|
||||
export default function Badge({ onClick, big, type, children }: React.PropsWithChildren<BadgeProps>) {
|
||||
const Comp = typeof onClick ? 'a' : 'span';
|
||||
|
||||
return (
|
||||
<Comp
|
||||
className={classNames(
|
||||
'badge',
|
||||
`badge--${type}`,
|
||||
{
|
||||
'badge--big': big === true,
|
||||
}
|
||||
)}
|
||||
className={classNames('badge', `badge--${type}`, {
|
||||
'badge--big': big === true,
|
||||
})}
|
||||
onClick={onClick}
|
||||
href={Comp === 'a' ? '#' : undefined}
|
||||
>
|
||||
44
src/components/Badge/badge.scss
Normal file
44
src/components/Badge/badge.scss
Normal file
@@ -0,0 +1,44 @@
|
||||
.badge {
|
||||
color: var(--color-white);
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
padding: 0 15px;
|
||||
text-transform: uppercase;
|
||||
border-radius: 13px;
|
||||
font-size: 12px;
|
||||
line-height: 25px;
|
||||
height: 25px;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
border: 1px solid var(--color-gray-normal);
|
||||
|
||||
&--big {
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
line-height: 30px;
|
||||
height: 30px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
&--all {
|
||||
background: var(--color-gray-normal);
|
||||
border-color: var(--color-gray-normal);
|
||||
}
|
||||
&--adopt {
|
||||
background: var(--color-green);
|
||||
border-color: var(--color-green);
|
||||
}
|
||||
&--trial {
|
||||
background: var(--color-orange);
|
||||
border-color: var(--color-orange);
|
||||
}
|
||||
&--assess {
|
||||
background: var(--color-blue);
|
||||
border-color: var(--color-blue);
|
||||
}
|
||||
&--hold {
|
||||
background: var(--color-marine);
|
||||
border-color: var(--color-marine);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +1,16 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import './branding.scss';
|
||||
type BrandingProps = {
|
||||
logoContent: React.ReactNode
|
||||
modifier?: "backlink" | "logo" | "content" | "footer"
|
||||
}
|
||||
logoContent: React.ReactNode;
|
||||
modifier?: 'backlink' | 'logo' | 'content' | 'footer';
|
||||
};
|
||||
|
||||
export default function Branding({ logoContent, modifier, children }: React.PropsWithChildren<BrandingProps>) {
|
||||
return (
|
||||
<div className={classNames('branding', { [`branding--${modifier}`]: modifier })}>
|
||||
<div className="branding__logo">
|
||||
{logoContent}
|
||||
</div>
|
||||
<div className="branding__content">
|
||||
{children}
|
||||
</div>
|
||||
<div className='branding__logo'>{logoContent}</div>
|
||||
<div className='branding__content'>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
44
src/components/Branding/branding.scss
Normal file
44
src/components/Branding/branding.scss
Normal file
@@ -0,0 +1,44 @@
|
||||
.branding {
|
||||
margin: 40px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 60px;
|
||||
transition: opacity 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98);
|
||||
opacity: 1;
|
||||
|
||||
&__backlink {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__logo {
|
||||
flex: 0 0 200px;
|
||||
|
||||
& img {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
&.is-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@media (--until-sm) {
|
||||
&--footer {
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
||||
.branding__logo {
|
||||
margin: 0 0 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (--until-xl) {
|
||||
margin: 15px 0 0;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import './fadeable.scss';
|
||||
type FadeableProps = {
|
||||
leaving: boolean
|
||||
onLeave: () => void
|
||||
}
|
||||
leaving: boolean;
|
||||
onLeave: () => void;
|
||||
};
|
||||
|
||||
export default function Fadeable({ leaving, onLeave, children }: React.PropsWithChildren<FadeableProps>) {
|
||||
const [faded, setFaded] = useState(leaving)
|
||||
const [faded, setFaded] = useState(leaving);
|
||||
|
||||
useEffect(() => {
|
||||
setFaded(leaving)
|
||||
}, [leaving])
|
||||
setFaded(leaving);
|
||||
}, [leaving]);
|
||||
|
||||
const handleTransitionEnd = () => {
|
||||
if (faded) {
|
||||
@@ -20,11 +20,8 @@ export default function Fadeable({ leaving, onLeave, children }: React.PropsWith
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('fadable', { 'is-faded': faded })}
|
||||
onTransitionEnd={handleTransitionEnd}
|
||||
>
|
||||
<div className={classNames('fadable', { 'is-faded': faded })} onTransitionEnd={handleTransitionEnd}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
8
src/components/Fadeable/fadeable.scss
Normal file
8
src/components/Fadeable/fadeable.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
.fadable {
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s cubic-bezier(0.54, 0, 0.28, 1);
|
||||
|
||||
&.is-faded {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
interface ItemFlag {
|
||||
flag: "default" | "new" | "changed"
|
||||
}
|
||||
|
||||
export default function Flag({ item, short = false }: {item: ItemFlag, short?: boolean}) {
|
||||
const ucFirst = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
|
||||
|
||||
if (item.flag !== 'default') {
|
||||
let name = item.flag.toUpperCase();
|
||||
let title = ucFirst(item.flag);
|
||||
if (short === true) {
|
||||
name = title[0]
|
||||
}
|
||||
return <span className={`flag flag--${item.flag}`} title={title}>{name}</span>;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
24
src/components/Flag/Flag.tsx
Normal file
24
src/components/Flag/Flag.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import './flag.scss';
|
||||
interface ItemFlag {
|
||||
flag: 'default' | 'new' | 'changed';
|
||||
}
|
||||
|
||||
export default function Flag({ item, short = false }: { item: ItemFlag; short?: boolean }) {
|
||||
const ucFirst = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
|
||||
|
||||
if (item.flag !== 'default') {
|
||||
let name = item.flag.toUpperCase();
|
||||
let title = ucFirst(item.flag);
|
||||
if (short === true) {
|
||||
name = title[0];
|
||||
}
|
||||
return (
|
||||
<span className={`flag flag--${item.flag}`} title={title}>
|
||||
{name}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
18
src/components/Flag/flag.scss
Normal file
18
src/components/Flag/flag.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
.flag {
|
||||
font-size: 9px;
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
margin-top: -2px;
|
||||
left: 5px;
|
||||
|
||||
&--new {
|
||||
background: var(--color-red);
|
||||
}
|
||||
|
||||
&--changed {
|
||||
background: var(--color-blue);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Branding from './Branding';
|
||||
import FooterEnd from './FooterEnd';
|
||||
import { assetUrl, getItemPageNames, isMobileViewport } from '../config';
|
||||
import { Item } from '../model';
|
||||
|
||||
export default function Footer({ items, pageName }: {items: Item[], pageName: string}) {
|
||||
return (
|
||||
<div className={classNames('footer', {'is-hidden': !isMobileViewport() && getItemPageNames(items).includes(pageName)})}>
|
||||
<Branding
|
||||
modifier="footer"
|
||||
logoContent={<img src={assetUrl('logo.svg')} width="150px" height="60px" alt="" />}
|
||||
>
|
||||
<span className="footnote">
|
||||
AOE is a leading global provider of services for digital transformation and digital business models. AOE relies exclusively on established Enterprise Open Source technologies. This leads to innovative solutions, digital products and portals in agile software projects, and helps build long-lasting, strategic partnerships with our customers.
|
||||
</span>
|
||||
</Branding>
|
||||
<FooterEnd/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
src/components/Footer/Footer.tsx
Normal file
21
src/components/Footer/Footer.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Branding from '../Branding/Branding';
|
||||
import FooterEnd from '../FooterEnd/FooterEnd';
|
||||
import { assetUrl, getItemPageNames, isMobileViewport } from '../../config';
|
||||
import { Item } from '../../model';
|
||||
import './footer.scss';
|
||||
export default function Footer({ items, pageName }: { items: Item[]; pageName: string }) {
|
||||
return (
|
||||
<div className={classNames('footer', { 'is-hidden': !isMobileViewport() && getItemPageNames(items).includes(pageName) })}>
|
||||
<Branding modifier='footer' logoContent={<img src={assetUrl('logo.svg')} width='150px' height='60px' alt='' />}>
|
||||
<span className='footnote'>
|
||||
AOE is a leading global provider of services for digital transformation and digital business models. AOE relies exclusively on established Enterprise
|
||||
Open Source technologies. This leads to innovative solutions, digital products and portals in agile software projects, and helps build long-lasting,
|
||||
strategic partnerships with our customers.
|
||||
</span>
|
||||
</Branding>
|
||||
<FooterEnd />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
11
src/components/Footer/footer.scss
Normal file
11
src/components/Footer/footer.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
.footer {
|
||||
border-top: 1px solid var(--color-gray-normal);
|
||||
transition: opacity 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98) 1500ms;
|
||||
opacity: 1;
|
||||
backface-visibility: hidden;
|
||||
|
||||
&.is-hidden {
|
||||
opacity: 0;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default function FooterEnd({modifier}: {modifier?: "in-sidebar"}) {
|
||||
return (
|
||||
<div className={classNames('footer-end', {[`footer-end__${modifier}`]: modifier})}>
|
||||
<div className="footer-social">
|
||||
<div className="footer-social__label">
|
||||
<p>Follow us:</p>
|
||||
</div>
|
||||
<div className="footer-social__links">
|
||||
<a className="social-links-icon" href="https://www.facebook.com/aoepeople" target="_blank" rel="noopener noreferrer"><i className="socicon-facebook social-icon"></i></a>
|
||||
<a className="social-links-icon" href="https://twitter.com/aoepeople" target="_blank" rel="noopener noreferrer"><i className="socicon-twitter social-icon"></i></a>
|
||||
<a className="social-links-icon" href="https://www.linkedin.com/company/aoe" target="_blank" rel="noopener noreferrer"><i className="socicon-linkedin social-icon"></i></a>
|
||||
<a className="social-links-icon" href="https://www.xing.com/company/aoe" target="_blank" rel="noopener noreferrer"><i className="socicon-xing social-icon"></i></a>
|
||||
<a className="social-links-icon" href="https://www.youtube.com/user/aoepeople" target="_blank" rel="noopener noreferrer"><i className="socicon-youtube social-icon"></i></a>
|
||||
<a className="social-links-icon" href="https://github.com/aoepeople" target="_blank" rel="noopener noreferrer"><i className="socicon-github social-icon"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer-copyright">
|
||||
<p><a href="https://www.aoe.com/en/copyright-meta/legal-information.html" target="_blank" rel="noopener noreferrer">Legal Information</a></p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
41
src/components/FooterEnd/FooterEnd.tsx
Normal file
41
src/components/FooterEnd/FooterEnd.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import './footerend.scss';
|
||||
export default function FooterEnd({ modifier }: { modifier?: 'in-sidebar' }) {
|
||||
return (
|
||||
<div className={classNames('footer-end', { [`footer-end__${modifier}`]: modifier })}>
|
||||
<div className='footer-social'>
|
||||
<div className='footer-social__label'>
|
||||
<p>Follow us:</p>
|
||||
</div>
|
||||
<div className='footer-social__links'>
|
||||
<a className='social-links-icon' href='https://www.facebook.com/aoepeople' target='_blank' rel='noopener noreferrer'>
|
||||
<i className='socicon-facebook social-icon'></i>
|
||||
</a>
|
||||
<a className='social-links-icon' href='https://twitter.com/aoepeople' target='_blank' rel='noopener noreferrer'>
|
||||
<i className='socicon-twitter social-icon'></i>
|
||||
</a>
|
||||
<a className='social-links-icon' href='https://www.linkedin.com/company/aoe' target='_blank' rel='noopener noreferrer'>
|
||||
<i className='socicon-linkedin social-icon'></i>
|
||||
</a>
|
||||
<a className='social-links-icon' href='https://www.xing.com/company/aoe' target='_blank' rel='noopener noreferrer'>
|
||||
<i className='socicon-xing social-icon'></i>
|
||||
</a>
|
||||
<a className='social-links-icon' href='https://www.youtube.com/user/aoepeople' target='_blank' rel='noopener noreferrer'>
|
||||
<i className='socicon-youtube social-icon'></i>
|
||||
</a>
|
||||
<a className='social-links-icon' href='https://github.com/aoepeople' target='_blank' rel='noopener noreferrer'>
|
||||
<i className='socicon-github social-icon'></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className='footer-copyright'>
|
||||
<p>
|
||||
<a href='https://www.aoe.com/en/copyright-meta/legal-information.html' target='_blank' rel='noopener noreferrer'>
|
||||
Legal Information
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
src/components/FooterEnd/footerend.scss
Normal file
36
src/components/FooterEnd/footerend.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
.footer-end {
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-normal);
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 0 10px 0;
|
||||
|
||||
& a {
|
||||
color: var(--color-gray-normal);
|
||||
}
|
||||
|
||||
&__in-sidebar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-top: 100px;
|
||||
}
|
||||
|
||||
@media (--until-sm) {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 20px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-social {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
&__label {
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import React, { useState, useRef, MouseEventHandler } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Branding from './Branding';
|
||||
import Link from './Link';
|
||||
import LogoLink from './LogoLink';
|
||||
import Search from './Search';
|
||||
import { radarNameShort } from '../config';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
export default function Header({ pageName }: { pageName: string }) {
|
||||
const [searchOpen, setSearchOpen] = useState(false)
|
||||
const [search, setSearch] = useState('')
|
||||
const history = useHistory()
|
||||
const searchRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const openSearch = () => {
|
||||
setSearchOpen(true)
|
||||
}
|
||||
|
||||
const closeSearch = () => {
|
||||
setSearchOpen(false)
|
||||
}
|
||||
|
||||
const handleSearchChange = setSearch
|
||||
|
||||
const handleSearchSubmit = () => {
|
||||
history.location.search = search
|
||||
|
||||
setSearchOpen(false)
|
||||
setSearch('')
|
||||
}
|
||||
|
||||
const handleOpenClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
// e.preventDefault(); // todo used to be a link
|
||||
openSearch();
|
||||
setTimeout(() => {
|
||||
searchRef?.current?.focus();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
const smallLogo = pageName !== 'index';
|
||||
|
||||
return (
|
||||
<Branding
|
||||
logoContent={<LogoLink small={smallLogo} />}
|
||||
>
|
||||
<div className="nav">
|
||||
<div className="nav__item">
|
||||
<Link pageName="help-and-about-tech-radar" className="icon-link">
|
||||
<span className="icon icon--question icon-link__icon"></span>How to Use {radarNameShort}?
|
||||
</Link>
|
||||
</div>
|
||||
<div className="nav__item">
|
||||
<Link pageName="overview" className="icon-link">
|
||||
<span className="icon icon--overview icon-link__icon"></span>Technologies Overview
|
||||
</Link>
|
||||
</div>
|
||||
<div className="nav__item">
|
||||
<button className="icon-link" onClick={handleOpenClick}>
|
||||
<span className="icon icon--search icon-link__icon"></span>Search
|
||||
</button>
|
||||
<div className={classNames('nav__search', { 'is-open': searchOpen })}>
|
||||
<Search
|
||||
value={search}
|
||||
onClose={closeSearch}
|
||||
onSubmit={handleSearchSubmit}
|
||||
onChange={handleSearchChange}
|
||||
open={searchOpen}
|
||||
ref={searchRef}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Branding>
|
||||
);
|
||||
}
|
||||
67
src/components/Header/Header.tsx
Normal file
67
src/components/Header/Header.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import React, { useState, useRef, MouseEventHandler } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Branding from '../Branding/Branding';
|
||||
import Link from '../Link/Link';
|
||||
import LogoLink from '../LogoLink/LogoLink';
|
||||
import Search from '../Search/Search';
|
||||
import { radarNameShort } from '../../config';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
export default function Header({ pageName }: { pageName: string }) {
|
||||
const [searchOpen, setSearchOpen] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const history = useHistory();
|
||||
const searchRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const openSearch = () => {
|
||||
setSearchOpen(true);
|
||||
};
|
||||
|
||||
const closeSearch = () => {
|
||||
setSearchOpen(false);
|
||||
};
|
||||
|
||||
const handleSearchChange = setSearch;
|
||||
|
||||
const handleSearchSubmit = () => {
|
||||
history.location.search = search;
|
||||
|
||||
setSearchOpen(false);
|
||||
setSearch('');
|
||||
};
|
||||
|
||||
const handleOpenClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
// e.preventDefault(); // todo used to be a link
|
||||
openSearch();
|
||||
setTimeout(() => {
|
||||
searchRef?.current?.focus();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const smallLogo = pageName !== 'index';
|
||||
|
||||
return (
|
||||
<Branding logoContent={<LogoLink small={smallLogo} />}>
|
||||
<div className='nav'>
|
||||
<div className='nav__item'>
|
||||
<Link pageName='help-and-about-tech-radar' className='icon-link'>
|
||||
<span className='icon icon--question icon-link__icon'></span>How to Use {radarNameShort}?
|
||||
</Link>
|
||||
</div>
|
||||
<div className='nav__item'>
|
||||
<Link pageName='overview' className='icon-link'>
|
||||
<span className='icon icon--overview icon-link__icon'></span>Technologies Overview
|
||||
</Link>
|
||||
</div>
|
||||
<div className='nav__item'>
|
||||
<button className='icon-link' onClick={handleOpenClick}>
|
||||
<span className='icon icon--search icon-link__icon'></span>Search
|
||||
</button>
|
||||
<div className={classNames('nav__search', { 'is-open': searchOpen })}>
|
||||
<Search value={search} onClose={closeSearch} onSubmit={handleSearchSubmit} onChange={handleSearchChange} open={searchOpen} ref={searchRef} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Branding>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default function({ children, secondary = false }: React.PropsWithChildren<{secondary?: boolean}>) {
|
||||
return (
|
||||
<div
|
||||
className={classNames('headline-group', {'headline-group--secondary': secondary})}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
6
src/components/HeadlineGroup/HeadlineGroup.tsx
Normal file
6
src/components/HeadlineGroup/HeadlineGroup.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import './headline-group.scss';
|
||||
export default function ({ children, secondary = false }: React.PropsWithChildren<{ secondary?: boolean }>) {
|
||||
return <div className={classNames('headline-group', { 'headline-group--secondary': secondary })}>{children}</div>;
|
||||
}
|
||||
13
src/components/HeadlineGroup/headline-group.scss
Normal file
13
src/components/HeadlineGroup/headline-group.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
.headline-group {
|
||||
margin: 0 0 60px;
|
||||
@media (--until-sm) {
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
&--secondary {
|
||||
margin: 10px 0;
|
||||
@media (--until-sm) {
|
||||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function({ children, alt }: React.PropsWithChildren<{alt?: string}>) {
|
||||
return (
|
||||
<div className="hero-headline">
|
||||
{children}
|
||||
<span className="hero-headline__alt">{alt}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
10
src/components/HeroHeadline/HeroHeadline.tsx
Normal file
10
src/components/HeroHeadline/HeroHeadline.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import './hero-headline.scss';
|
||||
export default function ({ children, alt }: React.PropsWithChildren<{ alt?: string }>) {
|
||||
return (
|
||||
<div className='hero-headline'>
|
||||
{children}
|
||||
<span className='hero-headline__alt'>{alt}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
src/components/HeroHeadline/hero-headline.scss
Normal file
23
src/components/HeroHeadline/hero-headline.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
.hero-headline {
|
||||
font-size: 38px;
|
||||
font-weight: 300;
|
||||
line-height: 1.2;
|
||||
color: var(--color-white);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
&__alt {
|
||||
color: var(--color-gray-light);
|
||||
}
|
||||
|
||||
&--inverse {
|
||||
color: var(--color-gray-light);
|
||||
}
|
||||
|
||||
@media (--until-sm) {
|
||||
font-size: 26px;
|
||||
&__alt {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Link from './Link';
|
||||
import Flag from './Flag';
|
||||
import { Item as mItem } from '../model';
|
||||
|
||||
import Link from '../Link/Link';
|
||||
import Flag from '../Flag/Flag';
|
||||
import { Item as mItem } from '../../model';
|
||||
import './item.scss';
|
||||
type ItemProps = {
|
||||
item: mItem
|
||||
noLeadingBorder?: boolean
|
||||
active?: boolean
|
||||
style: React.CSSProperties
|
||||
}
|
||||
item: mItem;
|
||||
noLeadingBorder?: boolean;
|
||||
active?: boolean;
|
||||
style: React.CSSProperties;
|
||||
};
|
||||
|
||||
export default function Item({ item, noLeadingBorder = false, active = false, style = {}}: ItemProps) {
|
||||
export default function Item({ item, noLeadingBorder = false, active = false, style = {} }: ItemProps) {
|
||||
return (
|
||||
<Link
|
||||
className={classNames('item', {
|
||||
@@ -21,11 +21,11 @@ export default function Item({ item, noLeadingBorder = false, active = false, st
|
||||
pageName={`${item.quadrant}/${item.name}`}
|
||||
style={style}
|
||||
>
|
||||
<div className="item__title">
|
||||
<div className='item__title'>
|
||||
{item.title}
|
||||
<Flag item={item} />
|
||||
</div>
|
||||
{item.info && <div className="item__info">{item.info}</div>}
|
||||
{item.info && <div className='item__info'>{item.info}</div>}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
49
src/components/Item/item.scss
Normal file
49
src/components/Item/item.scss
Normal file
@@ -0,0 +1,49 @@
|
||||
.item {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid var(--color-gray-normal);
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
transition: background 200ms ease-out;
|
||||
color: var(--color-gray-normal);
|
||||
box-sizing: border-box;
|
||||
|
||||
&.is-active {
|
||||
background: var(--color-gray-dark-alt);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-gray-dark-alt2);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top: 1px solid var(--color-gray-normal);
|
||||
}
|
||||
|
||||
&--big {
|
||||
min-height: 80px;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
&--no-leading-border {
|
||||
&:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
&--no-trailing-border {
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 16px;
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
&__info {
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-normal);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react';
|
||||
import Item from './Item';
|
||||
import { Item as mItem } from '../model'
|
||||
|
||||
type ItemListProps = {
|
||||
items: mItem[]
|
||||
activeItem?: mItem
|
||||
noLeadingBorder?: boolean
|
||||
headerStyle?: React.CSSProperties
|
||||
itemStyle?: React.CSSProperties[]
|
||||
}
|
||||
|
||||
export default function ItemList({ children, items, activeItem, noLeadingBorder, headerStyle = {}, itemStyle = [] }: React.PropsWithChildren<ItemListProps>) {
|
||||
return (
|
||||
<div className="item-list">
|
||||
<div className="item-list__header" style={headerStyle}>
|
||||
{children}
|
||||
</div>
|
||||
<div className="item-list__list">
|
||||
{
|
||||
items.map((item, i) => (
|
||||
<Item
|
||||
key={item.name}
|
||||
item={item}
|
||||
noLeadingBorder={noLeadingBorder}
|
||||
active={activeItem !== null && activeItem !== undefined && activeItem.name === item.name}
|
||||
style={itemStyle[i]}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
32
src/components/ItemList/ItemList.tsx
Normal file
32
src/components/ItemList/ItemList.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import Item from '../Item/Item';
|
||||
import { Item as mItem } from '../../model';
|
||||
import './item-list.scss';
|
||||
type ItemListProps = {
|
||||
items: mItem[];
|
||||
activeItem?: mItem;
|
||||
noLeadingBorder?: boolean;
|
||||
headerStyle?: React.CSSProperties;
|
||||
itemStyle?: React.CSSProperties[];
|
||||
};
|
||||
|
||||
export default function ItemList({ children, items, activeItem, noLeadingBorder, headerStyle = {}, itemStyle = [] }: React.PropsWithChildren<ItemListProps>) {
|
||||
return (
|
||||
<div className='item-list'>
|
||||
<div className='item-list__header' style={headerStyle}>
|
||||
{children}
|
||||
</div>
|
||||
<div className='item-list__list'>
|
||||
{items.map((item, i) => (
|
||||
<Item
|
||||
key={item.name}
|
||||
item={item}
|
||||
noLeadingBorder={noLeadingBorder}
|
||||
active={activeItem !== null && activeItem !== undefined && activeItem.name === item.name}
|
||||
style={itemStyle[i]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
6
src/components/ItemList/item-list.scss
Normal file
6
src/components/ItemList/item-list.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
.item-list {
|
||||
margin: 0 0 25px;
|
||||
&__header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import Badge from './Badge';
|
||||
import { formatRelease } from '../date';
|
||||
import { Revision } from '../model';
|
||||
|
||||
export default function ItemRevision({ revision }: {revision: Revision}) {
|
||||
return (
|
||||
<div className="item-revision">
|
||||
<div>
|
||||
<Badge type={revision.ring}>{revision.ring} | {formatRelease(revision.release)}</Badge>
|
||||
</div>
|
||||
<div className="markdown" dangerouslySetInnerHTML={{__html: revision.body}} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
17
src/components/ItemRevision/ItemRevision.tsx
Normal file
17
src/components/ItemRevision/ItemRevision.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import Badge from '../Badge/Badge';
|
||||
import { formatRelease } from '../../date';
|
||||
import { Revision } from '../../model';
|
||||
|
||||
export default function ItemRevision({ revision }: { revision: Revision }) {
|
||||
return (
|
||||
<div className='item-revision'>
|
||||
<div>
|
||||
<Badge type={revision.ring}>
|
||||
{revision.ring} | {formatRelease(revision.release)}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className='markdown' dangerouslySetInnerHTML={{ __html: revision.body }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
5
src/components/ItemRevision/item-revision.scss
Normal file
5
src/components/ItemRevision/item-revision.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
.item-revision {
|
||||
&+.item-revision {
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
import HeadlineGroup from './HeadlineGroup';
|
||||
import ItemRevision from './ItemRevision';
|
||||
import { Revision } from '../model';
|
||||
|
||||
export default function ItemRevisions({ revisions }: {revisions: Revision[]}) {
|
||||
return (
|
||||
<div className="item-revisions">
|
||||
<HeadlineGroup secondary>
|
||||
<h4 className="headline headline--dark">Revisions:</h4>
|
||||
</HeadlineGroup>
|
||||
|
||||
{revisions.map(revision => (
|
||||
<ItemRevision key={revision.release} revision={revision} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
src/components/ItemRevisions/ItemRevisions.tsx
Normal file
18
src/components/ItemRevisions/ItemRevisions.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import HeadlineGroup from '../HeadlineGroup/HeadlineGroup';
|
||||
import ItemRevision from '../ItemRevision/ItemRevision';
|
||||
import { Revision } from '../../model';
|
||||
import './item-revisions.scss';
|
||||
export default function ItemRevisions({ revisions }: { revisions: Revision[] }) {
|
||||
return (
|
||||
<div className='item-revisions'>
|
||||
<HeadlineGroup secondary>
|
||||
<h4 className='headline headline--dark'>Revisions:</h4>
|
||||
</HeadlineGroup>
|
||||
|
||||
{revisions.map((revision) => (
|
||||
<ItemRevision key={revision.release} revision={revision} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
3
src/components/ItemRevisions/item-revisions.scss
Normal file
3
src/components/ItemRevisions/item-revisions.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
.item-revisions {
|
||||
margin-top: 60px;
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Link as RLink } from 'react-router-dom';
|
||||
|
||||
import './link.scss';
|
||||
type LinkProps = {
|
||||
pageName: string
|
||||
style?: React.CSSProperties
|
||||
className?: string
|
||||
}
|
||||
pageName: string;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function Link({ pageName, children, className, style = {} }: React.PropsWithChildren<LinkProps>) {
|
||||
return (
|
||||
8
src/components/Link/link.scss
Normal file
8
src/components/Link/link.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
.link {
|
||||
color: var(--color-white);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Link from './Link';
|
||||
import { assetUrl, radarNameShort } from '../config';
|
||||
|
||||
export default function LogoLink({ small = false }: { small?: boolean }) {
|
||||
return (
|
||||
<Link pageName="index" className={classNames('logo-link', { 'logo-link--small': small })}>
|
||||
<span className="logo-link__icon icon icon--back"></span>
|
||||
<span className="logo-link__slide">
|
||||
<img className="logo-link__img" src={assetUrl('logo.svg')} width="150px" height="60px" alt={radarNameShort} />
|
||||
<span className="logo-link__text">
|
||||
{radarNameShort}
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
16
src/components/LogoLink/LogoLink.tsx
Normal file
16
src/components/LogoLink/LogoLink.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Link from '../Link/Link';
|
||||
import { assetUrl, radarNameShort } from '../../config';
|
||||
import './logo-link.scss';
|
||||
export default function LogoLink({ small = false }: { small?: boolean }) {
|
||||
return (
|
||||
<Link pageName='index' className={classNames('logo-link', { 'logo-link--small': small })}>
|
||||
<span className='logo-link__icon icon icon--back'></span>
|
||||
<span className='logo-link__slide'>
|
||||
<img className='logo-link__img' src={assetUrl('logo.svg')} width='150px' height='60px' alt={radarNameShort} />
|
||||
<span className='logo-link__text'>{radarNameShort}</span>
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
63
src/components/LogoLink/logo-link.scss
Normal file
63
src/components/LogoLink/logo-link.scss
Normal file
@@ -0,0 +1,63 @@
|
||||
.logo-link {
|
||||
display: inline-block;
|
||||
transition: 200ms all ease-out;
|
||||
width: 400px;
|
||||
color: var(--color-white);
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
|
||||
&__slide {
|
||||
transition: 400ms all cubic-bezier(0.54, 0, 0.28, 1);
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
transition: 400ms opacity ease-out;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&__img {
|
||||
transition: 400ms all cubic-bezier(0.54, 0, 0.28, 1);
|
||||
}
|
||||
|
||||
&__text {
|
||||
transition: 400ms all cubic-bezier(0.54, 0, 0.28, 1);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 130px;
|
||||
white-space: nowrap;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
margin-top: -1px;
|
||||
transform: translateY(-50%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&--small {
|
||||
.logo-link__img {
|
||||
transform: scale(0.55);
|
||||
}
|
||||
|
||||
.logo-link__text {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.logo-link__slide {
|
||||
transform: translateX(-32px);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.logo-link__icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.logo-link__slide {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import React from 'react';
|
||||
import HeroHeadline from './HeroHeadline';
|
||||
import Fadeable from './Fadeable';
|
||||
import SetTitle from './SetTitle';
|
||||
import { radarName } from '../config';
|
||||
|
||||
export default function PageHelp({ leaving, onLeave}: {leaving: boolean, onLeave: () => void}) {
|
||||
return (
|
||||
<Fadeable leaving={leaving} onLeave={onLeave}>
|
||||
<SetTitle title={ "How to use the " + radarName } />
|
||||
<HeroHeadline>How to use the {radarName}</HeroHeadline>
|
||||
<div className="fullpage-content">
|
||||
<h3>Introduction</h3>
|
||||
<p>Technology is moving fast and new technologies and innovations appear continuously.</p>
|
||||
<p>It's essential for a development and technology company such as AOE to constantly improve and keep track with the latest useful innovations.
|
||||
It is important to openly look for innovations and new technologies and to question established technologies and methods every now and then.</p>
|
||||
<p>But, it is also important to wisely choose which technologies to use in our daily work and in the different projects we are carrying out. As we all know: There is no silver bullet.</p>
|
||||
<h3>What is the {radarName}</h3>
|
||||
<p>The Tech Radar is an overview of different technologies - from languages, frameworks, tools and patterns to platforms - that we consider "new or mentionable".
|
||||
The radar therefore doesn't provide an overview of all established technologies - but it focuses on items that have recently gained in importance or changed.
|
||||
</p>
|
||||
<h3 >How it is created</h3><p>The items in the technology radar are raised by the different teams and therefore a lot of the items are related to the work and challenges the teams face in the different projects. In fact, we don't include anything on the radar, which we haven't already tried ourselves at least once.</p>
|
||||
<p>There have been a lot of valuable discussions in different expert groups about the classification and details of each of technologies and innovations. And the result of all this can be found in the latest technology radar.</p>
|
||||
<h3 >How should it be used</h3>
|
||||
<p>The radar acts as an overview of technologies that we think everyone in the teams should currently know about.</p>
|
||||
<p>Its goal is to act as a guide and inspiration for the daily work in the teams. Its purpose is also to provide helpful information and a bird's-eye perspective - so that decisions can be taken with a much deeper understanding of the subject matter. This results in more-informed and better-aligned decisions.</p>
|
||||
<p>We also hope that developers outside of AOE find the informations in our technologie overview inspirational.</p>
|
||||
<p>We group or categorize the items in 4 quadrants - (sometimes, when it's not 100% clear where a item belongs, we choose the best fit).</p>
|
||||
<p>The quadrants are:</p>
|
||||
<ul>
|
||||
<li><strong>Languages and Frameworks:</strong> We've placed development languages (such as Scala or Golang) here, as well as more low-level development frameworks (such as Play or Symfony), which are useful for implementing custom software of all kinds. </li>
|
||||
<li><strong>Tools:</strong> Here we put different software tools - from small helpers to bigger software projects</li>
|
||||
<li><strong>Methods and Patterns:</strong> Patterns are so important, and a lot of them are valid for a long time (compared to some tools or frameworks). So, this is the category where we put information on methods and patterns concerning development, continuous x, testing, organization, architecture, etc.</li>
|
||||
<li><strong>Platforms and Services</strong> (including AOE internal Services): Here we include infrastructure platforms and services. We also use this category to communicate news about AOE services that we want all AOE teams to be aware of.</li>
|
||||
</ul>
|
||||
<p>Each of the items is classified in one of these rings:</p>
|
||||
<ul>
|
||||
<li><strong>Adopt:</strong> We can clearly recommend this technology. We have used it for longer period of time in many teams and it has proven to be stable and useful.</li>
|
||||
<li><strong>Trial:</strong> We have used it with success and recommend to have a closer look at the technology in this ring. The goal of items here is to look at them more closely, with the goal to bring them to the adopt level.</li>
|
||||
<li><strong>Assess:</strong> We have tried it out and we find it promising. We recommend having a look at these items when you face a specific need for the technology in your project.</li>
|
||||
<li><strong>Hold:</strong> This category is a bit special. Unlike the others, we recommend to stop doing or using something. That does not mean that there are bad and it often might be ok to use them in existing projects. But we move things here if we think we shouldn't do them anymore - because we see better options or alternatives now.</li>
|
||||
</ul>
|
||||
<p>Contributions and source code of the radar are on github: <a href="https://github.com/AOEpeople/aoe_technology_radar" target="_blank" rel="noopener noreferrer">AOE Tech Radar on Github</a></p>
|
||||
|
||||
|
||||
</div>
|
||||
</Fadeable>
|
||||
);
|
||||
}
|
||||
95
src/components/PageHelp/PageHelp.tsx
Normal file
95
src/components/PageHelp/PageHelp.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
import HeroHeadline from '../HeroHeadline/HeroHeadline';
|
||||
import Fadeable from '../Fadeable/Fadeable';
|
||||
import SetTitle from '../SetTitle';
|
||||
import { radarName } from '../../config';
|
||||
|
||||
export default function PageHelp({ leaving, onLeave }: { leaving: boolean; onLeave: () => void }) {
|
||||
return (
|
||||
<Fadeable leaving={leaving} onLeave={onLeave}>
|
||||
<SetTitle title={'How to use the ' + radarName} />
|
||||
<HeroHeadline>How to use the {radarName}</HeroHeadline>
|
||||
<div className='fullpage-content'>
|
||||
<h3>Introduction</h3>
|
||||
<p>Technology is moving fast and new technologies and innovations appear continuously.</p>
|
||||
<p>
|
||||
It's essential for a development and technology company such as AOE to constantly improve and keep track with the latest useful innovations. It is
|
||||
important to openly look for innovations and new technologies and to question established technologies and methods every now and then.
|
||||
</p>
|
||||
<p>
|
||||
But, it is also important to wisely choose which technologies to use in our daily work and in the different projects we are carrying out. As we all
|
||||
know: There is no silver bullet.
|
||||
</p>
|
||||
<h3>What is the {radarName}</h3>
|
||||
<p>
|
||||
The Tech Radar is an overview of different technologies - from languages, frameworks, tools and patterns to platforms - that we consider "new or
|
||||
mentionable". The radar therefore doesn't provide an overview of all established technologies - but it focuses on items that have recently gained in
|
||||
importance or changed.
|
||||
</p>
|
||||
<h3>How it is created</h3>
|
||||
<p>
|
||||
The items in the technology radar are raised by the different teams and therefore a lot of the items are related to the work and challenges the teams
|
||||
face in the different projects. In fact, we don't include anything on the radar, which we haven't already tried ourselves at least once.
|
||||
</p>
|
||||
<p>
|
||||
There have been a lot of valuable discussions in different expert groups about the classification and details of each of technologies and innovations.
|
||||
And the result of all this can be found in the latest technology radar.
|
||||
</p>
|
||||
<h3>How should it be used</h3>
|
||||
<p>The radar acts as an overview of technologies that we think everyone in the teams should currently know about.</p>
|
||||
<p>
|
||||
Its goal is to act as a guide and inspiration for the daily work in the teams. Its purpose is also to provide helpful information and a bird's-eye
|
||||
perspective - so that decisions can be taken with a much deeper understanding of the subject matter. This results in more-informed and better-aligned
|
||||
decisions.
|
||||
</p>
|
||||
<p>We also hope that developers outside of AOE find the informations in our technologie overview inspirational.</p>
|
||||
<p>We group or categorize the items in 4 quadrants - (sometimes, when it's not 100% clear where a item belongs, we choose the best fit).</p>
|
||||
<p>The quadrants are:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Languages and Frameworks:</strong> We've placed development languages (such as Scala or Golang) here, as well as more low-level development
|
||||
frameworks (such as Play or Symfony), which are useful for implementing custom software of all kinds.{' '}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Tools:</strong> Here we put different software tools - from small helpers to bigger software projects
|
||||
</li>
|
||||
<li>
|
||||
<strong>Methods and Patterns:</strong> Patterns are so important, and a lot of them are valid for a long time (compared to some tools or
|
||||
frameworks). So, this is the category where we put information on methods and patterns concerning development, continuous x, testing, organization,
|
||||
architecture, etc.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Platforms and Services</strong> (including AOE internal Services): Here we include infrastructure platforms and services. We also use this
|
||||
category to communicate news about AOE services that we want all AOE teams to be aware of.
|
||||
</li>
|
||||
</ul>
|
||||
<p>Each of the items is classified in one of these rings:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Adopt:</strong> We can clearly recommend this technology. We have used it for longer period of time in many teams and it has proven to be
|
||||
stable and useful.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Trial:</strong> We have used it with success and recommend to have a closer look at the technology in this ring. The goal of items here is
|
||||
to look at them more closely, with the goal to bring them to the adopt level.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Assess:</strong> We have tried it out and we find it promising. We recommend having a look at these items when you face a specific need for
|
||||
the technology in your project.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Hold:</strong> This category is a bit special. Unlike the others, we recommend to stop doing or using something. That does not mean that
|
||||
there are bad and it often might be ok to use them in existing projects. But we move things here if we think we shouldn't do them anymore - because
|
||||
we see better options or alternatives now.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Contributions and source code of the radar are on github:{' '}
|
||||
<a href='https://github.com/AOEpeople/aoe_technology_radar' target='_blank' rel='noopener noreferrer'>
|
||||
AOE Tech Radar on Github
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</Fadeable>
|
||||
);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
import { formatRelease } from '../date';
|
||||
import { featuredOnly, Item } from '../model';
|
||||
import HeroHeadline from './HeroHeadline';
|
||||
import QuadrantGrid from './QuadrantGrid';
|
||||
import Fadeable from './Fadeable';
|
||||
import SetTitle from './SetTitle';
|
||||
import { radarName, radarNameShort } from '../config';
|
||||
import { MomentInput } from 'moment';
|
||||
|
||||
type PageIndexProps = {
|
||||
leaving: boolean
|
||||
onLeave: () => void
|
||||
items: Item[]
|
||||
releases: MomentInput[]
|
||||
}
|
||||
|
||||
export default function PageIndex({ leaving, onLeave, items, releases }: PageIndexProps) {
|
||||
const newestRelease = releases.slice(-1)[0];
|
||||
const numberOfReleases = releases.length;
|
||||
return (
|
||||
<Fadeable leaving={leaving} onLeave={onLeave}>
|
||||
<SetTitle title={radarNameShort} />
|
||||
<div className="headline-group">
|
||||
<HeroHeadline alt={`Version #${numberOfReleases}`}>
|
||||
{radarName}
|
||||
</HeroHeadline>
|
||||
</div>
|
||||
<QuadrantGrid items={featuredOnly(items)} />
|
||||
<div className="publish-date">
|
||||
Published {formatRelease(newestRelease)}
|
||||
</div>
|
||||
</Fadeable>
|
||||
);
|
||||
}
|
||||
31
src/components/PageIndex/PageIndex.tsx
Normal file
31
src/components/PageIndex/PageIndex.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { formatRelease } from '../../date';
|
||||
import { featuredOnly, Item } from '../../model';
|
||||
import HeroHeadline from '../HeroHeadline/HeroHeadline';
|
||||
import QuadrantGrid from '../QuadrantGrid/QuadrantGrid';
|
||||
import Fadeable from '../Fadeable/Fadeable';
|
||||
import SetTitle from '../SetTitle';
|
||||
import { radarName, radarNameShort } from '../../config';
|
||||
import { MomentInput } from 'moment';
|
||||
|
||||
type PageIndexProps = {
|
||||
leaving: boolean;
|
||||
onLeave: () => void;
|
||||
items: Item[];
|
||||
releases: MomentInput[];
|
||||
};
|
||||
|
||||
export default function PageIndex({ leaving, onLeave, items, releases }: PageIndexProps) {
|
||||
const newestRelease = releases.slice(-1)[0];
|
||||
const numberOfReleases = releases.length;
|
||||
return (
|
||||
<Fadeable leaving={leaving} onLeave={onLeave}>
|
||||
<SetTitle title={radarNameShort} />
|
||||
<div className='headline-group'>
|
||||
<HeroHeadline alt={`Version #${numberOfReleases}`}>{radarName}</HeroHeadline>
|
||||
</div>
|
||||
<QuadrantGrid items={featuredOnly(items)} />
|
||||
<div className='publish-date'>Published {formatRelease(newestRelease)}</div>
|
||||
</Fadeable>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +1,18 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Badge from './Badge';
|
||||
import ItemList from './ItemList';
|
||||
import Link from './Link';
|
||||
import FooterEnd from './FooterEnd';
|
||||
import SetTitle from './SetTitle';
|
||||
import ItemRevisions from './ItemRevisions';
|
||||
import { createAnimation, createAnimationRunner } from '../animation';
|
||||
|
||||
import { translate } from '../config';
|
||||
import { groupByQuadrants, Item } from '../model';
|
||||
import Badge from '../Badge/Badge';
|
||||
import ItemList from '../ItemList/ItemList';
|
||||
import Link from '../Link/Link';
|
||||
import FooterEnd from '../FooterEnd/FooterEnd';
|
||||
import SetTitle from '../SetTitle';
|
||||
import ItemRevisions from '../ItemRevisions/ItemRevisions';
|
||||
import { createAnimation, createAnimationRunner } from '../../animation';
|
||||
import './item-page.scss';
|
||||
import { translate } from '../../config';
|
||||
import { groupByQuadrants, Item } from '../../model';
|
||||
|
||||
const getItem = (pageName: string, items: Item[]) => {
|
||||
const [quadrantName, itemName] = pageName.split('/');
|
||||
const item = items.filter(
|
||||
item => item.quadrant === quadrantName && item.name === itemName,
|
||||
)[0];
|
||||
const item = items.filter((item) => item.quadrant === quadrantName && item.name === itemName)[0];
|
||||
return item;
|
||||
};
|
||||
|
||||
@@ -25,11 +23,11 @@ const getItemsInRing = (pageName: string, items: Item[]) => {
|
||||
};
|
||||
|
||||
type PageItemProps = {
|
||||
pageName: string
|
||||
items: Item[]
|
||||
leaving: boolean
|
||||
onLeave: () => void
|
||||
}
|
||||
pageName: string;
|
||||
items: Item[];
|
||||
leaving: boolean;
|
||||
onLeave: () => void;
|
||||
};
|
||||
|
||||
export default function PageItem({ pageName, items, leaving, onLeave }: PageItemProps) {
|
||||
const itemsInRing = getItemsInRing(pageName, items);
|
||||
@@ -44,7 +42,7 @@ export default function PageItem({ pageName, items, leaving, onLeave }: PageItem
|
||||
transition: 'transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)',
|
||||
transform: 'translateX(0)',
|
||||
},
|
||||
0,
|
||||
0
|
||||
),
|
||||
navHeader: createAnimation(
|
||||
{
|
||||
@@ -56,7 +54,7 @@ export default function PageItem({ pageName, items, leaving, onLeave }: PageItem
|
||||
transform: 'translateX(0px)',
|
||||
opacity: '1',
|
||||
},
|
||||
300,
|
||||
300
|
||||
),
|
||||
text: createAnimation(
|
||||
{
|
||||
@@ -68,7 +66,7 @@ export default function PageItem({ pageName, items, leaving, onLeave }: PageItem
|
||||
transform: 'translateY(0px)',
|
||||
opacity: '1',
|
||||
},
|
||||
600,
|
||||
600
|
||||
),
|
||||
items: itemsInRing.map((item, i) =>
|
||||
createAnimation(
|
||||
@@ -81,8 +79,8 @@ export default function PageItem({ pageName, items, leaving, onLeave }: PageItem
|
||||
transform: 'translateX(0px)',
|
||||
opacity: '1',
|
||||
},
|
||||
400 + 100 * i,
|
||||
),
|
||||
400 + 100 * i
|
||||
)
|
||||
),
|
||||
footer: createAnimation(
|
||||
{
|
||||
@@ -95,16 +93,12 @@ export default function PageItem({ pageName, items, leaving, onLeave }: PageItem
|
||||
transform: 'translateX(0px)',
|
||||
opacity: '1',
|
||||
},
|
||||
600 + itemsInRing.length * 100,
|
||||
600 + itemsInRing.length * 100
|
||||
),
|
||||
};
|
||||
|
||||
const animationsOut = {
|
||||
background: createAnimation(
|
||||
animationsIn.background.stateB,
|
||||
animationsIn.background.stateA,
|
||||
300 + itemsInRing.length * 50,
|
||||
),
|
||||
background: createAnimation(animationsIn.background.stateB, animationsIn.background.stateA, 300 + itemsInRing.length * 50),
|
||||
navHeader: createAnimation(
|
||||
animationsIn.navHeader.stateB,
|
||||
{
|
||||
@@ -112,7 +106,7 @@ export default function PageItem({ pageName, items, leaving, onLeave }: PageItem
|
||||
transform: 'translateX(40px)',
|
||||
opacity: '0',
|
||||
},
|
||||
0,
|
||||
0
|
||||
),
|
||||
text: createAnimation(
|
||||
animationsIn.text.stateB,
|
||||
@@ -121,7 +115,7 @@ export default function PageItem({ pageName, items, leaving, onLeave }: PageItem
|
||||
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
|
||||
opacity: '0',
|
||||
},
|
||||
0,
|
||||
0
|
||||
),
|
||||
items: itemsInRing.map((item, i) =>
|
||||
createAnimation(
|
||||
@@ -131,8 +125,8 @@ export default function PageItem({ pageName, items, leaving, onLeave }: PageItem
|
||||
transform: 'translateX(40px)',
|
||||
opacity: '0',
|
||||
},
|
||||
100 + 50 * i,
|
||||
),
|
||||
100 + 50 * i
|
||||
)
|
||||
),
|
||||
footer: createAnimation(
|
||||
animationsIn.text.stateB,
|
||||
@@ -141,23 +135,23 @@ export default function PageItem({ pageName, items, leaving, onLeave }: PageItem
|
||||
transform: 'translateX(40px)',
|
||||
opacity: '0',
|
||||
},
|
||||
200 + itemsInRing.length * 50,
|
||||
200 + itemsInRing.length * 50
|
||||
),
|
||||
};
|
||||
|
||||
const [animations, setAnimations] = useState<any>();
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
if (leaving) {
|
||||
// entering from an other page
|
||||
// setAnimations(createAnimationRunner(animationsIn).getState())
|
||||
} else {
|
||||
// Hard refresh
|
||||
setAnimations(null)
|
||||
setAnimations(null);
|
||||
}
|
||||
}, [leaving])
|
||||
}, [leaving]);
|
||||
|
||||
const [stateLeaving, setStateLeaving] = useState(leaving)
|
||||
const [stateLeaving, setStateLeaving] = useState(leaving);
|
||||
|
||||
let [animationRunner, setAnimationRunner] = useState<any>();
|
||||
|
||||
@@ -198,73 +192,49 @@ export default function PageItem({ pageName, items, leaving, onLeave }: PageItem
|
||||
return (
|
||||
<div>
|
||||
<SetTitle title={item.title} />
|
||||
<div className="item-page">
|
||||
<div className="item-page__nav">
|
||||
<div className="item-page__nav__inner">
|
||||
<div
|
||||
className="item-page__header"
|
||||
style={getAnimationState('navHeader')}
|
||||
>
|
||||
<h3 className="headline">{translate(item.quadrant)}</h3>
|
||||
<div className='item-page'>
|
||||
<div className='item-page__nav'>
|
||||
<div className='item-page__nav__inner'>
|
||||
<div className='item-page__header' style={getAnimationState('navHeader')}>
|
||||
<h3 className='headline'>{translate(item.quadrant)}</h3>
|
||||
</div>
|
||||
|
||||
<ItemList
|
||||
items={itemsInRing}
|
||||
activeItem={item}
|
||||
headerStyle={getAnimationState('navHeader')}
|
||||
itemStyle={getAnimationState('items')}
|
||||
>
|
||||
<div className="split">
|
||||
<div className="split__left">
|
||||
<ItemList items={itemsInRing} activeItem={item} headerStyle={getAnimationState('navHeader')} itemStyle={getAnimationState('items')}>
|
||||
<div className='split'>
|
||||
<div className='split__left'>
|
||||
<Badge big type={item.ring}>
|
||||
{item.ring}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="split__right">
|
||||
<Link className="icon-link" pageName={item.quadrant}>
|
||||
<span className="icon icon--pie icon-link__icon" />Quadrant
|
||||
Overview
|
||||
</Link>
|
||||
<div className='split__right'>
|
||||
<Link className='icon-link' pageName={item.quadrant}>
|
||||
<span className='icon icon--pie icon-link__icon' />
|
||||
Quadrant Overview
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</ItemList>
|
||||
<div
|
||||
className="item-page__footer"
|
||||
style={getAnimationState('footer')}
|
||||
>
|
||||
<FooterEnd modifier="in-sidebar" />
|
||||
<div className='item-page__footer' style={getAnimationState('footer')}>
|
||||
<FooterEnd modifier='in-sidebar' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="item-page__content"
|
||||
style={getAnimationState('background')}
|
||||
>
|
||||
<div
|
||||
className="item-page__content__inner"
|
||||
style={getAnimationState('text')}
|
||||
>
|
||||
<div className="item-page__header">
|
||||
<div className="split">
|
||||
<div className="split__left">
|
||||
<h1 className="hero-headline hero-headline--inverse">
|
||||
{item.title}
|
||||
</h1>
|
||||
<div className='item-page__content' style={getAnimationState('background')}>
|
||||
<div className='item-page__content__inner' style={getAnimationState('text')}>
|
||||
<div className='item-page__header'>
|
||||
<div className='split'>
|
||||
<div className='split__left'>
|
||||
<h1 className='hero-headline hero-headline--inverse'>{item.title}</h1>
|
||||
</div>
|
||||
<div className="split__right">
|
||||
<div className='split__right'>
|
||||
<Badge big type={item.ring}>
|
||||
{item.ring}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="markdown"
|
||||
dangerouslySetInnerHTML={{ __html: item.body }}
|
||||
/>
|
||||
{item.revisions.length > 1 && (
|
||||
<ItemRevisions revisions={item.revisions.slice(1)} />
|
||||
)}
|
||||
<div className='markdown' dangerouslySetInnerHTML={{ __html: item.body }} />
|
||||
{item.revisions.length > 1 && <ItemRevisions revisions={item.revisions.slice(1)} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
62
src/components/PageItem/item-page.scss
Normal file
62
src/components/PageItem/item-page.scss
Normal file
@@ -0,0 +1,62 @@
|
||||
.item-page {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
width: 100vw;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
|
||||
&__nav,
|
||||
&__content {
|
||||
box-sizing: border-box;
|
||||
padding-top: 130px;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__header {
|
||||
min-height: 40px;
|
||||
margin: 0 0 25px;
|
||||
}
|
||||
|
||||
&__nav {
|
||||
background: var(--color-gray-dark);
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: calc(((100vw - 1200px) / 2) + 400px);
|
||||
}
|
||||
|
||||
&__nav__inner {
|
||||
box-sizing: border-box;
|
||||
float: right;
|
||||
width: 410px;
|
||||
padding: 0 40px 0 10px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
transform: translate3d(0, 0, 0);
|
||||
//flex: 0 0 calc((100vw - 1200px) / 2 + 800px);
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: calc((100vw - 1200px) / 2 + 800px);
|
||||
background: var(--color-white);
|
||||
|
||||
&__inner {
|
||||
box-sizing: border-box;
|
||||
width: 810px;
|
||||
padding: 0 10px 0 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-item-page {
|
||||
background: #fff;
|
||||
margin: 0 -15px;
|
||||
padding: 15px;
|
||||
min-height: 300px;
|
||||
|
||||
&__aside {
|
||||
padding: 20px 0
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import React from 'react';
|
||||
import Badge from './Badge';
|
||||
import ItemList from './ItemList';
|
||||
import Link from './Link';
|
||||
import Fadeable from './Fadeable';
|
||||
import SetTitle from './SetTitle';
|
||||
import ItemRevisions from './ItemRevisions';
|
||||
|
||||
import { translate } from '../config';
|
||||
import { groupByQuadrants, Item } from '../model';
|
||||
|
||||
type PageItemMobileProps = {
|
||||
pageName: string
|
||||
items: Item[]
|
||||
leaving: boolean
|
||||
onLeave: () => void
|
||||
}
|
||||
|
||||
export default function PageItemMobile({ pageName, items, leaving, onLeave }: PageItemMobileProps) {
|
||||
const getItem = (pageName: string, items: Item[]) => {
|
||||
const [quadrantName, itemName] = pageName.split('/');
|
||||
const item = items.filter(item => item.quadrant === quadrantName && item.name === itemName)[0];
|
||||
return item;
|
||||
}
|
||||
|
||||
const getItemsInRing = (pageName: string, items: Item[]) => {
|
||||
const item = getItem(pageName, items);
|
||||
const itemsInRing = groupByQuadrants(items)[item.quadrant][item.ring];
|
||||
return itemsInRing;
|
||||
};
|
||||
|
||||
const item = getItem(pageName, items);
|
||||
const itemsInRing = getItemsInRing(pageName, items);
|
||||
return (
|
||||
<Fadeable leaving={leaving} onLeave={onLeave}>
|
||||
<SetTitle title={item.title} />
|
||||
<div className="mobile-item-page">
|
||||
<div className="mobile-item-page__content">
|
||||
<div className="mobile-item-page__content__inner">
|
||||
<div className="mobile-item-page__header">
|
||||
<div className="split">
|
||||
<div className="split__left">
|
||||
<h3 className="headline">{translate(item.quadrant)}</h3>
|
||||
<h1 className="hero-headline hero-headline--inverse">{item.title}</h1>
|
||||
</div>
|
||||
<div className="split__right">
|
||||
<Badge big type={item.ring}>{item.ring}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="markdown" dangerouslySetInnerHTML={{ __html: item.body }} />
|
||||
{item.revisions.length > 1 && <ItemRevisions revisions={item.revisions.slice(1)} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<aside className="mobile-item-page__aside">
|
||||
<ItemList
|
||||
items={itemsInRing}
|
||||
activeItem={item}
|
||||
>
|
||||
<div className="split">
|
||||
<div className="split__left">
|
||||
<h3 className="headline">{translate(item.quadrant)}</h3>
|
||||
</div>
|
||||
<div className="split__right">
|
||||
<Link className="icon-link" pageName={item.quadrant}>
|
||||
<span className="icon icon--pie icon-link__icon"></span>Zoom In
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</ItemList>
|
||||
</aside>
|
||||
</Fadeable>
|
||||
);
|
||||
}
|
||||
74
src/components/PageItemMobile/PageItemMobile.tsx
Normal file
74
src/components/PageItemMobile/PageItemMobile.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import Badge from '../Badge/Badge';
|
||||
import ItemList from '../ItemList/ItemList';
|
||||
import Link from '../Link/Link';
|
||||
import Fadeable from '../Fadeable/Fadeable';
|
||||
import SetTitle from '../SetTitle';
|
||||
import ItemRevisions from '../ItemRevisions/ItemRevisions';
|
||||
|
||||
import { translate } from '../../config';
|
||||
import { groupByQuadrants, Item } from '../../model';
|
||||
|
||||
type PageItemMobileProps = {
|
||||
pageName: string;
|
||||
items: Item[];
|
||||
leaving: boolean;
|
||||
onLeave: () => void;
|
||||
};
|
||||
|
||||
export default function PageItemMobile({ pageName, items, leaving, onLeave }: PageItemMobileProps) {
|
||||
const getItem = (pageName: string, items: Item[]) => {
|
||||
const [quadrantName, itemName] = pageName.split('/');
|
||||
const item = items.filter((item) => item.quadrant === quadrantName && item.name === itemName)[0];
|
||||
return item;
|
||||
};
|
||||
|
||||
const getItemsInRing = (pageName: string, items: Item[]) => {
|
||||
const item = getItem(pageName, items);
|
||||
const itemsInRing = groupByQuadrants(items)[item.quadrant][item.ring];
|
||||
return itemsInRing;
|
||||
};
|
||||
|
||||
const item = getItem(pageName, items);
|
||||
const itemsInRing = getItemsInRing(pageName, items);
|
||||
return (
|
||||
<Fadeable leaving={leaving} onLeave={onLeave}>
|
||||
<SetTitle title={item.title} />
|
||||
<div className='mobile-item-page'>
|
||||
<div className='mobile-item-page__content'>
|
||||
<div className='mobile-item-page__content__inner'>
|
||||
<div className='mobile-item-page__header'>
|
||||
<div className='split'>
|
||||
<div className='split__left'>
|
||||
<h3 className='headline'>{translate(item.quadrant)}</h3>
|
||||
<h1 className='hero-headline hero-headline--inverse'>{item.title}</h1>
|
||||
</div>
|
||||
<div className='split__right'>
|
||||
<Badge big type={item.ring}>
|
||||
{item.ring}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='markdown' dangerouslySetInnerHTML={{ __html: item.body }} />
|
||||
{item.revisions.length > 1 && <ItemRevisions revisions={item.revisions.slice(1)} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<aside className='mobile-item-page__aside'>
|
||||
<ItemList items={itemsInRing} activeItem={item}>
|
||||
<div className='split'>
|
||||
<div className='split__left'>
|
||||
<h3 className='headline'>{translate(item.quadrant)}</h3>
|
||||
</div>
|
||||
<div className='split__right'>
|
||||
<Link className='icon-link' pageName={item.quadrant}>
|
||||
<span className='icon icon--pie icon-link__icon'></span>Zoom In
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</ItemList>
|
||||
</aside>
|
||||
</Fadeable>
|
||||
);
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import HeadlineGroup from './HeadlineGroup';
|
||||
import HeroHeadline from './HeroHeadline';
|
||||
import Badge from './Badge';
|
||||
import Link from './Link';
|
||||
import Search from './Search';
|
||||
import Fadeable from './Fadeable';
|
||||
import SetTitle from './SetTitle';
|
||||
import Flag from './Flag';
|
||||
import { groupByFirstLetter, Item } from '../model';
|
||||
import { translate, ring } from '../config';
|
||||
|
||||
const containsSearchTerm = (text = '', term = '') => {
|
||||
// TODO search refinement
|
||||
return (
|
||||
text
|
||||
.trim()
|
||||
.toLocaleLowerCase()
|
||||
.indexOf(term.trim().toLocaleLowerCase()) !== -1
|
||||
);
|
||||
};
|
||||
|
||||
type PageOverviewProps = {
|
||||
rings: ring[]
|
||||
search: string
|
||||
items: Item[]
|
||||
leaving: boolean
|
||||
onLeave: () => void
|
||||
}
|
||||
|
||||
export default function PageOverview({ rings, search: searchProp, items, leaving, onLeave }: PageOverviewProps) {
|
||||
const [ring, setRing] = useState<ring | "all">("all")
|
||||
const [search, setSearch] = useState(searchProp)
|
||||
|
||||
useEffect(() => {
|
||||
if (rings.length > 0) {
|
||||
setRing(rings[0])
|
||||
}
|
||||
setSearch(searchProp)
|
||||
}, [rings, searchProp])
|
||||
|
||||
const handleRingClick = (ring: ring) => () => {
|
||||
setRing(ring)
|
||||
};
|
||||
|
||||
const isRingActive = (ringName: string) => ring === ringName
|
||||
|
||||
const itemMatchesRing = (item: Item) => ring === 'all' || item.ring === ring
|
||||
|
||||
const itemMatchesSearch = (item: Item) => {
|
||||
return (
|
||||
search.trim() === '' ||
|
||||
containsSearchTerm(item.title, search) ||
|
||||
containsSearchTerm(item.body, search) ||
|
||||
containsSearchTerm(item.info, search)
|
||||
);
|
||||
};
|
||||
|
||||
const isItemVisible = (item: Item) => itemMatchesRing(item) && itemMatchesSearch(item)
|
||||
|
||||
const getFilteredAndGroupedItems = () => {
|
||||
const groups = groupByFirstLetter(items);
|
||||
const groupsFiltered = groups.map(group => ({
|
||||
...group,
|
||||
items: group.items.filter(isItemVisible),
|
||||
}));
|
||||
const nonEmptyGroups = groupsFiltered.filter(
|
||||
group => group.items.length > 0,
|
||||
);
|
||||
return nonEmptyGroups;
|
||||
}
|
||||
|
||||
const handleSearchTermChange = setSearch
|
||||
|
||||
const groups = getFilteredAndGroupedItems();
|
||||
|
||||
return (
|
||||
<Fadeable leaving={leaving} onLeave={onLeave}>
|
||||
<SetTitle title="Technologies Overview" />
|
||||
<HeadlineGroup>
|
||||
<HeroHeadline>Technologies Overview</HeroHeadline>
|
||||
</HeadlineGroup>
|
||||
<div className="filter">
|
||||
<div className="split split--filter">
|
||||
<div className="split__left">
|
||||
<Search
|
||||
onChange={handleSearchTermChange}
|
||||
value={search}
|
||||
/>
|
||||
</div>
|
||||
<div className="split__right">
|
||||
<div className="nav">
|
||||
{rings.map(ringName => (
|
||||
<div className="nav__item" key={ringName}>
|
||||
<Badge
|
||||
big
|
||||
onClick={handleRingClick(ringName)}
|
||||
type={isRingActive(ringName) ? ringName : 'empty'}
|
||||
>
|
||||
{ringName}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="letter-index">
|
||||
{groups.map(({ letter, items }) => (
|
||||
<div key={letter} className="letter-index__group">
|
||||
<div className="letter-index__letter">{letter}</div>
|
||||
<div className="letter-index__items">
|
||||
<div className="item-list">
|
||||
<div className="item-list__list">
|
||||
{items.map(item => (
|
||||
<Link
|
||||
key={item.name}
|
||||
className="item item--big item--no-leading-border item--no-trailing-border"
|
||||
pageName={`${item.quadrant}/${item.name}`}
|
||||
>
|
||||
<div className="split split--overview">
|
||||
<div className="split__left">
|
||||
<div className="item__title">
|
||||
{item.title}
|
||||
<Flag item={item} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="split__right">
|
||||
<div className="nav nav--relations">
|
||||
<div className="nav__item">
|
||||
{translate(item.quadrant)}
|
||||
</div>
|
||||
<div className="nav__item">
|
||||
<Badge type={item.ring}>{item.ring}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Fadeable>
|
||||
);
|
||||
}
|
||||
129
src/components/PageOverview/PageOverview.tsx
Normal file
129
src/components/PageOverview/PageOverview.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import HeadlineGroup from '../HeadlineGroup/HeadlineGroup';
|
||||
import HeroHeadline from '../HeroHeadline/HeroHeadline';
|
||||
import Badge from '../Badge/Badge';
|
||||
import Link from '../Link/Link';
|
||||
import Search from '../Search/Search';
|
||||
import Fadeable from '../Fadeable/Fadeable';
|
||||
import SetTitle from '../SetTitle';
|
||||
import Flag from '../Flag/Flag';
|
||||
import { groupByFirstLetter, Item } from '../../model';
|
||||
import { translate, ring } from '../../config';
|
||||
|
||||
const containsSearchTerm = (text = '', term = '') => {
|
||||
// TODO search refinement
|
||||
return text.trim().toLocaleLowerCase().indexOf(term.trim().toLocaleLowerCase()) !== -1;
|
||||
};
|
||||
|
||||
type PageOverviewProps = {
|
||||
rings: ring[];
|
||||
search: string;
|
||||
items: Item[];
|
||||
leaving: boolean;
|
||||
onLeave: () => void;
|
||||
};
|
||||
|
||||
export default function PageOverview({ rings, search: searchProp, items, leaving, onLeave }: PageOverviewProps) {
|
||||
const [ring, setRing] = useState<ring | 'all'>('all');
|
||||
const [search, setSearch] = useState(searchProp);
|
||||
|
||||
useEffect(() => {
|
||||
if (rings.length > 0) {
|
||||
setRing(rings[0]);
|
||||
}
|
||||
setSearch(searchProp);
|
||||
}, [rings, searchProp]);
|
||||
|
||||
const handleRingClick = (ring: ring) => () => {
|
||||
setRing(ring);
|
||||
};
|
||||
|
||||
const isRingActive = (ringName: string) => ring === ringName;
|
||||
|
||||
const itemMatchesRing = (item: Item) => ring === 'all' || item.ring === ring;
|
||||
|
||||
const itemMatchesSearch = (item: Item) => {
|
||||
return search.trim() === '' || containsSearchTerm(item.title, search) || containsSearchTerm(item.body, search) || containsSearchTerm(item.info, search);
|
||||
};
|
||||
|
||||
const isItemVisible = (item: Item) => itemMatchesRing(item) && itemMatchesSearch(item);
|
||||
|
||||
const getFilteredAndGroupedItems = () => {
|
||||
const groups = groupByFirstLetter(items);
|
||||
const groupsFiltered = groups.map((group) => ({
|
||||
...group,
|
||||
items: group.items.filter(isItemVisible),
|
||||
}));
|
||||
const nonEmptyGroups = groupsFiltered.filter((group) => group.items.length > 0);
|
||||
return nonEmptyGroups;
|
||||
};
|
||||
|
||||
const handleSearchTermChange = setSearch;
|
||||
|
||||
const groups = getFilteredAndGroupedItems();
|
||||
|
||||
return (
|
||||
<Fadeable leaving={leaving} onLeave={onLeave}>
|
||||
<SetTitle title='Technologies Overview' />
|
||||
<HeadlineGroup>
|
||||
<HeroHeadline>Technologies Overview</HeroHeadline>
|
||||
</HeadlineGroup>
|
||||
<div className='filter'>
|
||||
<div className='split split--filter'>
|
||||
<div className='split__left'>
|
||||
<Search onChange={handleSearchTermChange} value={search} />
|
||||
</div>
|
||||
<div className='split__right'>
|
||||
<div className='nav'>
|
||||
{rings.map((ringName) => (
|
||||
<div className='nav__item' key={ringName}>
|
||||
<Badge big onClick={handleRingClick(ringName)} type={isRingActive(ringName) ? ringName : 'empty'}>
|
||||
{ringName}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='letter-index'>
|
||||
{groups.map(({ letter, items }) => (
|
||||
<div key={letter} className='letter-index__group'>
|
||||
<div className='letter-index__letter'>{letter}</div>
|
||||
<div className='letter-index__items'>
|
||||
<div className='item-list'>
|
||||
<div className='item-list__list'>
|
||||
{items.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
className='item item--big item--no-leading-border item--no-trailing-border'
|
||||
pageName={`${item.quadrant}/${item.name}`}
|
||||
>
|
||||
<div className='split split--overview'>
|
||||
<div className='split__left'>
|
||||
<div className='item__title'>
|
||||
{item.title}
|
||||
<Flag item={item} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='split__right'>
|
||||
<div className='nav nav--relations'>
|
||||
<div className='nav__item'>{translate(item.quadrant)}</div>
|
||||
<div className='nav__item'>
|
||||
<Badge type={item.ring}>{item.ring}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Fadeable>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
import React from 'react';
|
||||
import HeroHeadline from './HeroHeadline';
|
||||
import HeadlineGroup from './HeadlineGroup';
|
||||
import QuadrantSection from './QuadrantSection';
|
||||
import Fadeable from './Fadeable';
|
||||
import SetTitle from './SetTitle';
|
||||
import HeroHeadline from '../HeroHeadline/HeroHeadline';
|
||||
import HeadlineGroup from '../HeadlineGroup/HeadlineGroup';
|
||||
import QuadrantSection from '../QuadrantSection/QuadrantSection';
|
||||
import Fadeable from '../Fadeable/Fadeable';
|
||||
import SetTitle from '../SetTitle';
|
||||
|
||||
import { translate } from '../config';
|
||||
import {featuredOnly, groupByQuadrants, Item} from '../model';
|
||||
import { translate } from '../../config';
|
||||
import { featuredOnly, groupByQuadrants, Item } from '../../model';
|
||||
|
||||
type PageQuadrantProps = {
|
||||
leaving: boolean
|
||||
onLeave: () => void
|
||||
pageName: string
|
||||
items: Item[]
|
||||
}
|
||||
leaving: boolean;
|
||||
onLeave: () => void;
|
||||
pageName: string;
|
||||
items: Item[];
|
||||
};
|
||||
|
||||
export default function PageQuadrant({ leaving, onLeave, pageName, items }: PageQuadrantProps) {
|
||||
const groups = groupByQuadrants(featuredOnly(items));
|
||||
@@ -1,34 +1,31 @@
|
||||
import React from 'react';
|
||||
import HeroHeadline from './HeroHeadline';
|
||||
import Fadeable from './Fadeable';
|
||||
import SetTitle from './SetTitle';
|
||||
import HeroHeadline from '../HeroHeadline/HeroHeadline';
|
||||
import Fadeable from '../Fadeable/Fadeable';
|
||||
import SetTitle from '../SetTitle';
|
||||
|
||||
export default function PageToolbox({ leaving, onLeave }: {leaving: boolean, onLeave: () => void}) {
|
||||
export default function PageToolbox({ leaving, onLeave }: { leaving: boolean; onLeave: () => void }) {
|
||||
return (
|
||||
<Fadeable leaving={leaving} onLeave={onLeave}>
|
||||
<SetTitle title="Small AOE Toolbox" />
|
||||
<SetTitle title='Small AOE Toolbox' />
|
||||
<HeroHeadline>Small AOE Toolbox</HeroHeadline>
|
||||
<div className="fullpage-content">
|
||||
<div className='fullpage-content'>
|
||||
<h3>Useful Tools</h3>
|
||||
|
||||
<ul>
|
||||
<li>Fiddler - free web debugging proxy ( http://www.telerik.com/fiddler )</li>
|
||||
<li>SoapUI - Webservice Test Tool (https://www.soapui.org/ )</li>
|
||||
<li>Postman - API Test Tool ( https://www.getpostman.com/ )</li>
|
||||
<li> Modelio - Simple free UML Modelling tool ( https://www.modelio.org/ )</li>
|
||||
<li> Modelio - Simple free UML Modelling tool ( https://www.modelio.org/ )</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h3>Useful Tools (commercial)</h3>
|
||||
|
||||
<ul>
|
||||
<li> Paw Rest Client</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h3>Estabilshed Technologies</h3>
|
||||
<p>
|
||||
Not mentionable but adopted for a while now:
|
||||
</p>
|
||||
<p>Not mentionable but adopted for a while now:</p>
|
||||
<ul>
|
||||
<li>Jenkins</li>
|
||||
<li>Redis</li>
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { groupByQuadrants, Item, Group } from '../model';
|
||||
import { quadrants } from '../config';
|
||||
import QuadrantSection from './QuadrantSection';
|
||||
|
||||
const renderQuadrant = (quadrantName: string, groups: Group) => {
|
||||
return (
|
||||
<div key={quadrantName} className="quadrant-grid__quadrant">
|
||||
<QuadrantSection quadrantName={quadrantName} groups={groups} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function QuadrantGrid({ items }: { items: Item[] }) {
|
||||
const groups = groupByQuadrants(items);
|
||||
return (
|
||||
<div className="quadrant-grid">
|
||||
{
|
||||
quadrants.map((quadrantName) => renderQuadrant(quadrantName, groups))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
17
src/components/QuadrantGrid/QuadrantGrid.tsx
Normal file
17
src/components/QuadrantGrid/QuadrantGrid.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { groupByQuadrants, Item, Group } from '../../model';
|
||||
import { quadrants } from '../../config';
|
||||
import QuadrantSection from '../QuadrantSection/QuadrantSection';
|
||||
import './quadrant-grid.scss';
|
||||
const renderQuadrant = (quadrantName: string, groups: Group) => {
|
||||
return (
|
||||
<div key={quadrantName} className='quadrant-grid__quadrant'>
|
||||
<QuadrantSection quadrantName={quadrantName} groups={groups} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function QuadrantGrid({ items }: { items: Item[] }) {
|
||||
const groups = groupByQuadrants(items);
|
||||
return <div className='quadrant-grid'>{quadrants.map((quadrantName) => renderQuadrant(quadrantName, groups))}</div>;
|
||||
}
|
||||
13
src/components/QuadrantGrid/quadrant-grid.scss
Normal file
13
src/components/QuadrantGrid/quadrant-grid.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
.quadrant-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
&__quadrant {
|
||||
flex: 0 0 45%;
|
||||
margin-bottom: 40px;
|
||||
@media (--until-lg) {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import React from 'react';
|
||||
import { translate, rings, ring } from '../config';
|
||||
import Badge from './Badge';
|
||||
import Link from './Link';
|
||||
import ItemList from './ItemList';
|
||||
import Flag from './Flag';
|
||||
import { Item, Group } from '../model';
|
||||
|
||||
const renderList = (ringName: ring, quadrantName: string, groups: Group, big: boolean) => {
|
||||
const itemsInRing = groups[quadrantName][ringName];
|
||||
|
||||
if (big) {
|
||||
return (
|
||||
<ItemList items={itemsInRing} noLeadingBorder>
|
||||
<Badge type={ringName} big={big}>
|
||||
{ringName}
|
||||
</Badge>
|
||||
</ItemList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ring-list">
|
||||
<div className="ring-list__header">
|
||||
<Badge type={ringName}>{ringName}</Badge>
|
||||
</div>
|
||||
{itemsInRing.map(item => (
|
||||
<span key={item.name} className="ring-list__item">
|
||||
<Link className="link" pageName={`${item.quadrant}/${item.name}`}>
|
||||
{item.title}
|
||||
<Flag item={item} short />
|
||||
</Link>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderRing = (ringName: ring, quadrantName: string, groups: Group, big: boolean) => {
|
||||
if (
|
||||
!groups[quadrantName] ||
|
||||
!groups[quadrantName][ringName] ||
|
||||
groups[quadrantName][ringName].length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div key={ringName} className="quadrant-section__ring">
|
||||
{renderList(ringName, quadrantName, groups, big)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function QuadrantSection({ quadrantName, groups, big = false }: { quadrantName: string, groups: Group, big?: boolean }) {
|
||||
return (
|
||||
<div className="quadrant-section">
|
||||
<div className="quadrant-section__header">
|
||||
<div className="split">
|
||||
<div className="split__left">
|
||||
<h4 className="headline">{translate(quadrantName)}</h4>
|
||||
</div>
|
||||
{!big && (
|
||||
<div className="split__right">
|
||||
<Link className="icon-link" pageName={`${quadrantName}`}>
|
||||
<span className="icon icon--pie icon-link__icon" />Zoom In
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="quadrant-section__rings">
|
||||
{rings.map(ringName => renderRing(ringName, quadrantName, groups, big))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
71
src/components/QuadrantSection/QuadrantSection.tsx
Normal file
71
src/components/QuadrantSection/QuadrantSection.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import { translate, rings, ring } from '../../config';
|
||||
import Badge from '../Badge/Badge';
|
||||
import Link from '../Link/Link';
|
||||
import ItemList from '../ItemList/ItemList';
|
||||
import Flag from '../Flag/Flag';
|
||||
import { Item, Group } from '../../model';
|
||||
import './quadrant-section.scss';
|
||||
const renderList = (ringName: ring, quadrantName: string, groups: Group, big: boolean) => {
|
||||
const itemsInRing = groups[quadrantName][ringName];
|
||||
|
||||
if (big) {
|
||||
return (
|
||||
<ItemList items={itemsInRing} noLeadingBorder>
|
||||
<Badge type={ringName} big={big}>
|
||||
{ringName}
|
||||
</Badge>
|
||||
</ItemList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='ring-list'>
|
||||
<div className='ring-list__header'>
|
||||
<Badge type={ringName}>{ringName}</Badge>
|
||||
</div>
|
||||
{itemsInRing.map((item) => (
|
||||
<span key={item.name} className='ring-list__item'>
|
||||
<Link className='link' pageName={`${item.quadrant}/${item.name}`}>
|
||||
{item.title}
|
||||
<Flag item={item} short />
|
||||
</Link>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderRing = (ringName: ring, quadrantName: string, groups: Group, big: boolean) => {
|
||||
if (!groups[quadrantName] || !groups[quadrantName][ringName] || groups[quadrantName][ringName].length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div key={ringName} className='quadrant-section__ring'>
|
||||
{renderList(ringName, quadrantName, groups, big)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function QuadrantSection({ quadrantName, groups, big = false }: { quadrantName: string; groups: Group; big?: boolean }) {
|
||||
return (
|
||||
<div className='quadrant-section'>
|
||||
<div className='quadrant-section__header'>
|
||||
<div className='split'>
|
||||
<div className='split__left'>
|
||||
<h4 className='headline'>{translate(quadrantName)}</h4>
|
||||
</div>
|
||||
{!big && (
|
||||
<div className='split__right'>
|
||||
<Link className='icon-link' pageName={`${quadrantName}`}>
|
||||
<span className='icon icon--pie icon-link__icon' />
|
||||
Zoom In
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='quadrant-section__rings'>{rings.map((ringName) => renderRing(ringName, quadrantName, groups, big))}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
src/components/QuadrantSection/quadrant-section.scss
Normal file
21
src/components/QuadrantSection/quadrant-section.scss
Normal file
@@ -0,0 +1,21 @@
|
||||
.quadrant-section {
|
||||
&__header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&__rings {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__ring {
|
||||
box-sizing: border-box;
|
||||
padding: 0 8px;
|
||||
flex: 0 0 25%;
|
||||
margin: 0 0 25px;
|
||||
@media (--until-md) {
|
||||
flex-basis: 50%;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PageIndex from './PageIndex';
|
||||
import PageOverview from './PageOverview';
|
||||
import PageHelp from './PageHelp';
|
||||
import PageQuadrant from './PageQuadrant';
|
||||
import PageItem from './PageItem';
|
||||
import PageItemMobile from './PageItemMobile';
|
||||
import PageIndex from './PageIndex/PageIndex';
|
||||
import PageOverview from './PageOverview/PageOverview';
|
||||
import PageHelp from './PageHelp/PageHelp';
|
||||
import PageQuadrant from './PageQuadrant/PageQuadrant';
|
||||
import PageItem from './PageItem/PageItem';
|
||||
import PageItemMobile from './PageItemMobile/PageItemMobile';
|
||||
import { quadrants, getItemPageNames, isMobileViewport } from '../config';
|
||||
import { Item } from '../model';
|
||||
|
||||
export default function Router({ pageName, items, releases }: {pageName: string, items: Item[], releases: string[]}) {
|
||||
export default function Router({ pageName, items, releases }: { pageName: string; items: Item[]; releases: string[] }) {
|
||||
enum page {
|
||||
index,
|
||||
overview,
|
||||
@@ -35,26 +35,26 @@ export default function Router({ pageName, items, releases }: {pageName: string,
|
||||
if (getItemPageNames(items).includes(pageName)) {
|
||||
return isMobileViewport() ? page.itemMobile : page.item;
|
||||
}
|
||||
|
||||
|
||||
return page.notFound;
|
||||
}
|
||||
};
|
||||
|
||||
const [statePageName, setStatePageName] = useState(pageName);
|
||||
const [leaving, setLeaving] = useState(false);
|
||||
const [nextPageName, setNextPageName] = useState<string>("");
|
||||
const [nextPageName, setNextPageName] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const leaving = getPageByName(items, pageName) !== getPageByName(items, statePageName);
|
||||
if (leaving) {
|
||||
setLeaving(true)
|
||||
setLeaving(true);
|
||||
}
|
||||
setNextPageName(pageName)
|
||||
}, [pageName, items, statePageName])
|
||||
setNextPageName(pageName);
|
||||
}, [pageName, items, statePageName]);
|
||||
|
||||
const handlePageLeave = () => {
|
||||
setLeaving(true)
|
||||
setLeaving(true);
|
||||
setStatePageName(nextPageName);
|
||||
setNextPageName("")
|
||||
setNextPageName('');
|
||||
|
||||
window.setTimeout(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
@@ -65,18 +65,18 @@ export default function Router({ pageName, items, releases }: {pageName: string,
|
||||
|
||||
switch (getPageByName(items, pageName)) {
|
||||
case page.index:
|
||||
return <PageIndex leaving={leaving} items={items} onLeave={handlePageLeave} releases={releases} />
|
||||
case page.overview:
|
||||
return <PageOverview items={items} rings={[]} search={""} leaving={leaving} onLeave={handlePageLeave}/>
|
||||
case page.help:
|
||||
return <PageHelp leaving={leaving} onLeave={handlePageLeave}/>
|
||||
case page.quadrant:
|
||||
return <PageQuadrant leaving={leaving} onLeave={handlePageLeave} items={items} pageName={pageName} />
|
||||
case page.itemMobile:
|
||||
return <PageItemMobile items={items} pageName={pageName} leaving={leaving} onLeave={handlePageLeave}/>
|
||||
case page.item:
|
||||
return <PageItem items={items} pageName={pageName} leaving={leaving} onLeave={handlePageLeave}/>
|
||||
default:
|
||||
return <div/>
|
||||
return <PageIndex leaving={leaving} items={items} onLeave={handlePageLeave} releases={releases} />;
|
||||
case page.overview:
|
||||
return <PageOverview items={items} rings={[]} search={''} leaving={leaving} onLeave={handlePageLeave} />;
|
||||
case page.help:
|
||||
return <PageHelp leaving={leaving} onLeave={handlePageLeave} />;
|
||||
case page.quadrant:
|
||||
return <PageQuadrant leaving={leaving} onLeave={handlePageLeave} items={items} pageName={pageName} />;
|
||||
case page.itemMobile:
|
||||
return <PageItemMobile items={items} pageName={pageName} leaving={leaving} onLeave={handlePageLeave} />;
|
||||
case page.item:
|
||||
return <PageItem items={items} pageName={pageName} leaving={leaving} onLeave={handlePageLeave} />;
|
||||
default:
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import React, { FormEvent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import './search.scss';
|
||||
type SearchProps = {
|
||||
onClose?: () => void
|
||||
onSubmit?: () => void
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
open?: boolean
|
||||
}
|
||||
onClose?: () => void;
|
||||
onSubmit?: () => void;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
open?: boolean;
|
||||
};
|
||||
|
||||
export default React.forwardRef((props: SearchProps, ref) => {
|
||||
return Search(props, ref)
|
||||
})
|
||||
return Search(props, ref);
|
||||
});
|
||||
|
||||
function Search({ value, onChange, onClose, open = false, onSubmit = () => { }}: SearchProps, ref: any) {
|
||||
function Search({ value, onChange, onClose, open = false, onSubmit = () => {} }: SearchProps, ref: any) {
|
||||
const closable = onClose !== undefined;
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
@@ -26,31 +26,31 @@ function Search({ value, onChange, onClose, open = false, onSubmit = () => { }}:
|
||||
if (onClose != null) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form className={classNames('search', { 'search--closable': closable })} onSubmit={handleSubmit}>
|
||||
<input
|
||||
value={value}
|
||||
type="text"
|
||||
onChange={(e) => { onChange(e.target.value); }}
|
||||
className="search__field"
|
||||
placeholder="What are you looking for?"
|
||||
type='text'
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
className='search__field'
|
||||
placeholder='What are you looking for?'
|
||||
ref={ref}
|
||||
/>
|
||||
<span className={classNames('search__button', { 'is-open': open })}>
|
||||
<button type="submit" className="button">
|
||||
<span className="icon icon--search button__icon" />
|
||||
Search
|
||||
</button>
|
||||
<button type='submit' className='button'>
|
||||
<span className='icon icon--search button__icon' />
|
||||
Search
|
||||
</button>
|
||||
</span>
|
||||
{
|
||||
closable && (
|
||||
<button className={classNames('search__close', { 'is-open': open })} onClick={handleClose}>
|
||||
<span className="icon icon--close" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
{closable && (
|
||||
<a className={classNames('search__close', { 'is-open': open })} onClick={handleClose}>
|
||||
<span className='icon icon--close' />
|
||||
</a>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
74
src/components/Search/search.scss
Normal file
74
src/components/Search/search.scss
Normal file
@@ -0,0 +1,74 @@
|
||||
.search {
|
||||
box-sizing: border-box;
|
||||
width: 600px;
|
||||
height: 50px;
|
||||
position: relative;
|
||||
left: 20px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&__field {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
//padding: 10px 120px 10px 20px;
|
||||
padding-left: 20px;
|
||||
background: #3A444A;
|
||||
display: block;
|
||||
border: none;
|
||||
color: var(--color-white);
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
font-family: 'DIN';
|
||||
font-weight: normal;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-gray-normal);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
background: #2F393F;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -18px;
|
||||
right: 7px;
|
||||
}
|
||||
|
||||
&--closable {
|
||||
.search {
|
||||
&__field {
|
||||
padding-right: 160px;
|
||||
}
|
||||
|
||||
&__button {
|
||||
right: 50px;
|
||||
transform: translateX(20px);
|
||||
transition: transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98) 100ms;
|
||||
|
||||
&.is-open {
|
||||
transform: translateX(0);
|
||||
transition-delay: 250ms;
|
||||
}
|
||||
}
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
top: 50%;
|
||||
margin-top: -21px;
|
||||
right: 4px;
|
||||
transform: scale(0.2);
|
||||
transition: transform 400ms cubic-bezier(0.24, 1.12, 0.71, 0.98);
|
||||
|
||||
&.is-open {
|
||||
transform: rotate(180deg) scale(1);
|
||||
transition-delay: 300ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/fonts/clanot-bold.otf
Normal file
BIN
src/fonts/clanot-bold.otf
Normal file
Binary file not shown.
BIN
src/fonts/clanot-medium.otf
Normal file
BIN
src/fonts/clanot-medium.otf
Normal file
Binary file not shown.
BIN
src/fonts/clanot-news.otf
Normal file
BIN
src/fonts/clanot-news.otf
Normal file
Binary file not shown.
BIN
src/fonts/clanot-thin.otf
Normal file
BIN
src/fonts/clanot-thin.otf
Normal file
Binary file not shown.
1
src/icons/back.svg
Normal file
1
src/icons/back.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 30 21" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path id="Combined-Shape" d="M4.012,11.427l24.704,0c0.626,0 1.134,-0.508 1.134,-1.134c0,-0.626 -0.508,-1.133 -1.134,-1.133l-24.987,0l7.224,-7.225c0.443,-0.442 0.443,-1.16 0,-1.603c-0.443,-0.443 -1.16,-0.443 -1.603,0l-9.018,9.018c-0.404,0.404 -0.439,1.037 -0.106,1.481c0.099,0.182 0.245,0.335 0.423,0.439l8.701,8.701c0.443,0.443 1.16,0.443 1.603,0c0.443,-0.443 0.443,-1.16 0,-1.603l-6.941,-6.941Z" style="fill:#fff;"/></svg>
|
||||
|
After Width: | Height: | Size: 833 B |
1
src/icons/close.svg
Normal file
1
src/icons/close.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M18.703,1.405l-7.675,7.732l-7.674,-7.732c-0.54,-0.54 -1.405,-0.54 -1.892,0c-0.54,0.541 -0.54,1.406 0,1.892l7.675,7.731l-7.732,7.675c-0.54,0.54 -0.54,1.405 0,1.892c0.541,0.54 1.406,0.54 1.892,0l7.731,-7.675l7.675,7.675c0.54,0.54 1.405,0.54 1.892,0c0.487,-0.541 0.54,-1.405 0,-1.892l-7.675,-7.675l7.675,-7.674c0.54,-0.54 0.54,-1.405 0,-1.892c-0.484,-0.54 -1.352,-0.54 -1.892,-0.057Z" style="fill:#7e8991;"/></svg>
|
||||
|
After Width: | Height: | Size: 830 B |
10
src/icons/overview.svg
Normal file
10
src/icons/overview.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="16px" height="21px" viewBox="0 0 16 21" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Slice 1</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M2.66666667,5.21153877e-05 C1.21540741,5.21153877e-05 0,1.21545952 0,2.66671878 L0,17.7778299 C0,18.9976817 1.00237037,20.0000521 2.22222222,20.0000521 L15.3333333,20.0000521 C15.682963,20.0000521 16,19.6830151 16,19.3333854 L16,4.66671878 C15.9976296,4.27560767 15.7140741,3.99649656 15.3333333,4.00005212 C14.6921481,4.00005212 14,3.30790397 14,2.66671878 C14,2.0255336 14.6921481,1.33338545 15.3333333,1.33338545 C15.6853333,1.33812619 16,1.01871878 16,0.666718782 C16,0.314718782 15.6853333,-0.00468862535 15.3333333,5.21153877e-05 L2.66666667,5.21153877e-05 Z M2.66666667,1.33338545 L13.0346667,1.33338545 C12.8020741,1.72805212 12.6666667,2.18405212 12.6666667,2.66671878 C12.6666667,3.14938545 12.8020741,3.60538545 13.0346667,4.00005212 L2.66666667,4.00005212 C2.02548148,4.00005212 1.33333333,3.30790397 1.33333333,2.66671878 C1.33333333,2.0255336 2.02548148,1.33338545 2.66666667,1.33338545 Z M1.33333333,4.96538545 C1.72681481,5.19797804 2.18518519,5.33338545 2.66666667,5.33338545 L14.6666667,5.33338545 L14.6666667,18.6667188 L2.22222222,18.6667188 C1.71881481,18.6667188 1.33333333,18.2812373 1.33333333,17.7778299 L1.33333333,4.96538545 Z M3.77777778,7.77782989 C3.40977778,7.77782989 3.11111111,8.07649656 3.11111111,8.44449656 C3.11111111,8.81249656 3.40977778,9.11116323 3.77777778,9.11116323 L12.2222222,9.11116323 C12.5902222,9.11116323 12.8888889,8.81249656 12.8888889,8.44449656 C12.8888889,8.07649656 12.5902222,7.77782989 12.2222222,7.77782989 L3.77777778,7.77782989 Z M3.77777778,11.3333854 C3.40977778,11.3333854 3.11111111,11.6320521 3.11111111,12.0000521 C3.11111111,12.3680521 3.40977778,12.6667188 3.77777778,12.6667188 L12.2222222,12.6667188 C12.5902222,12.6667188 12.8888889,12.3680521 12.8888889,12.0000521 C12.8888889,11.6320521 12.5902222,11.3333854 12.2222222,11.3333854 L3.77777778,11.3333854 Z M3.77777778,14.888941 C3.40977778,14.888941 3.11111111,15.1876077 3.11111111,15.5556077 C3.11111111,15.9236077 3.40977778,16.2222743 3.77777778,16.2222743 L12.2222222,16.2222743 C12.5902222,16.2222743 12.8888889,15.9236077 12.8888889,15.5556077 C12.8888889,15.1876077 12.5902222,14.888941 12.2222222,14.888941 L3.77777778,14.888941 Z" fill="#7E8991"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
10
src/icons/pie.svg
Normal file
10
src/icons/pie.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Slice 1</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M9.33152835,0 L8.68756287,0 L8.68756287,2.60709564 C3.84312353,2.94056136 0,6.97751758 0,11.9064716 C0,17.0522244 4.18577564,21.238 9.33152835,21.238 C14.2586451,21.238 18.2974386,17.3948765 18.6290671,12.5504371 L21.2361627,12.5504371 L21.2361627,11.9064716 C21.2361627,5.34142268 15.8965773,0 9.33152835,0 Z M9.33152835,19.9496097 C4.89450513,19.9496097 1.28655301,16.3416576 1.28655301,11.9046344 C1.28655301,7.68624707 4.55001574,4.21471275 8.68756287,3.88629954 L8.68756287,12.5485999 L17.3498632,12.5485999 C17.0196127,16.6843097 13.5517529,19.9496097 9.33152835,19.9496097 Z M18.6616787,11.2620468 L9.97595316,11.2620468 L9.97595316,1.30584441 C15.3187538,1.62736784 19.6087949,5.91878689 19.929859,11.2620468 L18.6616787,11.2620468 Z" id="Page-1-Copy-2" fill="#A1A6AA"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
13
src/icons/question.svg
Normal file
13
src/icons/question.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="23px" height="23px" viewBox="0 0 23 23" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group" transform="translate(1.000000, 1.000000)">
|
||||
<path d="M10.7625,0.525 C5.09744667,0.525 0.525,5.09744667 0.525,10.7625 C0.525,16.4275533 5.09744667,21 10.7625,21 C16.4275533,21 21,16.4275533 21,10.7625 C21,5.09744667 16.4275533,0.525 10.7625,0.525 Z" id="Path" stroke="#7E8991" stroke-width="1.5"></path>
|
||||
<path d="M10.7625,16.45 C10.08,16.45 9.53369667,15.9036967 9.53369667,15.2211967 C9.53369667,14.5386967 10.08,13.9923933 10.7625,13.9923933 L10.7625,13.9923933 C11.445,13.9923933 11.9913033,14.5386967 11.9913033,15.2211967 C11.9913033,15.9036967 11.445,16.45 10.7625,16.45 Z M13.5373933,9.53369667 C13.4011967,9.71630333 13.24134,9.89739333 13.0823933,10.0575533 C12.9686433,10.1713033 12.8324467,10.2850533 12.67259,10.4673567 C12.4899833,10.6263033 12.3537867,10.7637133 12.2400367,10.89991 C12.1262867,11.01366 12.0125367,11.12741 11.9439833,11.2187133 C11.73893,11.4911067 11.6476267,11.8560167 11.6712867,12.1972667 L11.6712867,12.9025167 L9.89648333,12.9025167 L9.89648333,11.94732 C9.87373333,11.6285167 9.91893,11.3100167 10.0551267,10.9924267 C10.2377333,10.69607 10.44127,10.4236767 10.71518,10.17373 L11.85268,9.03623 C12.1026267,8.76353333 12.2163767,8.42228333 12.2163767,8.05737333 C12.2163767,7.69367667 12.08018,7.35242667 11.8302333,7.10248 C11.5799833,6.85223 11.2162867,6.71603333 10.8513767,6.71603333 C10.48768,6.69358667 10.1239833,6.82978333 9.85007333,7.07973 C9.57768,7.32998 9.41752,7.69367667 9.39507333,8.05858667 L7.48377,8.05858667 C7.55262667,7.21714 7.93877,6.42119333 8.57637667,5.87489 C9.23643,5.32858667 10.0551267,5.05619333 10.9202333,5.07864 C11.73893,5.03344333 12.5576267,5.30614 13.1952333,5.82969333 C13.78643,6.32989 14.0824833,7.05849667 14.0824833,7.96849667 C14.06125,8.53239333 13.8786433,9.10114333 13.5373933,9.53369667 Z" id="Page-1" fill="#7E8991"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
10
src/icons/search.svg
Normal file
10
src/icons/search.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Page 1</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M19.6015482,17.6785496 L15.5875479,13.6657346 C18.0332228,10.2976722 17.7392915,5.55921406 14.7021982,2.52330599 C13.0203893,0.841497065 10.8167929,0 8.61290024,0 C6.40811867,0 4.204226,0.841497065 2.52271338,2.52330599 C-0.840904461,5.88662753 -0.840904461,11.3403582 2.52271338,14.703976 C4.204226,16.3857849 6.40930388,17.227282 8.61290024,17.227282 C10.3883405,17.227282 12.1628919,16.6809015 13.6651419,15.5893257 L17.677957,19.6021408 C17.9431471,19.8673309 18.2913017,20.0003704 18.6397526,20.0003704 C18.9882035,20.0003704 19.3366544,19.8673309 19.6015482,19.6021408 C20.1328173,19.0708717 20.1328173,18.2098187 19.6015482,17.6785496 Z M3.86881239,13.3566918 C1.25276394,10.7409396 1.25276394,6.48397193 3.86881239,3.86821978 C5.13609511,2.60064075 6.82116335,1.90285005 8.61290024,1.90285005 C10.4046371,1.90285005 12.0900017,2.60064075 13.3572844,3.86821978 C15.9730365,6.48397193 15.9730365,10.7409396 13.3572844,13.3566918 C12.0900017,14.6239745 10.4046371,15.3220615 8.61290024,15.3220615 C6.82027445,15.3220615 5.13609511,14.6239745 3.86881239,13.3566918 Z" fill="#7E8991"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -7,7 +7,23 @@ body {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'DIN';
|
||||
src: url('./fonts/clanot-news.otf');
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'DIN';
|
||||
src: url('./fonts/clanot-thin.otf');
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
|
||||
@import './styles/main.scss'
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './components/App';
|
||||
import './index.scss';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
,
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
42
src/styles/base.scss
Normal file
42
src/styles/base.scss
Normal file
@@ -0,0 +1,42 @@
|
||||
:root {
|
||||
--color-gray-dark: #475157;
|
||||
--color-gray-dark-alt: #4F585E;
|
||||
--color-gray-dark-alt2: #434D53;
|
||||
--color-gray-normal: #7f858a;
|
||||
--color-gray-light: #7D878D;
|
||||
|
||||
--color-white: #fff;
|
||||
--color-green: #5CB449;
|
||||
--color-orange: #FAA03D;
|
||||
--color-blue: #40A7D1;
|
||||
--color-marine: #688190;
|
||||
--color-red: #F1235A;
|
||||
--color-brand: #F59134;
|
||||
}
|
||||
|
||||
@custom-media --until-sm (max-width: 30em);
|
||||
@custom-media --until-md (max-width: 48em);
|
||||
@custom-media --until-lg (max-width: 61.875em);
|
||||
@custom-media --until-xl (max-width: 75em);
|
||||
|
||||
|
||||
|
||||
body {
|
||||
background: var(--color-gray-dark);
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'DIN';
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
|
||||
& h1 {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
17
src/styles/components/button.scss
Normal file
17
src/styles/components/button.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
.button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
position: relative;
|
||||
padding: 10px 10px 10px 35px;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
color: var(--color-gray-normal);
|
||||
|
||||
&__icon {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 50%;
|
||||
margin-top: -11px;
|
||||
}
|
||||
}
|
||||
15
src/styles/components/content.scss
Normal file
15
src/styles/components/content.scss
Normal file
@@ -0,0 +1,15 @@
|
||||
.fullpage-content {
|
||||
|
||||
font-size: 16px;
|
||||
color: var(--color-white);
|
||||
|
||||
& p,
|
||||
& ul {
|
||||
font-weight: lighter
|
||||
}
|
||||
|
||||
& a {
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
}
|
||||
8
src/styles/components/filter.scss
Normal file
8
src/styles/components/filter.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
.filter {
|
||||
margin-bottom: 40px;
|
||||
|
||||
/* @todo: re-enable filter for mobile */
|
||||
@media (--until-sm) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
4
src/styles/components/footnote.scss
Normal file
4
src/styles/components/footnote.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
.footnote {
|
||||
font-size: 12px;
|
||||
color: var(--color-gray-normal);
|
||||
}
|
||||
11
src/styles/components/headline.scss
Normal file
11
src/styles/components/headline.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
.headline {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--color-white);
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
|
||||
&--dark {
|
||||
color: var(--color-gray-light);
|
||||
}
|
||||
}
|
||||
2
src/styles/components/hljs.scss
Normal file
2
src/styles/components/hljs.scss
Normal file
@@ -0,0 +1,2 @@
|
||||
/* copied from here: http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.10.0/styles/darcula.min.css*/
|
||||
.hljs{display:block;overflow-x:auto;padding:0.5em;background:#2b2b2b}.hljs{color:#bababa}.hljs-strong,.hljs-emphasis{color:#a8a8a2}.hljs-bullet,.hljs-quote,.hljs-link,.hljs-number,.hljs-regexp,.hljs-literal{color:#6896ba}.hljs-code,.hljs-selector-class{color:#a6e22e}.hljs-emphasis{font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-section,.hljs-attribute,.hljs-name,.hljs-variable{color:#cb7832}.hljs-params{color:#b9b9b9}.hljs-string{color:#6a8759}.hljs-subst,.hljs-type,.hljs-built_in,.hljs-builtin-name,.hljs-symbol,.hljs-selector-id,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-template-tag,.hljs-template-variable,.hljs-addition{color:#e0c46c}.hljs-comment,.hljs-deletion,.hljs-meta{color:#7f7f7f}
|
||||
36
src/styles/components/icon-link.scss
Normal file
36
src/styles/components/icon-link.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
.icon-link {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
height: 25px;
|
||||
font-size: 14px;
|
||||
line-height: 25px;
|
||||
border: 0;
|
||||
background: none;
|
||||
color: var(--color-gray-normal);
|
||||
|
||||
&--primary {
|
||||
color: var(--color-brand);
|
||||
}
|
||||
|
||||
&--big {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-right: 6px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -8px;
|
||||
width: 100%;
|
||||
border-bottom: 2px solid var(--color-gray-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/styles/components/icon.scss
Normal file
33
src/styles/components/icon.scss
Normal file
@@ -0,0 +1,33 @@
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background-size: 22px 22px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 0;
|
||||
vertical-align: middle;
|
||||
|
||||
&--pie {
|
||||
background-image: url('../../icons/pie.svg');
|
||||
}
|
||||
|
||||
&--question {
|
||||
background-image: url('../../icons/question.svg');
|
||||
}
|
||||
|
||||
&--overview {
|
||||
background-image: url('../../icons/overview.svg');
|
||||
}
|
||||
|
||||
&--search {
|
||||
background-image: url('../../icons/search.svg');
|
||||
}
|
||||
|
||||
&--back {
|
||||
background-image: url('../../icons/back.svg');
|
||||
}
|
||||
|
||||
&--close {
|
||||
background-image: url('../../icons/close.svg');
|
||||
}
|
||||
}
|
||||
24
src/styles/components/letter-index.scss
Normal file
24
src/styles/components/letter-index.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
.letter-index {
|
||||
margin-bottom: 60px;
|
||||
&__group {
|
||||
border-top: 1px solid var(--color-gray-normal);
|
||||
position: relative;
|
||||
padding: 0 0 0 200px;
|
||||
min-height: 80px;
|
||||
@media (--until-md) {
|
||||
padding: 0 0 0 50px;
|
||||
}
|
||||
}
|
||||
|
||||
&__letter {
|
||||
font-size: 50px;
|
||||
line-height: 80px;
|
||||
color: var(--color-gray-normal);
|
||||
position: absolute;
|
||||
left: 50px;
|
||||
top: 0;
|
||||
@media (--until-md) {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/styles/components/markdown.scss
Normal file
53
src/styles/components/markdown.scss
Normal file
@@ -0,0 +1,53 @@
|
||||
.markdown {
|
||||
color: var(--color-gray-normal);
|
||||
font-size: 16px;
|
||||
|
||||
p {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-gray-normal);
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
color: var(--color-gray-dark);
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: var(--color-gray-normal);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 1em 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
margin: 10 0 10 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
padding: 10px;
|
||||
background: var(--color-gray-dark);
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
42
src/styles/components/nav.scss
Normal file
42
src/styles/components/nav.scss
Normal file
@@ -0,0 +1,42 @@
|
||||
.nav {
|
||||
white-space: nowrap;
|
||||
|
||||
&__item {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
& + .nav__item {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__search {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
visibility: hidden;
|
||||
overflow: hidden;
|
||||
width: 0;
|
||||
margin-top: -25px;
|
||||
opacity: 0.8;
|
||||
transition:
|
||||
width 400ms cubic-bezier(0.24, 1.12, 0.71, 0.98) 100ms,
|
||||
visibility 0s linear 500ms,
|
||||
opacity 200ms linear;
|
||||
|
||||
&.is-open {
|
||||
opacity: 1;
|
||||
width: 600px;
|
||||
visibility: visible;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
}
|
||||
|
||||
&--relations {
|
||||
@media (--until-md) {
|
||||
.nav__item {
|
||||
display: block;
|
||||
margin: 5px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/styles/components/page.scss
Normal file
35
src/styles/components/page.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
.page {
|
||||
max-width: 1200px;
|
||||
min-height: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
|
||||
&__header {
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
|
||||
@media (--until-sm) {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
@media (--until-lg) {
|
||||
.nav {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
margin-top: 5px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
8
src/styles/components/publish-date.scss
Normal file
8
src/styles/components/publish-date.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
.publish-date {
|
||||
color: var(--color-gray-normal);
|
||||
text-align: right;
|
||||
|
||||
@media (--until-sm) {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
10
src/styles/components/ring-list.scss
Normal file
10
src/styles/components/ring-list.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
.ring-list {
|
||||
&__header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
&__item {
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
29
src/styles/components/social-icon.scss
Normal file
29
src/styles/components/social-icon.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
.social-icon {
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 16px; /* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
background-color: var(--color-white);
|
||||
border-radius: 50%;
|
||||
padding: 7px;
|
||||
margin: 0 5px 0 5px;
|
||||
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* Support for IE. */
|
||||
font-feature-settings: 'liga';
|
||||
|
||||
}
|
||||
25
src/styles/components/split.scss
Normal file
25
src/styles/components/split.scss
Normal file
@@ -0,0 +1,25 @@
|
||||
.split {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&--align-top {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&--overview {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
&__left {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
&__right {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
@media (--until-sm) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
17
src/styles/main.scss
Normal file
17
src/styles/main.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
@import "./base";
|
||||
@import "./components/button";
|
||||
@import "./components/content";
|
||||
@import "./components/filter";
|
||||
@import "./components/footnote";
|
||||
@import "./components/headline";
|
||||
@import "./components/hljs";
|
||||
@import "./components/icon";
|
||||
@import "./components/icon-link";
|
||||
@import "./components/letter-index";
|
||||
@import "./components/markdown";
|
||||
@import "./components/nav";
|
||||
@import "./components/page";
|
||||
@import "./components/publish-date";
|
||||
@import "./components/ring-list";
|
||||
@import "./components/social-icon";
|
||||
@import "./components/split";
|
||||
Reference in New Issue
Block a user