diff --git a/.env b/.env
index 3d6d6f7..0bcc887 100644
--- a/.env
+++ b/.env
@@ -1,2 +1,2 @@
REACT_APP_RADAR_NAME=AOE Technology Radar
-PUBLIC_URL=/techradar
\ No newline at end of file
+PUBLIC_URL=/techradar
diff --git a/.lintstagedrc.json b/.lintstagedrc.json
index 0429977..ae6a6c4 100644
--- a/.lintstagedrc.json
+++ b/.lintstagedrc.json
@@ -1,3 +1,3 @@
{
- "*.{json, md, yml, js, ts, tsx}": ["prettier --write"]
+ "*.{json, md, yml, js, ts, tsx, scss}": ["prettier --write"]
}
diff --git a/README.md b/README.md
index fd35af9..12b1a58 100644
--- a/README.md
+++ b/README.md
@@ -135,8 +135,10 @@ You can integrate images in your markdown. Put the image files in your public fo
```
## Development
+
For local development you need a `rd.json` in the public folder. You can use `rd_example.json`.
Then simply start the dev server
+
```
yarn start
```
diff --git a/dist_scripts/src/model.js b/dist_scripts/src/model.js
index 989b55c..615bc4d 100644
--- a/dist_scripts/src/model.js
+++ b/dist_scripts/src/model.js
@@ -16,11 +16,15 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from) {
return to;
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.getFirstLetter = exports.groupByFirstLetter = exports.groupByQuadrants = exports.featuredOnly = void 0;
+exports.getFirstLetter = exports.groupByFirstLetter = exports.groupByQuadrants = exports.nonFeaturedOnly = exports.featuredOnly = void 0;
var featuredOnly = function (items) {
return items.filter(function (item) { return item.featured; });
};
exports.featuredOnly = featuredOnly;
+var nonFeaturedOnly = function (items) {
+ return items.filter(function (item) { return !item.featured; });
+};
+exports.nonFeaturedOnly = nonFeaturedOnly;
var groupByQuadrants = function (items) {
return items.reduce(function (quadrants, item) {
var _a;
diff --git a/src/animation.ts b/src/animation.ts
index 2e344e4..ede4f8d 100644
--- a/src/animation.ts
+++ b/src/animation.ts
@@ -1,5 +1,9 @@
import React from "react";
+export interface AnimationConfig {
+ [k: string]: Animation | Animation[];
+}
+
export type Animations = {
[k: string]: Animation[];
};
@@ -8,7 +12,7 @@ export type AnimationStates = {
[k: string]: React.CSSProperties[];
};
-type Animation = {
+export type Animation = {
stateA: React.CSSProperties;
stateB: React.CSSProperties;
delay: number;
@@ -75,7 +79,7 @@ const getMaxAnimationsDuration = (animations: Animations) =>
);
export const createAnimationRunner = (
- animationsIn: { [k: string]: Animation | Animation[] },
+ animationsIn: AnimationConfig,
subscriber: () => void = () => {}
): AnimationRunner => {
const animations = Object.entries(animationsIn).reduce(
diff --git a/src/components/Flag/flag.scss b/src/components/Flag/flag.scss
index 218aa7c..f030a13 100644
--- a/src/components/Flag/flag.scss
+++ b/src/components/Flag/flag.scss
@@ -7,6 +7,7 @@
vertical-align: top;
margin-top: -2px;
left: 5px;
+ color: var(--color-white);
&--new {
background: var(--color-red);
diff --git a/src/components/HeadlineGroup/HeadlineGroup.tsx b/src/components/HeadlineGroup/HeadlineGroup.tsx
index 6b376fb..6296f80 100644
--- a/src/components/HeadlineGroup/HeadlineGroup.tsx
+++ b/src/components/HeadlineGroup/HeadlineGroup.tsx
@@ -1,17 +1,19 @@
import React from "react";
import classNames from "classnames";
import "./headline-group.scss";
-export default function ({
- children,
- secondary = false,
-}: React.PropsWithChildren<{ secondary?: boolean }>) {
- return (
-
- {children}
-
- );
+
+interface Props {
+ secondary?: boolean;
}
+
+const HeadlineGroup: React.FC = ({ children, secondary = false }) => (
+
+ {children}
+
+);
+
+export default HeadlineGroup;
diff --git a/src/components/HeroHeadline/HeroHeadline.tsx b/src/components/HeroHeadline/HeroHeadline.tsx
index cbd0f85..1717539 100644
--- a/src/components/HeroHeadline/HeroHeadline.tsx
+++ b/src/components/HeroHeadline/HeroHeadline.tsx
@@ -1,13 +1,15 @@
import React from "react";
import "./hero-headline.scss";
-export default function ({
- children,
- alt,
-}: React.PropsWithChildren<{ alt?: string }>) {
- return (
-
- {children}
- {alt}
-
- );
+
+interface Props {
+ alt?: string;
}
+
+const HeroHeadline: React.FC = ({ children, alt }) => (
+
+ {children}
+ {alt}
+
+);
+
+export default HeroHeadline;
diff --git a/src/components/Item/Item.tsx b/src/components/Item/Item.tsx
index 961dbc7..225d66b 100644
--- a/src/components/Item/Item.tsx
+++ b/src/components/Item/Item.tsx
@@ -4,33 +4,40 @@ import Link from "../Link/Link";
import Flag from "../Flag/Flag";
import { Item as mItem } from "../../model";
import "./item.scss";
-type ItemProps = {
+
+type Props = {
item: mItem;
noLeadingBorder?: boolean;
active?: boolean;
style: React.CSSProperties;
+ greyedOut?: boolean;
};
-export default function Item({
+const Item: React.FC = ({
item,
noLeadingBorder = false,
active = false,
style = {},
-}: ItemProps) {
- return (
- (
+
+
-
- {item.title}
-
-
- {item.info &&
{item.info}
}
-
- );
-}
+ {item.title}
+
+
+ {item.info && {item.info}
}
+
+);
+
+export default Item;
diff --git a/src/components/Item/item.scss b/src/components/Item/item.scss
index ef59282..da2e32b 100644
--- a/src/components/Item/item.scss
+++ b/src/components/Item/item.scss
@@ -39,6 +39,10 @@
&__title {
font-size: 16px;
color: var(--color-white);
+
+ &.greyed-out {
+ color: var(--color-gray-light-alt);
+ }
}
&__info {
diff --git a/src/components/ItemList/ItemList.tsx b/src/components/ItemList/ItemList.tsx
index 8859717..1c3b653 100644
--- a/src/components/ItemList/ItemList.tsx
+++ b/src/components/ItemList/ItemList.tsx
@@ -1,7 +1,12 @@
import React from "react";
import Item from "../Item/Item";
-import { Item as mItem } from "../../model";
+import {
+ featuredOnly,
+ nonFeaturedOnly,
+ Item as mItem,
+} from "../../model";
import "./item-list.scss";
+
type ItemListProps = {
items: mItem[];
activeItem?: mItem;
@@ -10,34 +15,45 @@ type ItemListProps = {
itemStyle?: React.CSSProperties[];
};
-export default function ItemList({
+const ItemList: React.FC = ({
children,
items,
activeItem,
noLeadingBorder,
headerStyle = {},
itemStyle = [],
-}: React.PropsWithChildren) {
+}) => {
+ const featuredItems = featuredOnly(items);
+ const nonFeaturedItems = nonFeaturedOnly(items);
+
return (
-
-
- {children}
-
-
- {items.map((item, i) => (
-
- ))}
-
+
+
+ {children}
- );
-}
+
+ {featuredItems.map((item, i) => (
+
+ ))}
+ {nonFeaturedItems.map((item, i) => (
+
+ ))}
+
+
+);}
+
+export default ItemList;
diff --git a/src/components/PageItem/PageItem.tsx b/src/components/PageItem/PageItem.tsx
index 2d8e60b..c88721a 100644
--- a/src/components/PageItem/PageItem.tsx
+++ b/src/components/PageItem/PageItem.tsx
@@ -1,18 +1,17 @@
-import React, { useEffect, useState } from "react";
+import React from "react";
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 {
- AnimationStates,
- createAnimation,
- createAnimationRunner,
-} from "../../animation";
+import { useAnimations } from "./useAnimations";
import "./item-page.scss";
import { translate } from "../../config";
-import { groupByQuadrants, Item } from "../../model";
+import {
+ groupByQuadrants,
+ Item,
+} from "../../model";
const getItem = (pageName: string, items: Item[]) => {
const [quadrantName, itemName] = pageName.split("/");
@@ -26,175 +25,26 @@ const getItemsInRing = (pageName: string, items: Item[]) => {
return groupByQuadrants(items)[item.quadrant][item.ring];
};
-type PageItemProps = {
+type Props = {
pageName: string;
items: Item[];
leaving: boolean;
onLeave: () => void;
};
-export default function PageItem({
- pageName,
- items,
- leaving,
- onLeave,
-}: PageItemProps) {
+const PageItem: React.FC
= ({ pageName, items, leaving, onLeave }) => {
const itemsInRing = getItemsInRing(pageName, items);
- const animationsIn = {
- background: createAnimation(
- {
- transform: "translateX(calc((100vw - 1200px) / 2 + 800px))",
- transition: "transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)",
- },
- {
- transition: "transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)",
- transform: "translateX(0)",
- },
- 0
- ),
- navHeader: createAnimation(
- {
- transform: "translateX(-40px)",
- opacity: "0",
- },
- {
- transition: "opacity 150ms ease-out, transform 300ms ease-out",
- transform: "translateX(0px)",
- opacity: "1",
- },
- 300
- ),
- text: createAnimation(
- {
- transform: "translateY(-20px)",
- opacity: "0",
- },
- {
- transition: "opacity 150ms ease-out, transform 300ms ease-out",
- transform: "translateY(0px)",
- opacity: "1",
- },
- 600
- ),
- items: itemsInRing.map((item, i) =>
- createAnimation(
- {
- transform: "translateX(-40px)",
- opacity: "0",
- },
- {
- transition: "opacity 150ms ease-out, transform 300ms ease-out",
- transform: "translateX(0px)",
- opacity: "1",
- },
- 400 + 100 * i
- )
- ),
- footer: createAnimation(
- {
- transition: "opacity 150ms ease-out, transform 300ms ease-out",
- transform: "translateX(-40px)",
- opacity: "0",
- },
- {
- transition: "opacity 150ms ease-out, transform 300ms ease-out",
- transform: "translateX(0px)",
- opacity: "1",
- },
- 600 + itemsInRing.length * 100
- ),
- };
-
- const animationsOut = {
- background: createAnimation(
- animationsIn.background.stateB,
- animationsIn.background.stateA,
- 300 + itemsInRing.length * 50
- ),
- navHeader: createAnimation(
- animationsIn.navHeader.stateB,
- {
- transition: "opacity 150ms ease-out, transform 300ms ease-out",
- transform: "translateX(40px)",
- opacity: "0",
- },
- 0
- ),
- text: createAnimation(
- animationsIn.text.stateB,
- {
- transform: "translateY(20px)",
- transition: "opacity 150ms ease-out, transform 300ms ease-out",
- opacity: "0",
- },
- 0
- ),
- items: itemsInRing.map((item, i) =>
- createAnimation(
- animationsIn.items[i].stateB,
- {
- transition: "opacity 150ms ease-out, transform 300ms ease-out",
- transform: "translateX(40px)",
- opacity: "0",
- },
- 100 + 50 * i
- )
- ),
- footer: createAnimation(
- animationsIn.text.stateB,
- {
- transition: "opacity 150ms ease-out, transform 300ms ease-out",
- transform: "translateX(40px)",
- opacity: "0",
- },
- 200 + itemsInRing.length * 50
- ),
- };
-
- const [animations, setAnimations] = useState(() => {
- return leaving ? createAnimationRunner(animationsIn).getState() : {};
+ const { getAnimationState, getAnimationStates } = useAnimations({
+ itemsInRing,
+ onLeave,
+ leaving,
});
- const [stateLeaving, setStateLeaving] = useState(leaving);
-
- useEffect(() => {
- if (!stateLeaving && leaving) {
- let animationRunner = createAnimationRunner(animationsOut, () =>
- setAnimations(animationRunner.getState)
- );
- animationRunner.run();
- animationRunner.awaitAnimationComplete(onLeave);
- setStateLeaving(true);
- }
- if (stateLeaving && !leaving) {
- let animationRunner = createAnimationRunner(animationsIn, () =>
- setAnimations(animationRunner.getState)
- );
- animationRunner.run();
- setStateLeaving(false);
- }
- }, [stateLeaving, leaving, animationsIn, animationsOut, onLeave]);
-
- const getAnimationStates = (name: string) => {
- if (!animations) {
- return undefined;
- }
- return animations[name];
- };
-
- const getAnimationState = (name: string) => {
- const animations = getAnimationStates(name);
- if (animations === undefined || animations.length === 0) {
- return undefined;
- }
- return animations[0];
- };
-
const item = getItem(pageName, items);
return (
-
+ <>
@@ -205,7 +55,6 @@ export default function PageItem({
>
{translate(item.quadrant)}
-
+
-
+ >
);
-}
+};
+
+export default PageItem;
diff --git a/src/components/PageItem/useAnimations.tsx b/src/components/PageItem/useAnimations.tsx
new file mode 100644
index 0000000..9d6f27e
--- /dev/null
+++ b/src/components/PageItem/useAnimations.tsx
@@ -0,0 +1,186 @@
+import { useEffect, useState, useMemo } from "react";
+import {
+ Animation,
+ AnimationStates,
+ createAnimation,
+ createAnimationRunner,
+} from "../../animation";
+import { Item } from "../../model";
+
+interface Props {
+ itemsInRing: Item[];
+ leaving: boolean;
+ onLeave: () => void;
+}
+
+export const useAnimations = ({
+ itemsInRing,
+ leaving,
+ onLeave,
+}: Props) => {
+ type AnimationConfig = {
+ background: Animation;
+ navHeader: Animation;
+ text: Animation;
+ items: Animation[];
+ footer: Animation;
+ }
+
+ type AnimationNames = keyof AnimationConfig;
+
+ const animationsIn: AnimationConfig = useMemo(
+ () => ({
+ background: createAnimation(
+ {
+ transform: "translateX(calc((100vw - 1200px) / 2 + 800px))",
+ transition: "transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)",
+ },
+ {
+ transition: "transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)",
+ transform: "translateX(0)",
+ },
+ 0
+ ),
+ navHeader: createAnimation(
+ {
+ transform: "translateX(-40px)",
+ opacity: "0",
+ },
+ {
+ transition: "opacity 150ms ease-out, transform 300ms ease-out",
+ transform: "translateX(0px)",
+ opacity: "1",
+ },
+ 300
+ ),
+ text: createAnimation(
+ {
+ transform: "translateY(-20px)",
+ opacity: "0",
+ },
+ {
+ transition: "opacity 150ms ease-out, transform 300ms ease-out",
+ transform: "translateY(0px)",
+ opacity: "1",
+ },
+ 600
+ ),
+ items: itemsInRing.map((item, i) =>
+ createAnimation(
+ {
+ transform: "translateX(-40px)",
+ opacity: "0",
+ },
+ {
+ transition: "opacity 150ms ease-out, transform 300ms ease-out",
+ transform: "translateX(0px)",
+ opacity: "1",
+ },
+ 400 + 100 * i
+ )
+ ),
+ footer: createAnimation(
+ {
+ transition: "opacity 150ms ease-out, transform 300ms ease-out",
+ transform: "translateX(-40px)",
+ opacity: "0",
+ },
+ {
+ transition: "opacity 150ms ease-out, transform 300ms ease-out",
+ transform: "translateX(0px)",
+ opacity: "1",
+ },
+ 600 + itemsInRing.length * 100
+ ),
+ }),
+ [itemsInRing]
+ );
+
+ const animationsOut: AnimationConfig = useMemo(
+ () => ({
+ background: createAnimation(
+ animationsIn.background.stateB,
+ animationsIn.background.stateA,
+ 300 + itemsInRing.length * 50
+ ),
+ navHeader: createAnimation(
+ animationsIn.navHeader.stateB,
+ {
+ transition: "opacity 150ms ease-out, transform 300ms ease-out",
+ transform: "translateX(40px)",
+ opacity: "0",
+ },
+ 0
+ ),
+ text: createAnimation(
+ animationsIn.text.stateB,
+ {
+ transform: "translateY(20px)",
+ transition: "opacity 150ms ease-out, transform 300ms ease-out",
+ opacity: "0",
+ },
+ 0
+ ),
+ items: itemsInRing.map((item, i) =>
+ createAnimation(
+ animationsIn.items[i].stateB,
+ {
+ transition: "opacity 150ms ease-out, transform 300ms ease-out",
+ transform: "translateX(40px)",
+ opacity: "0",
+ },
+ 100 + 50 * i
+ )
+ ),
+ footer: createAnimation(
+ animationsIn.text.stateB,
+ {
+ transition: "opacity 150ms ease-out, transform 300ms ease-out",
+ transform: "translateX(40px)",
+ opacity: "0",
+ },
+ 200 + itemsInRing.length * 50
+ ),
+ }),
+ [itemsInRing, animationsIn]
+ );
+
+ const [animations, setAnimations] = useState(() => {
+ return leaving ? createAnimationRunner(animationsIn).getState() : {};
+ });
+
+ const [stateLeaving, setStateLeaving] = useState(leaving);
+
+ useEffect(() => {
+ if (!stateLeaving && leaving) {
+ let animationRunner = createAnimationRunner(animationsOut, () =>
+ setAnimations(animationRunner.getState)
+ );
+ animationRunner.run();
+ animationRunner.awaitAnimationComplete(onLeave);
+ setStateLeaving(true);
+ }
+ if (stateLeaving && !leaving) {
+ let animationRunner = createAnimationRunner(animationsIn, () =>
+ setAnimations(animationRunner.getState)
+ );
+ animationRunner.run();
+ setStateLeaving(false);
+ }
+ }, [stateLeaving, leaving, animationsIn, animationsOut, onLeave]);
+
+ const getAnimationStates = (name: AnimationNames) => animations[name];
+
+ const getAnimationState = (name: AnimationNames) => {
+ const animations = getAnimationStates(name);
+ if (animations === undefined || animations.length === 0) {
+ return undefined;
+ }
+ return animations[0];
+ };
+
+ return {
+ getAnimationStates,
+ getAnimationState,
+ };
+};
diff --git a/src/index.scss b/src/index.scss
index 68e999b..b38f19b 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -16,6 +16,7 @@
--color-gray-dark-alt2: #434d53;
--color-gray-normal: #7f858a;
--color-gray-light: #7d878d;
+ --color-gray-light-alt: #adadad;
--color-white: #fff;
--color-green: #5cb449;
diff --git a/src/logo.svg b/src/logo.svg
deleted file mode 100644
index 9dfc1c0..0000000
--- a/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/model.js b/src/model.js
deleted file mode 100644
index a01a503..0000000
--- a/src/model.js
+++ /dev/null
@@ -1,85 +0,0 @@
-var __assign =
- (this && this.__assign) ||
- function () {
- __assign =
- Object.assign ||
- function (t) {
- for (var s, i = 1, n = arguments.length; i < n; i++) {
- s = arguments[i];
- for (var p in s)
- if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
- }
- return t;
- };
- return __assign.apply(this, arguments);
- };
-var __spreadArray =
- (this && this.__spreadArray) ||
- function (to, from) {
- for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
- to[j] = from[i];
- return to;
- };
-export var featuredOnly = function (items) {
- return items.filter(function (item) {
- return item.featured;
- });
-};
-export var groupByQuadrants = function (items) {
- return items.reduce(function (quadrants, item) {
- var _a;
- return __assign(
- __assign({}, quadrants),
- ((_a = {}),
- (_a[item.quadrant] = addItemToQuadrant(quadrants[item.quadrant], item)),
- _a)
- );
- }, {});
-};
-export var groupByFirstLetter = function (items) {
- var index = items.reduce(function (letterIndex, item) {
- var _a;
- return __assign(
- __assign({}, letterIndex),
- ((_a = {}),
- (_a[getFirstLetter(item)] = addItemToList(
- letterIndex[getFirstLetter(item)],
- item
- )),
- _a)
- );
- }, {});
- return Object.keys(index)
- .sort()
- .map(function (letter) {
- return {
- letter: letter,
- items: index[letter],
- };
- });
-};
-var addItemToQuadrant = function (quadrant, item) {
- var _a;
- if (quadrant === void 0) {
- quadrant = {};
- }
- return __assign(
- __assign({}, quadrant),
- ((_a = {}), (_a[item.ring] = addItemToRing(quadrant[item.ring], item)), _a)
- );
-};
-var addItemToList = function (list, item) {
- if (list === void 0) {
- list = [];
- }
- return __spreadArray(__spreadArray([], list), [item]);
-};
-var addItemToRing = function (ring, item) {
- if (ring === void 0) {
- ring = [];
- }
- return __spreadArray(__spreadArray([], ring), [item]);
-};
-export var getFirstLetter = function (item) {
- return item.title.substr(0, 1).toUpperCase();
-};
diff --git a/src/model.ts b/src/model.ts
index 4d9796a..c8daac6 100644
--- a/src/model.ts
+++ b/src/model.ts
@@ -39,6 +39,8 @@ export type Group = {
export const featuredOnly = (items: Item[]) =>
items.filter((item) => item.featured);
+export const nonFeaturedOnly = (items: Item[]) =>
+ items.filter((item) => !item.featured);
export const groupByQuadrants = (items: Item[]): Group =>
items.reduce(
diff --git a/src/styles/components/headline.scss b/src/styles/components/headline.scss
index 0803a8f..5452aa0 100644
--- a/src/styles/components/headline.scss
+++ b/src/styles/components/headline.scss
@@ -1,3 +1,7 @@
+h4.headline {
+ font-size: 18px;
+}
+
.headline {
margin: 0;
padding: 0;