Browse Source

Merge branch 'init_atlas' into atlas-video-preview

Klaudiusz Dembler 4 years ago
parent
commit
41e16ba310

+ 7 - 9
packages/app/src/App.tsx

@@ -8,14 +8,12 @@ import HomeView from "app/src/views/HomeView";
 
 export default function App() {
 	return (
-		<main>
-			<Provider store={store}>
-				<Layout>
-					<Router primary={false}>
-						<HomeView default />
-					</Router>
-				</Layout>
-			</Provider>
-		</main>
+		<Provider store={store}>
+			<Layout>
+				<Router primary={false}>
+					<HomeView default />
+				</Router>
+			</Layout>
+		</Provider>
 	);
 }

+ 7 - 9
packages/app/src/components/Layout.tsx

@@ -1,13 +1,11 @@
 import React from "react";
 import { GlobalStyle } from "@joystream/components";
 
-type LayoutProps = { children: React.ReactNode };
+const Layout: React.FC = ({ children }) => (
+	<main>
+		<GlobalStyle />
+		{children}
+	</main>
+);
 
-export default function Layout({ children }: LayoutProps) {
-	return (
-		<>
-			<GlobalStyle />
-			{children}
-		</>
-	);
-}
+export default Layout;

+ 1 - 0
packages/components/assets/binocular.svg

@@ -0,0 +1 @@
+<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 19V5H5L2 15v4M10 22H1M10 2H7M17 2h-3M14 19V5h5l3 10v4M14 22h9M7 10h9" stroke="#fff" stroke-width="2" stroke-miterlimit="10" stroke-linecap="square"/></svg>

+ 1 - 0
packages/components/assets/books.svg

@@ -0,0 +1 @@
+<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 15h6M7 15h6M7 3H1v18h6V3zM13 3H7v18h6V3zM15.629 15.146l5.855-1.315" stroke="#fff" stroke-width="2" stroke-miterlimit="10" stroke-linecap="square"/><path d="M18.854 2.123L13 3.438 16.942 21l5.854-1.314-3.942-17.563z" stroke="#fff" stroke-width="2" stroke-miterlimit="10" stroke-linecap="square"/></svg>

+ 1 - 0
packages/components/assets/browse.svg

@@ -0,0 +1 @@
+<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 2H5M20 6H4M22 10H2l3 12h14l3-12z" stroke="#fff" stroke-width="2" stroke-miterlimit="10" stroke-linecap="square"/></svg>

+ 1 - 0
packages/components/assets/home.svg

@@ -0,0 +1 @@
+<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 11l11-9 11 9M10 23v-6h4v6" stroke="#fff" stroke-width="2" stroke-miterlimit="10"/><path d="M4 13v10h16V13" stroke="#fff" stroke-width="2" stroke-miterlimit="10" stroke-linecap="square"/></svg>

+ 6 - 1
packages/components/src/components/GlobalStyle/GlobalStyle.tsx

