responsive design

This commit is contained in:
amirhouieh 2022-05-18 12:24:24 +02:00
parent 37ca3fd7e4
commit 7dd52656e8
9 changed files with 263 additions and 105 deletions

View File

@ -2,26 +2,19 @@ import {FC} from "react";
import {LogoHolder} from "./LogoHolder"; import {LogoHolder} from "./LogoHolder";
import {SidebarToggleButton} from "./SidebarToggleButton"; import {SidebarToggleButton} from "./SidebarToggleButton";
import {useLogosTheme} from "../context/ThemeProvider"; import {useLogosTheme} from "../context/ThemeProvider";
import {Stack} from "./design-system/Stack/Stack";
interface IProps{ interface IProps{
toggleSidebar: () => void; className?: string;
onSidebarToggle: () => void;
} }
export const Header: FC<IProps> = (props) => { export const Header: FC<IProps> = ({className="", onSidebarToggle}) => {
const {toggleSidebar} = props;
const {toggleMode} = useLogosTheme(); const {toggleMode} = useLogosTheme();
return ( return (
<header> <header className={className}>
<Stack justifyContent={"space-between"} <SidebarToggleButton onClick={onSidebarToggle} />
> <LogoHolder onClick={toggleMode} filePath={"/assets/logos-logo.svg"}/>
<SidebarToggleButton onClick={toggleSidebar}/>
<div style={{width: "30%"}}/>
<div style={{width: "50%"}}>
<LogoHolder onClick={toggleMode} filePath={"/assets/logos-logo.svg"}/>
</div>
</Stack>
</header> </header>
) )
} }

View File

@ -9,7 +9,12 @@ export interface ILogosHolderProps{
} }
export const LogoHolder: FC<PropsWithChildren<ILogosHolderProps>> = ({onClick, filePath, alt, title, children}) => ( export const LogoHolder: FC<PropsWithChildren<ILogosHolderProps>> = ({onClick, filePath, alt, title, children}) => (
<div onClick={onClick} className={"logo-holder button"}> <div onClick={onClick}
className={"logo-holder button"}
style={{
display: "inline-block"
}}
>
<LogoSvg/> <LogoSvg/>
</div> </div>
) )

View File

