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;