@@ -7,12 +7,17 @@ const globalStyles = css`
 	${emotionNormalize};
 
 	body {
-		box-sizing: border-box;
 		font-family: ${typography.fonts.base};
 		background: ${colors.black};
 		color: ${colors.gray[500]};
 	}
 
+	*,
+	*::after,
+	*::before {
+		box-sizing: border-box;
+	}
+
 	h1,
 	h2,
 	h3,

+ 65 - 0
packages/components/src/components/HamburgerButton/HamburgerButton.style.ts

@@ -0,0 +1,65 @@
+// based on https://github.com/jonsuh/hamburgers licensed under MIT
+
+import { makeStyles, StyleFn } from "../../utils";
+import { colors } from "../../theme";
+
+type HamburgerButtonStyleProps = {
+	active: boolean;
+};
+
+const hamburgerBox: StyleFn = () => ({
+	width: "18px",
+	height: "12px",
+	display: "inline-block",
+	position: "relative",
+});
+
+const hamburgerInner: StyleFn<HamburgerButtonStyleProps> = (_, { active }) => ({
+	display: "block",
+	top: "50%",
+	marginTop: "-1px",
+
+	transitionDuration: "0.075s",
+	transitionDelay: active ? "0.12s" : "0",
+	transitionTimingFunction: active ? "cubic-bezier(0.215, 0.61, 0.355, 1)" : "cubic-bezier(0.55, 0.055, 0.675, 0.19)",
+	transform: active ? "rotate(45deg)" : "none",
+	"&, &::before, &::after": {
+		width: "18px",
+		height: "2px",
+		backgroundColor: colors.white,
+		position: "absolute",
+	},
+	"&::before, &::after": {
+		content: '""',
+		display: "block",
+	},
+	"&::before": {
+		top: active ? 0 : "-5px",
+		opacity: active ? 0 : 1,
+		transition: active
+			? "top 0.075s ease, opacity 0.075s 0.12s ease"
+			: "top 0.075s 0.12s ease, opacity 0.075s ease",
+	},
+	"&::after": {
+		bottom: active ? 0 : "-5px",
+		transform: active ? "rotate(-90deg)" : "none",
+		transition: active
+			? "bottom 0.075s ease, transform 0.075s 0.12s cubic-bezier(0.215, 0.61, 0.355, 1)"
+			: "bottom 0.075s 0.12s ease, transform 0.075s cubic-bezier(0.55, 0.055, 0.675, 0.19)",
+	},
+});
+
+const hamburger: StyleFn<HamburgerButtonStyleProps> = (_, { active }) => ({
+	padding: "3px",
+	display: "inline-block",
+	cursor: "pointer",
+	"&:hover": {
+		opacity: 0.7,
+	},
+});
+
+export const useCSS = (props: HamburgerButtonStyleProps) => ({
+	hamburgerBox: makeStyles([hamburgerBox])(props),
+	hamburgerInner: makeStyles([hamburgerInner])(props),
+	hamburger: makeStyles([hamburger])(props),
+});

+ 23 - 0
packages/components/src/components/HamburgerButton/HamburgerButton.tsx

@@ -0,0 +1,23 @@
+import React from "react";
+import { SerializedStyles } from "@emotion/core";
+import { useCSS } from "./HamburgerButton.style";
+
+type HamburgerButtonProps = {
+	active: boolean;
+	onClick: (e: React.MouseEvent<HTMLElement>) => void;
+	outerStyles?: SerializedStyles;
+};
+
+const HamburgerButton: React.FC<HamburgerButtonProps> = ({ active, onClick, outerStyles }) => {
+	const styles = useCSS({ active });
+
+	return (
+		<div css={[styles.hamburger, outerStyles]} onClick={onClick}>
+			<span css={styles.hamburgerBox}>
+				<span css={styles.hamburgerInner} />
+			</span>
+		</div>
+	);
+};
+
+export default HamburgerButton;

+ 3 - 0
packages/components/src/components/HamburgerButton/index.ts

@@ -0,0 +1,3 @@
+import HamburgerButton from "./HamburgerButton";
+
+export default HamburgerButton;

+ 101 - 0
packages/components/src/components/Sidenav/Sidenav.style.ts

@@ -0,0 +1,101 @@
+import { theme, utils } from "../..";
+
+export const SIDENAV_WIDTH = 56;
+export const EXPANDED_SIDENAV_WIDTH = 360;
+
+type SidenavStyleProps = {
+	expanded: boolean;
+};
+
+const nav: utils.StyleFn = () => ({
+	position: "fixed",
+	top: 0,
+	left: 0,
+	bottom: 0,
+	zIndex: 100,
+
+	overflow: "hidden",
+
+	padding: `${theme.spacing.xxl} ${theme.spacing.m}`,
+
+	display: "flex",
+	flexDirection: "column",
+	alignItems: "flex-start",
+
+	backgroundColor: theme.colors.blue[700],
+	color: theme.colors.white,
+});
+
+const expandButton: utils.StyleFn = () => ({
+	padding: "7px",
+	margin: "-4px",
+});
+
+const drawerOverlay: utils.StyleFn<SidenavStyleProps> = (_, { expanded }) => ({
+	position: "fixed",
+	top: 0,
+	right: 0,
+	bottom: 0,
+	left: 0,
+	zIndex: 99,
+
+	display: expanded ? "block" : "none",
+
+	backgroundColor: "rgba(0, 0, 0, 0.5)",
+});
+
+const navItemsWrapper: utils.StyleFn = () => ({
+	marginTop: "90px",
+});
+
+const navItemContainer: utils.StyleFn = () => ({
+	":not(:first-child)": {
+		marginTop: theme.spacing.xxxl,
+	},
+	display: "flex",
+	flexDirection: "column",
+});
+
+const navItem: utils.StyleFn = () => ({
+	display: "flex",
+	alignItems: "center",
+	"> span": {
+		marginLeft: theme.spacing.xxl,
+		fontWeight: "bold",
+		fontFamily: theme.typography.fonts.headers,
+		fontSize: theme.typography.sizes.h5,
+		lineHeight: 1,
+	},
+});
+
+const navSubitemsWrapper: utils.StyleFn = () => ({
+	paddingLeft: `calc(${theme.typography.sizes.icon.xlarge} + ${theme.spacing.xxl})`,
+	overflow: "hidden",
+	"> div": {
+		display: "flex",
+		flexDirection: "column",
+	},
+});
+
+const navSubitem: utils.StyleFn = () => ({
+	fontSize: theme.typography.sizes.body2,
+	fontFamily: theme.typography.fonts.base,
+	marginTop: theme.spacing.xxl,
+	":first-child": {
+		marginTop: theme.spacing.xl,
+	},
+});
+
+export const useSidenavCSS = (props: SidenavStyleProps) => ({
+	nav: utils.makeStyles([nav])(props),
+	expandButton: utils.makeStyles([expandButton])(props),
+	drawerOverlay: utils.makeStyles([drawerOverlay])(props),
+	navItemsWrapper: utils.makeStyles([navItemsWrapper])(props),
+});
+
+export const useNavItemCSS = (props: SidenavStyleProps) => ({
+	navItemContainer: utils.makeStyles([navItemContainer])(props),
+	navItem: utils.makeStyles([navItem])(props),
+	navSubitemsWrapper: utils.makeStyles([navSubitemsWrapper])(props),
+	navSubitem: utils.makeStyles([navSubitem])(props),
+});

+ 94 - 0
packages/components/src/components/Sidenav/Sidenav.tsx

@@ -0,0 +1,94 @@
+import React, { useState } from "react";
+import { animated, useSpring, useTransition } from "react-spring";
+import useResizeObserver from "use-resize-observer";
+import HamburgerButton from "../HamburgerButton";
+import { EXPANDED_SIDENAV_WIDTH, SIDENAV_WIDTH, useNavItemCSS, useSidenavCSS } from "./Sidenav.style";
+
+type NavSubitem = {
+	name: string;
+};
+
+export type NavItem = {
+	subitems?: NavSubitem[];
+	icon: React.ReactNode;
+} & NavSubitem;
+
+type SidenavProps = {
+	items: NavItem[];
+};
+
+const Sidenav: React.FC<SidenavProps> = ({ items }) => {
+	const [expanded, setExpanded] = useState(false);
+	const styles = useSidenavCSS({ expanded });
+
+	const containerAnimationStyles = useSpring({
+		from: { width: SIDENAV_WIDTH },
+		width: expanded ? EXPANDED_SIDENAV_WIDTH : SIDENAV_WIDTH,
+	});
+	const overlayTransitions = useTransition(expanded, null, {
+		from: { opacity: 0, display: "none" },
+		enter: { opacity: 1, display: "block" },
+		leave: { opacity: 0 },
+	});
+
+	return (
+		<>
+			<animated.nav css={styles.nav} style={containerAnimationStyles}>
+				<HamburgerButton
+					active={expanded}
+					onClick={() => setExpanded(!expanded)}
+					outerStyles={styles.expandButton}
+				/>
+				<div css={styles.navItemsWrapper}>
+					{items.map((item) => (
+						<NavItem key={item.name} expanded={expanded} subitems={item.subitems}>
+							{item.icon}
+							<span>{item.name}</span>
+						</NavItem>
+					))}
+				</div>
+			</animated.nav>
+			{overlayTransitions.map(
+				({ item, key, props }) =>
+					item && (
+						<animated.div
+							css={styles.drawerOverlay}
+							key={key}
+							style={props}
+							onClick={() => setExpanded(false)}
+						/>
+					)
+			)}
+		</>
+	);
+};
+
+type NavItemProps = {
+	subitems?: NavSubitem[];
+	expanded: boolean;
+};
+
+const NavItem: React.FC<NavItemProps> = ({ expanded, subitems, children }) => {
+	const styles = useNavItemCSS({ expanded });
+	const { height: subitemsHeight, ref: subitemsRef } = useResizeObserver<HTMLDivElement>();
+	const subitemsAnimationStyles = useSpring({ height: expanded ? subitemsHeight || 0 : 0 });
+
+	return (
+		<div css={styles.navItemContainer}>
+			<a css={styles.navItem}>{children}</a>
+			{subitems && (
+				<animated.div css={styles.navSubitemsWrapper} style={subitemsAnimationStyles}>
+					<div ref={subitemsRef}>
+						{subitems.map((item) => (
+							<a key={item.name} css={styles.navSubitem}>
+								{item.name}
+							</a>
+						))}
+					</div>
+				</animated.div>
+			)}
+		</div>
+	);
+};
+
+export default Sidenav;

+ 4 - 0
packages/components/src/components/Sidenav/index.ts

@@ -0,0 +1,4 @@
+import Sidenav, { NavItem } from "./Sidenav";
+import { EXPANDED_SIDENAV_WIDTH, SIDENAV_WIDTH } from "./Sidenav.style";
+
+export { Sidenav as default, SIDENAV_WIDTH, EXPANDED_SIDENAV_WIDTH, NavItem };

+ 14 - 14
packages/components/src/components/Tabs/Tabs.style.ts

@@ -1,16 +1,16 @@
-import { typography, colors, spacing } from "../../theme"
-import { StyleFn, makeStyles } from "../../utils"
+import { colors, spacing, typography } from "../../theme";
+import { makeStyles, StyleFn } from "../../utils";
 
-export type TabsStyleProps = {}
+export type TabsStyleProps = {};
 
 const container: StyleFn = () => ({
 	fontFamily: typography.fonts.base,
-	color: colors.white
-})
+	color: colors.white,
+});
 
 const tabs: StyleFn = () => ({
-	display: "flex"
-})
+	display: "flex",
+});
 const tab: StyleFn = () => ({
 	flexBasis: "content",
 	padding: `${spacing.m} ${spacing.l}`,
@@ -18,18 +18,18 @@ const tab: StyleFn = () => ({
 	borderBottom: `3px solid ${colors.gray[900]}`,
 	minWidth: "100px",
 	colors: colors.gray[200],
-	textAlign: "center"
-})
+	textAlign: "center",
+});
 
 const activeTab: StyleFn = () => ({
-	...tab({}),
+	...tab({}, {}),
 	color: colors.white,
 	backgroundColor: "transparent",
-	borderBottom: `3px solid ${colors.blue[500]}`
-})
+	borderBottom: `3px solid ${colors.blue[500]}`,
+});
 export const useCSS = (props: TabsStyleProps) => ({
 	container: makeStyles([container])(props),
 	tabs: makeStyles([tabs])(props),
 	tab: makeStyles([tab])(props),
-	activeTab: makeStyles([activeTab])(props)
-})
+	activeTab: makeStyles([activeTab])(props),
+});

+ 9 - 0
packages/components/src/index.ts

@@ -1,4 +1,5 @@
 import * as theme from "./theme";
+import * as utils from "./utils";
 
 export { default as Button } from "./components/Button";
 export { default as Carousel } from "./components/Carousel";
@@ -19,6 +20,14 @@ export { default as VideoPreview } from "./components/VideoPreview";
 export { default as VideoPlayer } from "./components/VideoPlayer";
 export { default as SeriesPreview } from "./components/SeriesPreview";
 export { default as ChannelPreview } from "./components/ChannelPreview";
+export { default as HamburgerButton } from "./components/HamburgerButton";
 export { default as Gallery } from "./components/Gallery";
+export { default as BarsIcon } from "../assets/bars.svg";
+export { default as HomeIcon } from "../assets/home.svg";
+export { default as BinocularIcon } from "../assets/binocular.svg";
+export { default as BrowseIcon } from "../assets/browse.svg";
+export { default as BooksIcon } from "../assets/books.svg";
+export { default as Sidenav, SIDENAV_WIDTH, EXPANDED_SIDENAV_WIDTH, NavItem } from "./components/Sidenav";
 export { default as GlobalStyle } from "./components/GlobalStyle";
+export { utils };
 export { theme };

+ 1 - 1
packages/components/src/theme/typography.ts

@@ -16,7 +16,7 @@ export default {
 		h2: "2.5rem",
 		h3: "2rem",
 		h4: "1.5rem",
-		h5: "1.1rem",
+		h5: "1.25rem",
 		h6: "1rem",
 		subtitle1: "1.1rem",
 		subtitle2: "0.9rem",

+ 17 - 17
packages/components/src/utils/style-reducer.ts

@@ -1,38 +1,38 @@
-import * as CSS from "csstype"
-import { css } from "@emotion/core"
+import * as CSS from "csstype";
+import { css } from "@emotion/core";
 
-export type StyleObj = { [k in keyof CSS.Properties]: number | string | StyleObj }
-export type StyleFn = (styles: StyleObj, x?: any) => StyleObj
+export type StyleObj = { [k in keyof CSS.Properties]: number | string | StyleObj };
+export type StyleFn<T = any> = (styles: StyleObj, x: T) => StyleObj;
 
-export type Modifiers = { [k: string]: StyleFn }
+export type Modifiers = { [k: string]: StyleFn };
 
 type StyleMonad = (
 	run: StyleFn
 ) => {
-	run: StyleFn
-	map: (f: (args: any) => any) => StyleMonad
-	contramap: (f: (args: any) => any) => StyleMonad
-	concat: (other: StyleMonad) => StyleMonad
-}
+	run: StyleFn;
+	map: (f: (args: any) => any) => StyleMonad;
+	contramap: (f: (args: any) => any) => StyleMonad;
+	concat: (other: StyleMonad) => StyleMonad;
+};
 
 export const Reducer = (run: StyleFn) => ({
 	run,
 	concat: (other: any) => Reducer((styles: StyleObj, props: any) => other.run(run(styles, props), props)),
 	map: (f: (x: any) => any) => Reducer((styles: StyleObj, props: any) => f(run(styles, props))),
-	contramap: (f: (x: any) => any) => Reducer((styles: StyleObj, props: any) => run(styles, f(props)))
-})
+	contramap: (f: (x: any) => any) => Reducer((styles: StyleObj, props: any) => run(styles, f(props))),
+});
 
 export function combineReducers(...reducers: StyleFn[]) {
 	return reducers.reduce(
 		(acc, reducer) => acc.concat(Reducer(reducer)),
 		Reducer(() => ({}))
-	)
+	);
 }
 
 export function makeStyles(reducers: StyleFn[]) {
-	const reducer = combineReducers(...reducers)
+	const reducer = combineReducers(...reducers);
 	return function (props: any) {
-		const styles: any = reducer.run({}, props)
-		return css(styles)
-	}
+		const styles: any = reducer.run({}, props);
+		return css(styles);
+	};
 }

+ 72 - 0
packages/components/stories/13-Sidenav.stories.tsx

@@ -0,0 +1,72 @@
+import React from "react";
+import { BinocularIcon, BrowseIcon, HomeIcon, NavItem, Sidenav, SIDENAV_WIDTH } from "../src";
+import styled from "@emotion/styled";
+
+export default {
+	title: "Sidenav",
+	component: Sidenav,
+};
+
+const NAV_ITEMS: NavItem[] = [
+	{
+		name: "Home",
+		icon: <HomeIcon />,
+	},
+	{
+		name: "Discover",
+		icon: <BinocularIcon />,
+		subitems: [
+			{
+				name: "Channels 1",
+			},
+			{
+				name: "Channels 2",
+			},
+			{
+				name: "Channels 3",
+			},
+			{
+				name: "Channels 4",
+			},
+			{
+				name: "Channels 5",
+			},
+		],
+	},
+	{
+		name: "Browse",
+		icon: <BrowseIcon />,
+		subitems: [
+			{
+				name: "Channels",
+			},
+		],
+	},
+];
+
+export const Default = () => (
+	<StoryStyles>
+		<Sidenav items={NAV_ITEMS} />
+		<ContentWrapper>
+			<p>Sensorem, barcas, et fraticinida. Zeta manducares, tanquam barbatus gallus.</p>
+			<p>Sensorem, barcas, et fraticinida. Zeta manducares, tanquam barbatus gallus.</p>
+			<p>Sensorem, barcas, et fraticinida. Zeta manducares, tanquam barbatus gallus.</p>
+			<p>Sensorem, barcas, et fraticinida. Zeta manducares, tanquam barbatus gallus.</p>
+			<p>Sensorem, barcas, et fraticinida. Zeta manducares, tanquam barbatus gallus.</p>
+			<p>Sensorem, barcas, et fraticinida. Zeta manducares, tanquam barbatus gallus.</p>
+		</ContentWrapper>
+	</StoryStyles>
+);
+
+// this is needed because proper storybook styling isn't merged yet
+// TODO: remove
+const StoryStyles = styled.div`
+	color: white;
+	* {
+		box-sizing: border-box;
+	}
+`;
+
+const ContentWrapper = styled.div`
+	margin-left: ${SIDENAV_WIDTH}px;
+`;

+ 12 - 0
packages/components/stories/14-HamburgerButton.stories.tsx

@@ -0,0 +1,12 @@
+import React, { useState } from "react";
+import { HamburgerButton } from "../src";
+
+export default {
+	title: "HamburgerButton",
+	component: HamburgerButton,
+};
+
+export const Default = () => {
+	const [active, setActive] = useState(false);
+	return <HamburgerButton active={active} onClick={() => setActive(!active)} />;
+};