Add routing (#4)

* Adjust blank line rule

* Disable ramda typings

* Add common styles

* Add routing
This commit is contained in:
TonCherAmi 2021-08-05 18:38:45 +03:00 committed by GitHub
parent 58c28d58c0
commit e7d448dbb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 287 additions and 2 deletions

View File

@ -24,6 +24,11 @@
"prev": ["let", "const"],
"next": "*"
},
{
"blankLine": "always",
"prev": "*",
"next": ["let", "const"]
},
{
"blankLine": "any",
"prev": ["singleline-let", "singleline-const"],

2
global.d.ts vendored
View File

@ -1,3 +1,3 @@
type Nullable<T> = T | null | undefined
type UnknownRecord = Record<string, unknown>
declare module 'ramda'

View File

@ -3,12 +3,17 @@
"version": "0.1.0",
"dependencies": {
"@types/qs": "^6.9.7",
"@types/ramda": "^0.27.44",
"@types/react": "^17.0.15",
"@types/react-router-dom": "^5.1.8",
"@types/sprintf-js": "^1.1.2",
"axios": "^0.21.1",
"loglevel": "^1.7.1",
"modern-css-reset": "^1.4.0",
"qs": "^6.10.1",
"ramda": "^0.27.1",
"react": "^17.0.2",
"react-router-dom": "^5.2.0",
"sass-rem": "^3.0.0",
"sprintf-js": "^1.1.2"
}
}

15
routing/Route.ts Normal file
View File

@ -0,0 +1,15 @@
import React from 'react'
interface Route {
path: string
name?: string
redirect?: string
exact?: boolean
navbar?: boolean
isIndex?: boolean
icon?: React.ComponentType
childRoutes?: Route[]
component?: React.ComponentType
}
export default Route

94
routing/Router.tsx Normal file
View File

@ -0,0 +1,94 @@
import React from 'react'
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'
import { prepareRoutes } from './config'
import RouteDefinition from './Route'
const getComponentRoute = (contextPath: string, component: React.ComponentType) => (
<Route
exact
key={contextPath}
component={component}
path={contextPath}
/>
)
const renderRouteConfig = (
Container: React.ComponentType,
routes: RouteDefinition[],
contextPath: string,
): JSX.Element => {
// Resolve route config object in React Router v3.
const children: React.ReactNode[] = []
const renderRoute = (item: RouteDefinition, routeContextPath: string) => {
let newContextPath: string
if (/(^\/)|(^\*)/.test(item.path)) {
newContextPath = item.path
} else {
newContextPath = `${routeContextPath}/${item.path}`
}
newContextPath = newContextPath.replace(/\/+/g, '/')
if (item.redirect) {
const route = (
<Route
exact
key={newContextPath}
render={() => <Redirect to={item.redirect as string} />}
path={newContextPath}
/>
)
children.push(route)
} else if (item.component && item.childRoutes) {
const routeConfig = renderRouteConfig(item.component, item.childRoutes, newContextPath)
children.push(routeConfig)
} else if (item.component) {
const route = getComponentRoute(newContextPath, item.component)
children.push(route)
} else if (item.childRoutes) {
item.childRoutes.forEach(r => renderRoute(r, newContextPath))
}
}
routes.forEach(item => renderRoute(item, contextPath))
// Use Switch as the default container by default
if (!Container) {
return (
<Switch>
{children as JSX.TChildren[]}
</Switch>
)
}
return (
<BrowserRouter>
<Container key={contextPath}>
<Switch>
{children as JSX.TChildren[]}
</Switch>
</Container>
</BrowserRouter>
)
}
interface Props {
routeConfig: RouteDefinition[]
component: React.ComponentType
baseUrlPath: string
}
const Router: React.FC<Props> = (props: Props) => {
const { routeConfig, component, baseUrlPath } = props
const preparedRoutes = prepareRoutes(routeConfig)
return renderRouteConfig(component, preparedRoutes, baseUrlPath)
}
export default Router

33
routing/config.ts Normal file
View File

@ -0,0 +1,33 @@
import * as R from 'ramda'
import Route from './Route'
// Handle isIndex property of route config:
// Duplicate it and put it as the first route rule.
const handleIndexRoute = (route: Route) => {
if (!route.childRoutes || !route.childRoutes.length) {
return
}
const indexRoute = route.childRoutes.find(R.prop('isIndex'))
if (indexRoute) {
const first = {
...indexRoute,
path: route.path,
exact: true,
}
route.childRoutes.unshift(first)
}
route.childRoutes.forEach(handleIndexRoute)
}
export const prepareRoutes: (route: Route[]) => Route[] = R.pipe(
R.filter((r: Route): boolean => Boolean(
r.redirect
|| r.component
|| (r.childRoutes && r.childRoutes.length > 0),
)),
R.forEach(handleIndexRoute),
)

4
styles/breakpoints.scss Normal file
View File

@ -0,0 +1,4 @@
$mobile-wide-min: 480px !default;
$tablet-min: 887px !default;
$tablet-wide-min: 1287px !default;
$desktop-min: 1288px !default;

49
styles/global.scss Normal file
View File

@ -0,0 +1,49 @@
$color: black !default;
$link-color: $color !default;
$font-family: default !default;
html, body, #root {
padding: 0;
margin: 0;
font-family: $font-family;
color: $color;
height: 100%;
}
input, textarea {
font-family: $font-family;
font-weight: 400;
}
/* Hide spin button for number input */
input {
/* Chrome, Safari, Edge, Opera */
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
&[type=number] {
-moz-appearance: textfield;
}
}
button {
cursor: pointer;
}
p {
margin: 0;
}
*:focus {
outline: none;
}
a {
color: $link-color;
}

79
styles/mixins.scss Normal file
View File

@ -0,0 +1,79 @@
@use "breakpoints";
@mixin mobile-narrow {
@media screen and (max-width: breakpoints.$mobile-wide-min - 1) {
@content;
}
}
@mixin mobile {
@media screen and (max-width: breakpoints.$tablet-min - 1) {
@content;
}
}
@mixin tablet-narrow {
@media screen and (min-width: breakpoints.$tablet-min) and (max-width: breakpoints.$tablet-wide-min - 1) {
@content;
}
}
@mixin tablet {
@media screen and (min-width: breakpoints.$tablet-min) and (max-width: breakpoints.$desktop-min - 1) {
@content;
}
}
@mixin desktop {
@media screen and (min-width: breakpoints.$desktop-min) {
@content;
}
}
@mixin no-desktop {
@media screen and (max-width: breakpoints.$desktop-min - 1) {
@content;
}
}
@mixin no-mobile {
@media screen and (min-width: breakpoints.$tablet-min) {
@content;
}
}
@mixin retina {
@media (min-device-pixel-ratio: 1.5), (min-resolution: 192dpi) {
@content;
}
}
@mixin webp-fallback {
:global(.no-webp) {
@content;
}
}
@mixin hover {
@media(hover: hover) and (pointer: fine) {
&:hover {
@content;
}
}
@media (hover: hover), (-ms-high-contrast:none) {
&:hover {
@content;
}
}
&:active {
@content;
}
}
@mixin ie11-fallback {
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
@content;
}
}

1
styles/reset.scss Normal file
View File

@ -0,0 +1 @@
@use "~modern-css-reset" as *;