Add routing (#4)
* Adjust blank line rule * Disable ramda typings * Add common styles * Add routing
This commit is contained in:
parent
58c28d58c0
commit
e7d448dbb0
|
|
@ -24,6 +24,11 @@
|
|||
"prev": ["let", "const"],
|
||||
"next": "*"
|
||||
},
|
||||
{
|
||||
"blankLine": "always",
|
||||
"prev": "*",
|
||||
"next": ["let", "const"]
|
||||
},
|
||||
{
|
||||
"blankLine": "any",
|
||||
"prev": ["singleline-let", "singleline-const"],
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
type Nullable<T> = T | null | undefined
|
||||
|
||||
type UnknownRecord = Record<string, unknown>
|
||||
declare module 'ramda'
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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),
|
||||
)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
$mobile-wide-min: 480px !default;
|
||||
$tablet-min: 887px !default;
|
||||
$tablet-wide-min: 1287px !default;
|
||||
$desktop-min: 1288px !default;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
@use "~modern-css-reset" as *;
|
||||
Loading…
Reference in New Issue