@ -1,10 +1,18 @@
import {FC, PropsWithChildren} from "react"; import {FC, PropsWithChildren, useEffect, useState} from "react";
import {INavigationItemProps} from "../types/data.types"; import {INavigationItemProps} from "../types/data.types";
import Link from "next/link"; import Link from "next/link";
import {SidebarToggleButton} from "./SidebarToggleButton";
import CloseIcon from "/public/assets/sidebar-icon-close.svg";
import {useViewport} from "../utils/ui-utils";
const sidebar: INavigationItemProps = require("../public/compiled/sidebar.tree.min.json"); const sidebar: INavigationItemProps = require("../public/compiled/sidebar.tree.min.json");
interface ISidebarProps{ interface ISidebarProps{
className?: string;
onClose?: () => void;
onOpen?: () => void;
initialHide?: boolean;
hide: boolean;
} }
interface IMenuProps{ interface IMenuProps{
@ -56,14 +64,33 @@ const Menu: FC<IMenuProps> = (props) => {
} }
export const Sidebar: FC<ISidebarProps> = (props) => { export const Sidebar: FC<ISidebarProps> = (props) => {
const {className = "", onClose = ()=>{}, onOpen = () => {}, initialHide = false, hide} = props;
const mainItems = sidebar.children.filter((c) => c.children.length===0) const mainItems = sidebar.children.filter((c) => c.children.length===0)
const subItems = sidebar.children.filter((c) => c.children.length!==0); const subItems = sidebar.children.filter((c) => c.children.length!==0);
const [firstTime, setFirstTime] = useState(false);
const isOpen = !((!firstTime&&initialHide) || hide);
const hideClass = isOpen? "":"hide"
useEffect(() => {
if(hide){
setFirstTime(true);
}
if(isOpen){
onOpen();
}
}, [hide, isOpen])
return ( return (
<nav className={`default-sidebar`}> <div className={`${className} ${hideClass}`}>
<Menu items={[{...sidebar, title: "", children: mainItems}]}/> <nav className={`sidebarNav`}>
<br/> <div className={"sidebar-close-icon button"} onClick={onClose}>
<Menu items={subItems}/> <CloseIcon/>
</nav> </div>
<Menu items={[{...sidebar, title: "", children: mainItems}]}/>
<br/>
<Menu items={subItems}/>
</nav>
</div>
) )
} }

View File

@ -1,6 +1,7 @@
import type {AppProps} from 'next/app' import type {AppProps} from 'next/app'
import {PageComponent} from "../types/page"; import {PageComponent} from "../types/page";
import {LogosThemeProvider} from "../context/ThemeProvider"; import {LogosThemeProvider} from "../context/ThemeProvider";
import Head from 'next/head'
interface IProps extends AppProps{ interface IProps extends AppProps{
Component: PageComponent Component: PageComponent
@ -13,6 +14,10 @@ function App({Component, pageProps}: IProps) {
const {children, ...rest} = pageProps; const {children, ...rest} = pageProps;
return ( return (
<LogosThemeProvider> <LogosThemeProvider>
<Head>
<title>Logos site builder</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<Component {...rest}>{children}</Component> <Component {...rest}>{children}</Component>
</LogosThemeProvider> </LogosThemeProvider>
); );

View File

@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.34317 2.75729L11.2427 12.6568C11.6316 13.0457 12.268 13.0457 12.6569 12.6568C13.0458 12.2679 13.0458 11.6315 12.6569 11.2426L2.75738 1.34308C2.36847 0.954168 1.73208 0.954168 1.34317 1.34308C0.954258 1.73199 0.954258 2.36838 1.34317 2.75729Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.75741 12.6568L12.6569 2.75728C13.0458 2.36837 13.0458 1.73197 12.6569 1.34306C12.268 0.954155 11.6316 0.954155 11.2427 1.34306L1.3432 11.2426C0.95429 11.6315 0.95429 12.2679 1.3432 12.6568C1.73211 13.0457 2.3685 13.0457 2.75741 12.6568Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 718 B

View File

@ -1,61 +1,50 @@
:root{
--bg-color: var(--dark-background-color);
--txt-color: var(--dark-text-color);
--hl-color: var(--dark-highlight-color);
--ff: var(--fontFamily);
}
*, :after, :before { *, :after, :before {
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
} }
body, body.dark{ html, body{
background: var(--dark-background-color);
color: var(--dark-text-color); }
font-family: var(--fontFamily);
body{
background: var(--bg-color);
color: var(--txt-color);
font-family: var(--ff);
} }
body.light{ body.light{
background: var(--light-background-color); --bg-color: var(--light-background-color);
color: var(--light-text-color); --txt-color: var(--light-text-color);
font-family: var(--fontFamily); --hl-color: var(--light-highlight-color);
} }
a, a:visited, a:hover, a, a:visited, a:hover{
body.dark a, body.dark a:visited, body.dark a:hover{ color: var(--hl-color);
color: var(--dark-highlight-color);
} }
body.light a, body.light a:visited, body.light a:hover{ svg, svg *{
color: var(--dark-highlight-color); fill: var(--txt-color);
}
svg, svg *,
body.dark svg, body.dark svg *{
fill: var(--dark-text-color);
}
body.light svg, body.light svg *{
fill: var(--light-text-color);
} }
input[type="text"]{ input[type="text"]{
background: var(--dark-background-color); background: var(--bg-color);
color: var(--dark-text-color); color: var(--txt-color);
padding: 0.5em; padding: 0.5em;
border: 1px solid; border: 1px solid;
} }
body.light input[type="text"]{
background: var(--light-background-color);
color: var(--light-text-color);
}
::placeholder, ::placeholder,
:-ms-input-placeholder, :-ms-input-placeholder,
::-ms-input-placeholder { ::-ms-input-placeholder {
color: var(--dark-text-color); color: var(--txt-color);
opacity: 1;
}
body.light ::placeholder,
body.light :-ms-input-placeholder,
body.light ::-ms-input-placeholder {
color: var(--light-text-color);
opacity: 1; opacity: 1;
} }
@ -67,19 +56,10 @@ nav a,
nav a:visited, nav a:visited,
nav a:hover{ nav a:hover{
text-decoration: none; text-decoration: none;
color: var(--dark-text-color) !important; color: var(--txt-color) !important;
text-transform: capitalize; text-transform: capitalize;
} }
body.light nav a,
body.light nav a:visited,
body.light nav a:hover{
text-decoration: none;
color: var(--light-text-color) !important;
text-transform: capitalize;
}
.default-sidebar .sidebar-menu > li.level-0{ .default-sidebar .sidebar-menu > li.level-0{
margin-bottom: 1em; margin-bottom: 1em;
} }
@ -88,9 +68,12 @@ body.light nav a:hover{
margin-bottom: 1em; margin-bottom: 1em;
} }
/*this part should be coming from the content but for now for sake of the look*/ /*this part should be coming from the content but for now for sake of the look*/
h1,h2,h3,h4,h5,h6{ h1,h2,h3,h4,h5,h6{
font-size: inherit !important; font-size: inherit !important;
/*font-weight: normal;*/ /*font-weight: normal;*/
}
pre{
overflow-x: auto;
} }

View File

@ -1,15 +1,13 @@
import {FC, useState} from "react"; import {FC, useEffect, useState} from "react";
import {TTemplateProps} from "../../types/ui.types"; import {TTemplateProps} from "../../types/ui.types";
import {Sidebar} from "../../components/Sidebar"; import {Sidebar} from "../../components/Sidebar";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import {IMarkdown} from "../../types/data.types"; import {IMarkdown} from "../../types/data.types";
import {Header} from "../../components/Header"; import {Header} from "../../components/Header";
import {Stack} from "../../components/design-system/Stack/Stack"; import {SearchInput} from "../../components/SearchInput";
import style from "./Style.module.css"; import style from "./Style.module.css";
import {SearchInput} from "../../components/SearchInput";
interface IProps{ interface IProps{
markdown: IMarkdown<any>; markdown: IMarkdown<any>;
} }
@ -17,33 +15,46 @@ interface IProps{
export const DefaultTemplate_Markdown: FC<TTemplateProps<IProps>> = (props) => { export const DefaultTemplate_Markdown: FC<TTemplateProps<IProps>> = (props) => {
const {append = false, markdown} = props; const {append = false, markdown} = props;
const [sidebarHide, setSideBarHide] = useState(false); const [sidebarHide, setSideBarHide] = useState(false);
const [sidebarOpen, setSidebarOpen] = useState(false);
const onSidebarOpen = () => {
setSidebarOpen(true);
}
const onSidebarClose = () => {
setSidebarOpen(false);
setSideBarHide(true)
}
return ( return (
<div className={style.container}> <div className={style.container}>
<Header toggleSidebar={() => setSideBarHide(!sidebarHide)}/> <Header className={style.header} onSidebarToggle={() => setSideBarHide(!sidebarHide)}/>
{!append&&props.children} <div className={style.banner}/>
<div className={style.banner}> <div className={style.mainContainer}>
<Stack> <div className={`${style.sidebarWrapper} ${style.col_1_4}`}>
<div className={style.col_1_4}/> <Sidebar className={`${style.sidebar} ${style.sidebarDefault}`}
<div className={`${style.searchInput} ${style.col_2_4}`}> hide={sidebarHide}
/>
<Sidebar className={`${style.sidebar} ${style.sidebarNarrow}`}
hide={sidebarHide}
initialHide={true}
onOpen={onSidebarOpen}
onClose={onSidebarClose}
/>
</div>
<main className={`${style.col_2_4}`}>
<div className={`${style.searchInput} ${sidebarOpen? style.withOpenSidebar: ""}`}>
<SearchInput/> <SearchInput/>
</div> </div>
</Stack> <div className={style.content}>
</div> {!append&&props.children}
<Stack> <ReactMarkdown>
<div className={`${style.sidebarWrapper} ${style.col_1_4}`}> {markdown.content}
{ </ReactMarkdown>
!sidebarHide&& {append&&props.children}
<Sidebar/> </div>
}
</div>
<main className={style.col_2_4}>
<ReactMarkdown>
{markdown.content}
</ReactMarkdown>
</main> </main>
</Stack> </div>
{append&&props.children}
</div> </div>
) )
} }

View File

@ -1,29 +1,44 @@
@value headerGapT: 300px; @value headerGapT: 300px;
@value headerGapB: 4em; @value headerGapB: 4em;
@value containerGapT: 2em; @value containerGapT: 2em;
@value mainGapT: 3em;
@value mainGapB: 20em; @value mainGapB: 20em;
@value pagePaddingT: containerGapT;
@value pagePaddingR: inherit;
@value pagePaddingB: inherit;
@value pagePaddingL: inherit;
.container{ .container{
max-width: 1200px; max-width: 1200px;
margin: auto; margin: auto;
padding-top: containerGapT; padding-top: pagePaddingT;
padding-bottom: pagePaddingB;
padding-right: pagePaddingR;
padding-left: pagePaddingL;
} }
.container :global(.sidebar-toggle-button){ .mainContainer{
display: flex;
flex-direction: row;
}
.header{
text-align: center;
}
.header :global(.sidebar-toggle-button){
position: fixed; position: fixed;
top: containerGapT;
} }
.banner{ .banner{
/*background: var(--dark-background-color);*/
position: sticky;
z-index: 9999; z-index: 9999;
padding-bottom: headerGapB; padding-bottom: headerGapB;
padding-top: headerGapT; padding-top: headerGapT;
top: calc(calc(-1 * headerGapT) + containerGapT) top: calc(calc(-1 * headerGapT) + containerGapT);
} }
.container main{ .container main .content{
padding-top: mainGapT;
padding-bottom: mainGapB; padding-bottom: mainGapB;
} }
@ -35,13 +50,9 @@
width: 50%; width: 50%;
} }
.sidebarWrapper{ .searchInput{
position: sticky; position: sticky;
top: calc(headerGapB + 1em); top: containerGapT;
}
.headerGap{
} }
.searchInput input{ .searchInput input{
@ -54,8 +65,90 @@
.container nav > ul{ .container nav > ul{
padding-left: 0; padding-left: 0;
padding-top: 0;
margin-top: 0;
} }
.container ul, .container li{ .container ul, .container li{
list-style: none; list-style: none;
}
/*SIDEBAR*/
.sidebarWrapper{
padding-top: calc(mainGapT - 1em)
}
.sidebar{
position: sticky;
top: calc(headerGapB + 3em);
padding-top: mainGapT;
}
.sidebar:global(.hide) nav{
display: none;
}
.sidebarDefault{
}
.sidebarNarrow{
display: none;
}
.sidebar :global(.sidebar-close-icon){
display: none;
}
@media (max-width:600px) {
@value pagePaddingT: 6em;
@value pagePaddingR: 4em;
@value pagePaddingB: 3em;
@value pagePaddingL: 4em;
@value headerGapT: 100px;
@value headerGapB: 4em;
.sidebarDefault{
display: none;
}
.sidebarNarrow{
display: block;
}
.searchInput.withOpenSidebar{
z-index: -1;
}
.sidebar :global(.sidebar-close-icon){
display: block;
position: fixed;
top: pagePaddingT;
transform: translate(9px, 10px);
}
.sidebar nav{
position: fixed;
background: var(--bg-color);
top: 0;
left: 0;
width: 100vw;
height: 100vh;
padding-top: calc(pagePaddingT + headerGapT);
padding-bottom: pagePaddingB;
padding-right: pagePaddingR;
padding-left: pagePaddingL;
}
.sidebar :global(.sidebar-toggle-button){
z-index: 999999999;
}
.searchInput{
/*z-index: -1;*/
}
.col_1_4, .col_2_4{
width: 100%;
}
} }

37
utils/ui-utils.ts Normal file
View File

@ -0,0 +1,37 @@
import {useEffect, useState} from "react";
type TBox = {
width: number;
height: number;
}
export const useWindowDimensions = () => {
//default SSR
const [state, setState] = useState<TBox>({width: 1200, height: 800});
const onResize = () => {
setState({
width: window.innerWidth,
height: window.innerHeight
});
}
useEffect(() => {
onResize();
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
return state;
}
interface IViewportState{
isNarrow: boolean;
}
export const useViewport = (): IViewportState => {
const {width} = useWindowDimensions();
return {
isNarrow: width <= 600
}
}