Plugin
This commit is contained in:
commit
7204cdab6f
8
.babelrc
Normal file
8
.babelrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"presets": ["@babel/env", "@babel/preset-react", "@babel/preset-typescript"],
|
||||
"plugins": [
|
||||
["@babel/plugin-transform-typescript", {
|
||||
"allowNamespaces": true
|
||||
}]
|
||||
]
|
||||
}
|
||||
4
.browserslistrc
Normal file
4
.browserslistrc
Normal file
@ -0,0 +1,4 @@
|
||||
> 0.5%
|
||||
last 3 versions
|
||||
Firefox ESR
|
||||
not dead
|
||||
4
.eslintignore
Normal file
4
.eslintignore
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
.parcel-cache
|
||||
dist
|
||||
build
|
||||
27
.eslintrc.js
Normal file
27
.eslintrc.js
Normal file
@ -0,0 +1,27 @@
|
||||
module.exports = {
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
parcel: {
|
||||
extensions: ['.ts', '.tsx'], // whatever extra extensions you want to look for
|
||||
},
|
||||
},
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
linkComponents: [{ name: 'Link', linkAttribute: 'to' }],
|
||||
},
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:import/errors',
|
||||
'plugin:import/typescript',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
rules: {
|
||||
'import/no-unresolved': [2, { ignore: ['url:', 'svg:'] }],
|
||||
},
|
||||
};
|
||||
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
# production
|
||||
dist
|
||||
build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# AppSync
|
||||
AppSync.js
|
||||
|
||||
.happypack
|
||||
*.log
|
||||
webpack/webpack-assets.json
|
||||
/.parcel-cache
|
||||
/.netrc
|
||||
node_modules
|
||||
6
.parcelrc
Normal file
6
.parcelrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "@parcel/config-default",
|
||||
"transformers": {
|
||||
"svg:*": ["@parcel/transformer-svgo", "@parcel/transformer-raw"]
|
||||
}
|
||||
}
|
||||
8
.postcssrc
Normal file
8
.postcssrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"modules": true,
|
||||
"plugins": {
|
||||
"autoprefixer": {
|
||||
"grid": true
|
||||
}
|
||||
}
|
||||
}
|
||||
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"arrowParens": "always"
|
||||
}
|
||||
20
.vscode/settings.json
vendored
Normal file
20
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[javascript]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"yarn.lock": true,
|
||||
"package.json": true,
|
||||
"tsconfig.json": true,
|
||||
".*": true,
|
||||
},
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
}
|
||||
14
README.md
Normal file
14
README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Requirements
|
||||
- Node 14
|
||||
|
||||
# Setup
|
||||
- Install Node 14 (recommend using nvm)
|
||||
- Install yarn globaly `npm i -g yarn`
|
||||
- Clone repository `git clone https://git.hofmannnet.myhome-server.de/GermanAirlines/ga-test.git`
|
||||
- CD into repository
|
||||
- Fetch dependencies: `yarn install`
|
||||
|
||||
# Scripts
|
||||
- Dev server: `yarn start`
|
||||
- Prod build: `yarn build`
|
||||
- Demo prod build: `yarn demo`. Requires Python 3
|
||||
62
package.json
Normal file
62
package.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "ga-test",
|
||||
"version": "1.0.0",
|
||||
"description": "GA Build System Test",
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^2.5.1",
|
||||
"@reach/router": "^1.3.4",
|
||||
"bem-classnames": "^1.0.7",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hook-form": "^7.6.10",
|
||||
"uuid": "^8.3.2",
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.14.3",
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/plugin-transform-typescript": "^7.14.3",
|
||||
"@babel/preset-env": "^7.14.2",
|
||||
"@babel/preset-react": "^7.13.13",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@parcel/config-default": "^2.0.0-alpha.3.1",
|
||||
"@parcel/core": "^2.0.0-beta.3.1",
|
||||
"@parcel/transformer-raw": "^2.0.0-alpha.3.1",
|
||||
"@parcel/transformer-sass": "^2.0.0-beta.3.1",
|
||||
"@parcel/transformer-svgo": "^2.0.0-beta.3.1",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^15.3.1",
|
||||
"@types/prop-types": "^15.7.3",
|
||||
"@types/reach__router": "^1.3.7",
|
||||
"@types/react": "^17.0.6",
|
||||
"@types/react-dom": "^17.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^4.24.0",
|
||||
"@typescript-eslint/parser": "^4.24.0",
|
||||
"autoprefixer": "^10.2.5",
|
||||
"eslint": "^7.26.0",
|
||||
"eslint-import-resolver-parcel": "^1.10.6",
|
||||
"eslint-plugin-import": "^2.23.2",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"parcel": "^2.0.0-beta.3.1",
|
||||
"postcss": "^8.2.15",
|
||||
"postcss-modules": "^4.0.0",
|
||||
"prettier": "^2.3.0",
|
||||
"prettier-plugin-organize-imports": "^2.1.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"svgo": "^2.3.0",
|
||||
"typescript": "^4.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rimraf ./build && parcel build src/index.html --dist-dir build --public-url 'plugins/germanairlinesva/react/assets' --log-level verbose",
|
||||
"clean": "rimraf ./build ./dist ./.parcel-cache",
|
||||
"format": "prettier --write 'src/**/*.{ts,tsx,scss}'",
|
||||
"lint": "eslint ./src --fix",
|
||||
"start": "parcel serve src/index.html --open --log-level verbose",
|
||||
"demo": "cd ./build && python3 -m http.server 1234"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Kilian Kurt Hofmann",
|
||||
"license": "MIT"
|
||||
}
|
||||
54
src/App.tsx
Normal file
54
src/App.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import { Redirect, Router } from '@reach/router';
|
||||
import React from 'react';
|
||||
import classNames from './assets/scss/App.scss';
|
||||
import { useSideBar } from './context/SideBar/SideBarContext';
|
||||
import MenuBar from './layout/MenuBar/MenuBar';
|
||||
import SideBar from './layout/SideBar/SideBar';
|
||||
import HomePage from './pages/HomePage/HomePage';
|
||||
import NotFoundPage from './pages/NotFound/NotFoundPage';
|
||||
import LogbookPage from './pages/user/LogbookPage/LogbookPage';
|
||||
import ProfilePage from './pages/user/ProfilePage/ProfilePage';
|
||||
import ProfilePages from './pages/user/ProfilePages';
|
||||
import UserPages from './pages/user/UserPages';
|
||||
import routes from './routes';
|
||||
import OnRouteChange from './util/OnRouteChange';
|
||||
import resolveClassName from './util/resolveClassName';
|
||||
|
||||
const App = (): React.ReactElement => {
|
||||
const { state, toggleState } = useSideBar();
|
||||
|
||||
const classes = {
|
||||
name: 'container',
|
||||
modifiers: ['sidebarExtended'],
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuBar />
|
||||
<div className={resolveClassName(classNames, classes, { sidebarExtended: state == 'open' })}>
|
||||
<SideBar />
|
||||
<Router
|
||||
className={classNames.router}
|
||||
onClick={() => {
|
||||
if (state == 'open') toggleState();
|
||||
}}
|
||||
>
|
||||
<HomePage path={routes.company.home} />
|
||||
|
||||
<UserPages path={routes.personal.base}>
|
||||
<Redirect from="/" to="/" noThrow />
|
||||
<LogbookPage path={routes.personal.logbook} />
|
||||
<ProfilePages path={routes.personal.profile.base}>
|
||||
<ProfilePage path="/" />
|
||||
<ProfilePage path={routes.personal.profile.others} />
|
||||
</ProfilePages>
|
||||
</UserPages>
|
||||
<NotFoundPage default />
|
||||
</Router>
|
||||
<OnRouteChange />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
35
src/assets/img/arrow.svg
Normal file
35
src/assets/img/arrow.svg
Normal file
@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 284.935 284.936" style="enable-background:new 0 0 284.935 284.936;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M222.701,135.9L89.652,2.857C87.748,0.955,85.557,0,83.084,0c-2.474,0-4.664,0.955-6.567,2.857L62.244,17.133 c-1.906,1.903-2.855,4.089-2.855,6.567c0,2.478,0.949,4.664,2.855,6.567l112.204,112.204L62.244,254.677 c-1.906,1.903-2.855,4.093-2.855,6.564c0,2.477,0.949,4.667,2.855,6.57l14.274,14.271c1.903,1.905,4.093,2.854,6.567,2.854 c2.473,0,4.663-0.951,6.567-2.854l133.042-133.044c1.902-1.902,2.854-4.093,2.854-6.567S224.603,137.807,222.701,135.9z" fill="#999999"/>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 883 B |
41
src/assets/img/pilot.svg
Normal file
41
src/assets/img/pilot.svg
Normal file
@ -0,0 +1,41 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 495.992 495.992" style="enable-background:new 0 0 495.992 495.992;" xml:space="preserve" width="512px" height="512px">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M447.816,390.184c-5.376-32.232-27.976-58.68-58.976-69.016L320,298.224v-10.232h-16v14.976l-12.344,47.312 l-31.904-38.288h-23.504l-31.904,38.288L192,302.968v-14.976h-16v10.232l-68.832,22.944c-31,10.336-53.6,36.784-58.976,69.016 l-17.64,105.808H465.44L447.816,390.184z M120.84,479.992l7.12-71.2l-15.92-1.592l-7.28,72.8H49.448l14.52-87.184 c4.4-26.368,22.888-48.008,48.256-56.472l33.408-11.136l-19.608,45.752l23.664,15.776l-15.104,30.184l49.056,63.072H120.84z M203.912,479.992l-50.496-64.928l16.896-33.816l-24.336-16.224l19.96-46.576l12.48-4.16l43.232,165.704H203.912z M220.384,411.768l-11.072-42.448l4.792-5.752l9.656,14.48L220.384,411.768z M263.984,456.328l-6.168,23.664h-19.64L232,456.328 l8.232-82.392l-15.456-23.184l18.976-22.76h8.496l18.96,22.76l-15.456,23.184L263.984,456.328z M281.896,363.568l4.792,5.752 l-11.072,42.448l-3.376-33.72L281.896,363.568z M274.352,479.992l43.232-165.704l12.48,4.16l19.96,46.576l-24.336,16.224 l16.896,33.816l-50.496,64.928H274.352z M391.24,479.992l-7.28-72.8l-15.92,1.592l7.12,71.2h-62.8l49.056-63.072l-15.104-30.184 l23.664-15.776L350.368,325.2l33.408,11.136c25.368,8.464,43.856,30.104,48.256,56.472l14.52,87.184H391.24z" fill="#FFFFFF"/>
|
||||
<path d="M128,122.888v21.304c-9.656,7.312-16,18.784-16,31.792c0,20.528,15.6,37.296,35.536,39.552 c4.08,14.04,11.616,26.912,22.24,37.536l31.192,31.192c7.552,7.552,17.6,11.72,28.288,11.72h37.488 c10.688,0,20.736-4.168,28.288-11.72l31.192-31.192c10.624-10.624,18.152-23.504,22.24-37.536 C368.4,213.288,384,196.52,384,175.992c0-13.016-6.344-24.488-16-31.792v-21.304C398.896,109.64,416,91.728,416,72 c0-40.376-73.8-72-168-72S80,31.624,80,72C80,91.72,97.104,109.632,128,122.888z M128,175.992c0-10.416,6.712-19.216,16-22.528 v37.392c0,2.616,0.152,5.208,0.376,7.784C134.896,195.432,128,186.544,128,175.992z M336,190.856 c0,19.232-7.496,37.312-21.088,50.912L283.72,272.96c-4.536,4.536-10.56,7.032-16.976,7.032h-37.488 c-6.416,0-12.448-2.496-16.976-7.032l-31.192-31.192c-13.592-13.6-21.088-31.68-21.088-50.912v-44.944 c19.632-4.464,51.112-9.92,88-9.92c36.816,0,68.336,5.464,88,9.928V190.856z M351.624,198.632 c0.224-2.568,0.376-5.16,0.376-7.776v-37.392c9.288,3.312,16,12.112,16,22.528C368,186.544,361.104,195.432,351.624,198.632z M248,15.992c89.576,0,152,29.512,152,56c0,11.832-11.88,23.912-32.952,33.912l-4.528-1.504 c-0.928-0.304-23.088-7.592-57.424-12.376l-2.2,15.84c23.128,3.224,40.696,7.696,49.104,10.08v15.376 c-17.776-4.76-56.368-13.328-104-13.328c-47.632,0-86.224,8.568-104,13.336v-15.384c8.384-2.376,25.896-6.84,49.104-10.072 l-2.2-15.84c-34.344,4.784-56.496,12.072-57.424,12.376l-4.528,1.504C107.88,95.904,96,83.824,96,71.992 C96,45.504,158.424,15.992,248,15.992z" fill="#FFFFFF"/>
|
||||
<path d="M211.392,87.992c6.192,14.104,20.248,24,36.608,24s30.416-9.896,36.608-24H308l36-48h-56v16h24l-12,16h-12 c0-22.056-17.944-40-40-40c-22.056,0-40,17.944-40,40h-12l-12-16h24v-16h-56l36,48H211.392z M248,47.992 c13.232,0,24,10.768,24,24s-10.768,24-24,24s-24-10.768-24-24S234.768,47.992,248,47.992z" fill="#FFFFFF"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
22
src/assets/scss/App.scss
Normal file
22
src/assets/scss/App.scss
Normal file
@ -0,0 +1,22 @@
|
||||
@import './variables';
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
|
||||
@media (max-width: $mediumScreen) {
|
||||
& {
|
||||
transform: none;
|
||||
transition: transform 150ms ease-in-out;
|
||||
}
|
||||
|
||||
&--sidebarExtended {
|
||||
transform: translateX($sidebarWidth);
|
||||
transition: transform 150ms ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.router {
|
||||
flex-grow: 1;
|
||||
padding: 0.625rem;
|
||||
}
|
||||
21
src/assets/scss/_variables.scss
Normal file
21
src/assets/scss/_variables.scss
Normal file
@ -0,0 +1,21 @@
|
||||
// COLORS
|
||||
|
||||
$colDarkBlue: #000b29;
|
||||
$colDarkBlue-10: #1a2543;
|
||||
$colDarkBlue-20: #333e5c;
|
||||
$colDarkBlue-30: #4d5876;
|
||||
$colLightBlue: #0070b9;
|
||||
$colLightBlue-10: #1a8ad3;
|
||||
$colLightBlue-20: #33a3ec;
|
||||
$colLightBlue-30: #4dbdff;
|
||||
$colGray: #999999;
|
||||
$colGray-10: #b3b3b3;
|
||||
$colGray-20: #cccccc;
|
||||
$colGray-30: #e6e6e6;
|
||||
|
||||
// SIZES
|
||||
|
||||
$mediumScreen: 768px;
|
||||
|
||||
$menubarHeight: 3.5rem;
|
||||
$sidebarWidth: 250px;
|
||||
67
src/context/CollapseGroup/CollapseGroupContext.tsx
Normal file
67
src/context/CollapseGroup/CollapseGroupContext.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
interface ICollapseGroupContext {
|
||||
/** Register a new collapse to the group */
|
||||
register: ((id: string, callback: () => void) => void) | null;
|
||||
/** Unregister the collapse with the given ID from the group */
|
||||
unregister: ((id: string) => void) | null;
|
||||
/** Collapse all registered collapses but the one with the given ID */
|
||||
collapse: ((id: string) => void) | null;
|
||||
}
|
||||
|
||||
export const CollapseGroupContext = React.createContext<ICollapseGroupContext>({
|
||||
register: null,
|
||||
unregister: null,
|
||||
collapse: null,
|
||||
});
|
||||
|
||||
export const useCollapseGroup = (): ICollapseGroupContext => {
|
||||
const { register, unregister, collapse } = React.useContext(CollapseGroupContext);
|
||||
|
||||
if (register !== null && unregister !== null && collapse !== null) {
|
||||
return {
|
||||
register,
|
||||
unregister,
|
||||
collapse,
|
||||
};
|
||||
} else {
|
||||
throw new Error("Couldn't find context. Is your component inside a CollapseGroupProvider?");
|
||||
}
|
||||
};
|
||||
|
||||
export const CollapseGroupProvider: React.FunctionComponent = ({ children }: { children: React.ReactNode }) => {
|
||||
const [ID] = React.useState(uuidv4());
|
||||
const collapses = React.useRef<{ id: string; callback: () => void }[]>([]);
|
||||
|
||||
const register = (id: string, callback: () => void) => {
|
||||
console.log('Group', ID, 'registered collapse', id);
|
||||
collapses.current = [...collapses.current, { id, callback }];
|
||||
};
|
||||
|
||||
const unregister = (id: string) => {
|
||||
console.log('Group', ID, 'unregistered collapse', id);
|
||||
collapses.current = collapses.current.map((_collapse) => {
|
||||
if (_collapse.id != id) return _collapse;
|
||||
});
|
||||
};
|
||||
|
||||
const collapse = (id: string) => {
|
||||
console.log('Group', ID, 'collapse all but', id);
|
||||
collapses.current.forEach((_collapse) => _collapse.id != id && _collapse.callback());
|
||||
};
|
||||
|
||||
return (
|
||||
<CollapseGroupContext.Provider
|
||||
value={{
|
||||
register,
|
||||
unregister,
|
||||
collapse,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CollapseGroupContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollapseGroupProvider;
|
||||
47
src/context/SideBar/SideBarContext.tsx
Normal file
47
src/context/SideBar/SideBarContext.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
|
||||
interface ISideBarContext {
|
||||
/** State of side bar */
|
||||
state: 'open' | 'closed' | null;
|
||||
/** Toggle side bar state */
|
||||
toggleState: (() => void) | null;
|
||||
}
|
||||
|
||||
export const SideBarContext = React.createContext<ISideBarContext>({
|
||||
state: null,
|
||||
toggleState: null,
|
||||
});
|
||||
|
||||
export const useSideBar = (): ISideBarContext => {
|
||||
const { state, toggleState } = React.useContext(SideBarContext);
|
||||
|
||||
if (state !== null && toggleState !== null) {
|
||||
return {
|
||||
state,
|
||||
toggleState,
|
||||
};
|
||||
} else {
|
||||
throw new Error("Couldn't find context. Is your component inside a SidebarProvider?");
|
||||
}
|
||||
};
|
||||
|
||||
export const SideBarProvider: React.FunctionComponent = ({ children }: { children: React.ReactNode }) => {
|
||||
const [state, setState] = React.useState<'open' | 'closed'>('closed');
|
||||
|
||||
const toggleState = () => {
|
||||
setState(state == 'open' ? 'closed' : 'open');
|
||||
};
|
||||
|
||||
return (
|
||||
<SideBarContext.Provider
|
||||
value={{
|
||||
state,
|
||||
toggleState,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SideBarContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default SideBarProvider;
|
||||
4
src/definition/scss.d.ts
vendored
Normal file
4
src/definition/scss.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare module '*.scss' {
|
||||
const classNames: { [className: string]: string };
|
||||
export = classNames;
|
||||
}
|
||||
9
src/definition/transformers.d.ts
vendored
Normal file
9
src/definition/transformers.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
declare module 'url:*' {
|
||||
const asset: string;
|
||||
export = asset;
|
||||
}
|
||||
|
||||
declare module 'svg:*.svg' {
|
||||
const asset: string;
|
||||
export = asset;
|
||||
}
|
||||
33
src/index.html
Normal file
33
src/index.html
Normal file
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>GA Test</title>
|
||||
<style>
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
|
||||
sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable Javascript to run this application.</noscript>
|
||||
<div id="root">
|
||||
<!-- Your react app will be rendered here -->
|
||||
</div>
|
||||
<script src="../src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
11
src/index.tsx
Normal file
11
src/index.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import SideBarProvider from './context/SideBar/SideBarContext';
|
||||
|
||||
ReactDOM.render(
|
||||
<SideBarProvider>
|
||||
<App />
|
||||
</SideBarProvider>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
23
src/layout/MenuBar/MenuBar.scss
Normal file
23
src/layout/MenuBar/MenuBar.scss
Normal file
@ -0,0 +1,23 @@
|
||||
@import '../../assets/scss/variables';
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 1rem;
|
||||
height: $menubarHeight;
|
||||
background-color: $colDarkBlue;
|
||||
}
|
||||
|
||||
.navbar--fixed-top {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 1030;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
height: $menubarHeight;
|
||||
}
|
||||
25
src/layout/MenuBar/MenuBar.tsx
Normal file
25
src/layout/MenuBar/MenuBar.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { useSideBar } from '../../context/SideBar/SideBarContext';
|
||||
import Button from '../../lib/Button/Button';
|
||||
import useWindowDimensions from '../../util/useWindowDimensions';
|
||||
import classNames from './MenuBar.scss';
|
||||
|
||||
const MenuBar = (): React.ReactElement => {
|
||||
const { width } = useWindowDimensions();
|
||||
const { state, toggleState } = useSideBar();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (width > 768 && state == 'open') toggleState();
|
||||
}, [width]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav className={`${classNames.navbar} ${classNames['navbar--fixed-top']}`}>
|
||||
{width <= 768 && <Button color="navbar" onClick={toggleState} />}
|
||||
</nav>
|
||||
<div className={classNames.spacer}></div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuBar;
|
||||
79
src/layout/SideBar/MenuItem/MenuItem.scss
Normal file
79
src/layout/SideBar/MenuItem/MenuItem.scss
Normal file
@ -0,0 +1,79 @@
|
||||
@import '../../../assets/scss/variables';
|
||||
|
||||
.menu-item {
|
||||
line-height: 2.5rem;
|
||||
display: block;
|
||||
color: $colGray;
|
||||
text-decoration: none;
|
||||
|
||||
.name {
|
||||
text-indent: 1.25rem;
|
||||
height: 4.063rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
line-height: 3.75rem;
|
||||
|
||||
&--active {
|
||||
color: white;
|
||||
background-color: white;
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
&--subMenu {
|
||||
.icon {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
padding: 0.9375rem;
|
||||
transform: rotate(0deg);
|
||||
transition: 0.35s transform ease;
|
||||
float: right;
|
||||
|
||||
&--open {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&--subMenu .name:hover {
|
||||
color: white;
|
||||
background-color: white;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
&--noHover:hover {
|
||||
color: $colGray;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&--active {
|
||||
color: white;
|
||||
background-color: white;
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
& {
|
||||
text-indent: 1.875rem;
|
||||
}
|
||||
|
||||
& & {
|
||||
text-indent: 2.5rem;
|
||||
|
||||
.name {
|
||||
text-indent: 2.5rem;
|
||||
height: auto;
|
||||
line-height: 2.5rem;
|
||||
font-weight: normal;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
& & & {
|
||||
text-indent: 3.125rem;
|
||||
}
|
||||
}
|
||||
110
src/layout/SideBar/MenuItem/MenuItem.tsx
Normal file
110
src/layout/SideBar/MenuItem/MenuItem.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { Link, Location } from '@reach/router';
|
||||
import React from 'react';
|
||||
import arrow from 'svg:../../../assets/img/arrow.svg';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useCollapseGroup } from '../../../context/CollapseGroup/CollapseGroupContext';
|
||||
import Collapse from '../../../lib/Collapse/Collapse';
|
||||
import resolveClassName from '../../../util/resolveClassName';
|
||||
import classNames from './MenuItem.scss';
|
||||
|
||||
interface IMenuItemProps {
|
||||
/** Name of menu entry */
|
||||
name: React.ReactNode;
|
||||
/** Null for static, string to link, array of strings for submenu */
|
||||
to?: string | string[];
|
||||
/** True if matching for active shall match even with url parameters */
|
||||
hasParameters?: boolean;
|
||||
}
|
||||
|
||||
const MenuItem: React.FunctionComponent<IMenuItemProps> = ({
|
||||
name,
|
||||
to,
|
||||
hasParameters,
|
||||
children,
|
||||
}: React.PropsWithChildren<IMenuItemProps>) => {
|
||||
const classesMenuItem = {
|
||||
name: 'menu-item',
|
||||
modifiers: ['active', 'noHover', 'subMenu'],
|
||||
};
|
||||
const classesMenuName = {
|
||||
name: 'name',
|
||||
modifiers: ['active'],
|
||||
};
|
||||
const classesMenuIcon = {
|
||||
name: 'icon',
|
||||
modifiers: ['open'],
|
||||
};
|
||||
|
||||
const [subMenuOpen, setSubMenuOpen] = React.useState(false);
|
||||
const [ID] = React.useState(uuidv4());
|
||||
|
||||
// Conditional Hook calls are illegal
|
||||
// Since only an array for to creates a collapsible submenu, this is safe(ish)
|
||||
if (Array.isArray(to)) {
|
||||
try {
|
||||
//eslint-disable-next-line react-hooks/rules-of-hooks, no-var
|
||||
var { register, unregister, collapse } = useCollapseGroup();
|
||||
} catch {} //eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (register) {
|
||||
register(ID, () => {
|
||||
setSubMenuOpen(false);
|
||||
});
|
||||
|
||||
return () => unregister(ID);
|
||||
}
|
||||
}, []); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
React.useEffect(() => {
|
||||
if (collapse) {
|
||||
if (subMenuOpen && collapse) collapse(ID);
|
||||
}
|
||||
}, [subMenuOpen]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return to ? (
|
||||
Array.isArray(to) ? (
|
||||
<Location>
|
||||
{({ location }) => (
|
||||
<div className={resolveClassName(classNames, classesMenuItem, { noHover: true, subMenu: true })}>
|
||||
<div
|
||||
className={resolveClassName(classNames, classesMenuName, {
|
||||
active:
|
||||
to.findIndex((_to) => (hasParameters ? location.pathname.includes(_to) : _to == location.pathname)) !=
|
||||
-1,
|
||||
})}
|
||||
onClick={() => setSubMenuOpen(!subMenuOpen)}
|
||||
>
|
||||
{name}
|
||||
<img className={resolveClassName(classNames, classesMenuIcon, { open: subMenuOpen })} src={arrow} />
|
||||
</div>
|
||||
<Collapse id={ID} open={subMenuOpen}>
|
||||
{children}
|
||||
</Collapse>
|
||||
</div>
|
||||
)}
|
||||
</Location>
|
||||
) : (
|
||||
<Location>
|
||||
{({ location }) => (
|
||||
<Link
|
||||
to={to}
|
||||
className={resolveClassName(classNames, classesMenuItem, {
|
||||
active: hasParameters ? location.pathname.includes(to) : to == location.pathname,
|
||||
})}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
)}
|
||||
</Location>
|
||||
)
|
||||
) : (
|
||||
<div className={resolveClassName(classNames, classesMenuItem, { noHover: true })}>
|
||||
<div className={classNames.name}> {name}</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuItem;
|
||||
33
src/layout/SideBar/SideBar.scss
Normal file
33
src/layout/SideBar/SideBar.scss
Normal file
@ -0,0 +1,33 @@
|
||||
@import '../../assets/scss/variables';
|
||||
|
||||
.sidebar {
|
||||
background-color: $colDarkBlue;
|
||||
width: $sidebarWidth;
|
||||
min-height: calc(100vh - #{$menubarHeight});
|
||||
margin-left: -$sidebarWidth;
|
||||
flex-shrink: 0;
|
||||
|
||||
.memberData {
|
||||
width: 100%;
|
||||
padding-top: 1.25rem;
|
||||
text-align: center !important;
|
||||
|
||||
span {
|
||||
color: white;
|
||||
}
|
||||
h4 {
|
||||
color: white;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
font-size: 1.5rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $mediumScreen) {
|
||||
.sidebar {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
110
src/layout/SideBar/SideBar.tsx
Normal file
110
src/layout/SideBar/SideBar.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import CollapseGroupProvider from '../../context/CollapseGroup/CollapseGroupContext';
|
||||
import MemberImage from '../../lib/MemberImage/MemberImage';
|
||||
import Pill from '../../lib/Pill/Pill';
|
||||
import routes from '../../routes';
|
||||
import MenuItem from './MenuItem/MenuItem';
|
||||
import classNames from './SideBar.scss';
|
||||
|
||||
const SideBar = (): React.ReactElement => {
|
||||
return (
|
||||
<div className={classNames.sidebar}>
|
||||
<div className={classNames.memberData}>
|
||||
<MemberImage />
|
||||
<h4>VGA1582</h4>
|
||||
<span>Kilian Hofmann</span>
|
||||
</div>
|
||||
|
||||
<CollapseGroupProvider>
|
||||
<MenuItem
|
||||
name={
|
||||
<>
|
||||
Allgemein<Pill>4</Pill>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<MenuItem
|
||||
name="Unternehmen"
|
||||
to={[routes.company.home, routes.company.travel, routes.company.cargo, routes.company.civil]}
|
||||
>
|
||||
<MenuItem to={routes.company.home} name="Übersicht" />
|
||||
<MenuItem to={routes.company.travel} name="GA - Travel" />
|
||||
<MenuItem to={routes.company.cargo} name="GA - Cargo" />
|
||||
<MenuItem to={routes.company.civil} name="GA - Civil" />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
name="Community"
|
||||
to={[
|
||||
`${routes.community.base}${routes.community.magazine}`,
|
||||
`${routes.community.base}${routes.community.downloads}`,
|
||||
`${routes.community.base}${routes.community.ranking}`,
|
||||
`${routes.community.base}${routes.community.map}`,
|
||||
`${routes.community.base}${routes.community.news}`,
|
||||
`${routes.community.base}${routes.community.forum.base}`,
|
||||
`${routes.community.base}${routes.community.events}`,
|
||||
]}
|
||||
hasParameters
|
||||
>
|
||||
<MenuItem to={`${routes.community.base}${routes.community.magazine}`} name="Magazin" />
|
||||
<MenuItem to={`${routes.community.base}${routes.community.downloads}`} name="Downloads" />
|
||||
<MenuItem to={`${routes.community.base}${routes.community.ranking}`} name="Rangliste" />
|
||||
<MenuItem to={`${routes.community.base}${routes.community.map}`} name="Weltkarte" />
|
||||
<MenuItem to={`${routes.community.base}${routes.community.news}`} name="Nachrichten" />
|
||||
<MenuItem to={`${routes.community.base}${routes.community.forum.base}`} name="Forum" hasParameters />
|
||||
<MenuItem to={`${routes.community.base}${routes.community.events}`} name="Events" />
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem name="Flugbetrieb">
|
||||
<MenuItem to={`${routes.booking.base}${routes.booking.planner}`} name="Umlaufplanner" />
|
||||
<MenuItem
|
||||
name="Buchen"
|
||||
to={[
|
||||
`${routes.booking.base}${routes.booking.passenger}`,
|
||||
`${routes.booking.base}${routes.booking.charter}`,
|
||||
`${routes.booking.base}${routes.booking.cargo}`,
|
||||
`${routes.booking.base}${routes.booking.heritage}`,
|
||||
`${routes.booking.base}${routes.booking.visual}`,
|
||||
]}
|
||||
>
|
||||
<MenuItem to={`${routes.booking.base}${routes.booking.passenger}`} name="Passenger" />
|
||||
<MenuItem to={`${routes.booking.base}${routes.booking.charter}`} name="Charter" />
|
||||
<MenuItem to={`${routes.booking.base}${routes.booking.cargo}`} name="Cargo" />
|
||||
<MenuItem to={`${routes.booking.base}${routes.booking.heritage}`} name="Heritage" />
|
||||
<MenuItem to={`${routes.booking.base}${routes.booking.visual}`} name="Visual" />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
name="Briefing"
|
||||
to={[
|
||||
`${routes.briefing.base}${routes.briefing.passenger}`,
|
||||
`${routes.briefing.base}${routes.briefing.charter}`,
|
||||
`${routes.briefing.base}${routes.briefing.cargo}`,
|
||||
`${routes.briefing.base}${routes.briefing.heritage}`,
|
||||
`${routes.briefing.base}${routes.briefing.visual}`,
|
||||
]}
|
||||
>
|
||||
<MenuItem to={`${routes.briefing.base}${routes.briefing.passenger}`} name="Passenger" />
|
||||
<MenuItem to={`${routes.briefing.base}${routes.briefing.charter}`} name="Charter" />
|
||||
<MenuItem to={`${routes.briefing.base}${routes.briefing.cargo}`} name="Cargo" />
|
||||
<MenuItem to={`${routes.briefing.base}${routes.briefing.heritage}`} name="Heritage" />
|
||||
<MenuItem to={`${routes.briefing.base}${routes.briefing.visual}`} name="Visual" />
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem name="Flugschule">
|
||||
<MenuItem to={routes.school.base} name="Übersicht" />
|
||||
<MenuItem to={[`${routes.school.base}${routes.school.documents}`]} name="Ausbildung">
|
||||
<MenuItem to={`${routes.school.base}${routes.school.documents}`} name="Unterlagen" />
|
||||
</MenuItem>
|
||||
<MenuItem to={`${routes.school.base}${routes.school.exam.rank}`} name="Rangprüfung" />
|
||||
<MenuItem to={`${routes.school.base}${routes.school.exam.typerating}`} name="Typerating" />
|
||||
</MenuItem>
|
||||
<MenuItem name="Persönliches">
|
||||
<MenuItem to={`${routes.personal.base}${routes.personal.logbook}`} name="Logbuch" />
|
||||
<MenuItem to={`${routes.personal.base}${routes.personal.profile.base}`} name="Profil" />
|
||||
</MenuItem>
|
||||
</CollapseGroupProvider>
|
||||
<div style={{ backgroundColor: 'red' }}>END OF BAR</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SideBar;
|
||||
93
src/lib/Button/Button.scss
Normal file
93
src/lib/Button/Button.scss
Normal file
@ -0,0 +1,93 @@
|
||||
@import '../../assets/scss/variables';
|
||||
|
||||
.button {
|
||||
&:not(:disabled),
|
||||
[type='button']:not(:disabled),
|
||||
[type='reset']:not(:disabled),
|
||||
[type='submit']:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
border-radius: 0.25rem;
|
||||
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
|
||||
box-shadow 0.15s ease-in-out;
|
||||
|
||||
background-color: $colDarkBlue;
|
||||
border-color: $colDarkBlue;
|
||||
color: white;
|
||||
margin: 0.3125rem;
|
||||
|
||||
&:hover {
|
||||
background-color: white;
|
||||
border-color: $colDarkBlue;
|
||||
color: $colDarkBlue;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #0062cc;
|
||||
border-color: #005cbf;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
&--danger {
|
||||
background-color: #dc3545;
|
||||
border-color: #dc3545;
|
||||
|
||||
&:hover {
|
||||
background-color: #c82333;
|
||||
border-color: #bd2130;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #bd2130;
|
||||
border-color: #b21f2d;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.65;
|
||||
}
|
||||
}
|
||||
|
||||
&--navbar {
|
||||
margin: 0;
|
||||
padding: 0.25rem 0.75rem;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.25rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.toggler {
|
||||
display: inline-block;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
vertical-align: middle;
|
||||
content: '';
|
||||
background: no-repeat center center;
|
||||
background-size: 100% 100%;
|
||||
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/lib/Button/Button.tsx
Normal file
24
src/lib/Button/Button.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import resolveClassName from '../../util/resolveClassName';
|
||||
import classNames from './Button.scss';
|
||||
|
||||
interface IButtonProps
|
||||
extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
|
||||
/** Button color */
|
||||
color?: 'danger' | 'navbar';
|
||||
}
|
||||
|
||||
const Button: React.FunctionComponent<IButtonProps> = ({ color, children, ...props }: IButtonProps) => {
|
||||
const classes = {
|
||||
name: 'button',
|
||||
modifiers: ['color'],
|
||||
};
|
||||
|
||||
return (
|
||||
<button {...props} className={resolveClassName(classNames, classes, { color: color })}>
|
||||
{color == 'navbar' ? <span className={classNames.toggler}></span> : children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
5
src/lib/Collapse/Collapse.scss
Normal file
5
src/lib/Collapse/Collapse.scss
Normal file
@ -0,0 +1,5 @@
|
||||
.collapse {
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
transition: height 0.35s ease;
|
||||
}
|
||||
35
src/lib/Collapse/Collapse.tsx
Normal file
35
src/lib/Collapse/Collapse.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import classNames from './Collapse.scss';
|
||||
|
||||
interface ICollapseProps {
|
||||
/** True if collapse is open */
|
||||
open: boolean;
|
||||
/** Optional ID for use with collapse groups, if null internal ID will be generated */
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const Collapse: React.FunctionComponent<React.PropsWithChildren<ICollapseProps>> = ({
|
||||
open,
|
||||
id,
|
||||
children,
|
||||
}: React.PropsWithChildren<ICollapseProps>) => {
|
||||
const [ID] = React.useState(uuidv4());
|
||||
const [height, setHeight] = React.useState(0);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
setHeight(
|
||||
Array.from(document.getElementById(id ?? ID).children)
|
||||
.map((child) => child.clientHeight)
|
||||
.reduce((acc, val) => (acc += val))
|
||||
);
|
||||
}, []); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<div id={id ?? ID} className={classNames.collapse} style={{ height: open ? height : 0 }}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Collapse;
|
||||
14
src/lib/MemberImage/MemberImage.scss
Normal file
14
src/lib/MemberImage/MemberImage.scss
Normal file
@ -0,0 +1,14 @@
|
||||
.image {
|
||||
width: 6.25rem;
|
||||
height: 6.25rem;
|
||||
margin: auto;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
border: 2px solid white;
|
||||
border-radius: 6.25rem;
|
||||
|
||||
&--none {
|
||||
background-size: 4.5rem;
|
||||
}
|
||||
}
|
||||
20
src/lib/MemberImage/MemberImage.tsx
Normal file
20
src/lib/MemberImage/MemberImage.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import pilot from 'svg:../../assets/img/pilot.svg';
|
||||
import resolveClassName from '../../util/resolveClassName';
|
||||
import classNames from './MemberImage.scss';
|
||||
|
||||
const MemberImage = ({ image }: { image?: string }): React.ReactElement => {
|
||||
const classes = {
|
||||
name: 'image',
|
||||
modifiers: ['none'],
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={resolveClassName(classNames, classes, { none: image == null })}
|
||||
style={{ backgroundImage: `url('${image ?? pilot}')` }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default MemberImage;
|
||||
16
src/lib/Pill/Pill.scss
Normal file
16
src/lib/Pill/Pill.scss
Normal file
@ -0,0 +1,16 @@
|
||||
@import '../../assets/scss/variables';
|
||||
|
||||
.pill {
|
||||
padding: 0.25em 0.6em 0.25em 0.6em;
|
||||
font-size: 75%;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: 10rem;
|
||||
color: $colDarkBlue;
|
||||
background-color: white;
|
||||
display: inline;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
8
src/lib/Pill/Pill.tsx
Normal file
8
src/lib/Pill/Pill.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import classNames from './Pill.scss';
|
||||
|
||||
const Pill: React.FunctionComponent = ({ children }: { children: React.ReactNode }) => {
|
||||
return <div className={classNames.pill}>{children}</div>;
|
||||
};
|
||||
|
||||
export default Pill;
|
||||
120
src/lib/SplitFlapBoard/SplitFlapBoard.scss
Normal file
120
src/lib/SplitFlapBoard/SplitFlapBoard.scss
Normal file
@ -0,0 +1,120 @@
|
||||
@import '../../assets/scss/variables';
|
||||
|
||||
.split-flap {
|
||||
margin: 1vw;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
font-size: 40px;
|
||||
text-align: center;
|
||||
font-family: monospace;
|
||||
perspective: 100px;
|
||||
color: white;
|
||||
|
||||
.top-next {
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
background-color: $colLightBlue;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
.top-current {
|
||||
margin-top: -50%;
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
background-color: $colLightBlue;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
border-radius: 5px 5px 0 0;
|
||||
transform-origin: bottom;
|
||||
transform: rotateX(0deg);
|
||||
|
||||
&--animate {
|
||||
animation: flipUp ease-in 1;
|
||||
animation-duration: 2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&--stop {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-current {
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
background-image: linear-gradient(180deg, $colLightBlue, $colDarkBlue);
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
border-radius: 0 0 5px 5px;
|
||||
|
||||
span {
|
||||
transform: translateY(-50%);
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-next {
|
||||
margin-top: -50%;
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
background-image: linear-gradient(180deg, $colLightBlue, $colDarkBlue);
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
border-radius: 0 0 5px 5px;
|
||||
transform-origin: top;
|
||||
transform: rotateX(90deg);
|
||||
|
||||
span {
|
||||
transform: translateY(-50%);
|
||||
display: block;
|
||||
}
|
||||
|
||||
&--animate {
|
||||
animation: flipDown ease-out 1;
|
||||
animation-duration: 2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&--stop {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flipDown {
|
||||
0% {
|
||||
transform: rotateX(90deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotateX(90deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flipUp {
|
||||
0% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotateX(-90deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(-90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: #111111;
|
||||
border: solid 5px black;
|
||||
}
|
||||
177
src/lib/SplitFlapBoard/SplitFlapBoard.tsx
Normal file
177
src/lib/SplitFlapBoard/SplitFlapBoard.tsx
Normal file
@ -0,0 +1,177 @@
|
||||
import React from 'react';
|
||||
import resolveClassName from '../../util/resolveClassName';
|
||||
import classNames from './SplitFlapBoard.scss';
|
||||
|
||||
interface ISplitFlapBoardProps {
|
||||
/** Message the split flap board shall display */
|
||||
message: string;
|
||||
/** True to start the animation */
|
||||
start: boolean;
|
||||
/** Size of one split flap module, default of 40px */
|
||||
size?: number;
|
||||
/** Duration of one transition animation */
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
const SplitFlapBoard: React.FunctionComponent<ISplitFlapBoardProps> = ({
|
||||
message,
|
||||
start,
|
||||
size,
|
||||
duration,
|
||||
}: ISplitFlapBoardProps) => {
|
||||
const classesMain = {
|
||||
name: 'split-flap',
|
||||
modifiers: [],
|
||||
};
|
||||
const classesTopNext = {
|
||||
name: 'top-next',
|
||||
modifiers: [],
|
||||
};
|
||||
const classesTopCurrent = {
|
||||
name: 'top-current',
|
||||
modifiers: ['stop', 'animate'],
|
||||
};
|
||||
const classesBottomCurrent = {
|
||||
name: 'bottom-current',
|
||||
modifiers: [],
|
||||
};
|
||||
const classesBottomNext = {
|
||||
name: 'bottom-next',
|
||||
modifiers: ['stop', 'animate'],
|
||||
};
|
||||
|
||||
const typeSet = [
|
||||
' ',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'I',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'M',
|
||||
'N',
|
||||
'O',
|
||||
'P',
|
||||
'Q',
|
||||
'R',
|
||||
'S',
|
||||
'T',
|
||||
'U',
|
||||
'V',
|
||||
'W',
|
||||
'X',
|
||||
'Y',
|
||||
'Z',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
'8',
|
||||
'9',
|
||||
'0',
|
||||
];
|
||||
|
||||
const [animate, setAnimate] = React.useState(false);
|
||||
const currentCharacter = React.useRef<number[]>(Array(message.length).fill(0));
|
||||
const nextCharacter = React.useRef<number[]>(Array(message.length).fill(1));
|
||||
const flapStop = React.useRef<boolean[]>(Array(message.length).fill(false));
|
||||
const timeout = React.useRef<NodeJS.Timeout>();
|
||||
|
||||
const animation = () => {
|
||||
for (let i = 0; i < currentCharacter.current.length; i++) {
|
||||
if (typeSet[currentCharacter.current[i]] !== message.split('')[i]) {
|
||||
currentCharacter.current[i] = nextCharacter.current[i];
|
||||
nextCharacter.current[i] = (nextCharacter.current[i] % 36) + 1;
|
||||
if (typeSet[currentCharacter.current[i]] === message.split('')[i]) {
|
||||
flapStop.current[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setAnimate(false);
|
||||
|
||||
if (!flapStop.current.reduce((acc, val) => acc && val)) {
|
||||
timeout.current = setTimeout(() => {
|
||||
setAnimate(true);
|
||||
timeout.current = setTimeout(animation, duration ? duration * 1000 : 2000);
|
||||
}, Math.max(25, Math.random() * 100));
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (start) {
|
||||
setAnimate(true);
|
||||
timeout.current = setTimeout(animation, duration ? duration * 1000 : 2000);
|
||||
}
|
||||
}, [start]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => clearTimeout(timeout.current);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={classNames.container}>
|
||||
{message.split('').map((char, index) => {
|
||||
return (
|
||||
<div
|
||||
key={`${char}-${index}`}
|
||||
className={resolveClassName(classNames, classesMain, {})}
|
||||
style={{
|
||||
width: size ?? null,
|
||||
height: size ?? null,
|
||||
lineHeight: size ? `${size}px` : null,
|
||||
fontSize: size ?? null,
|
||||
perspective: size ? size * 10 : null,
|
||||
}}
|
||||
>
|
||||
{/* TOP NEXT CHAR */}
|
||||
<div
|
||||
className={resolveClassName(classNames, classesTopNext, {})}
|
||||
style={{ borderRadius: size ? `${size / 8}px ${size / 8}px 0 0` : null }}
|
||||
>
|
||||
{typeSet[nextCharacter.current[index]]}
|
||||
</div>
|
||||
{/* TOP CUR CHAR */}
|
||||
<div
|
||||
className={resolveClassName(classNames, classesTopCurrent, { animate, stop: flapStop.current[index] })}
|
||||
style={{
|
||||
animationDuration: `${duration}s` ?? null,
|
||||
borderRadius: size ? `${size / 8}px ${size / 8}px 0 0` : null,
|
||||
}}
|
||||
>
|
||||
{typeSet[currentCharacter.current[index]]}
|
||||
</div>
|
||||
{/* BOTTOM CUR CHAR */}
|
||||
<div
|
||||
className={resolveClassName(classNames, classesBottomCurrent, {})}
|
||||
style={{ borderRadius: size ? `0 0 ${size / 8}px ${size / 8}px` : null }}
|
||||
>
|
||||
<span>{typeSet[currentCharacter.current[index]]}</span>
|
||||
</div>
|
||||
{/* BOTTOM NEXT CHAR */}
|
||||
<div
|
||||
className={resolveClassName(classNames, classesBottomNext, { animate, stop: flapStop.current[index] })}
|
||||
style={{
|
||||
animationDuration: `${duration}s` ?? null,
|
||||
borderRadius: size ? `0 0 ${size / 8}px ${size / 8}px` : null,
|
||||
}}
|
||||
>
|
||||
<span>{typeSet[nextCharacter.current[index]]}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SplitFlapBoard;
|
||||
8
src/pages/HomePage/HomePage.tsx
Normal file
8
src/pages/HomePage/HomePage.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import React from 'react';
|
||||
|
||||
const HomePage: React.FunctionComponent<RouteComponentProps> = () => {
|
||||
return <>Home</>;
|
||||
};
|
||||
|
||||
export default HomePage;
|
||||
21
src/pages/NotFound/NotFoundPage.tsx
Normal file
21
src/pages/NotFound/NotFoundPage.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { RouteComponentProps, useLocation } from '@reach/router';
|
||||
import React from 'react';
|
||||
import SplitFlapBoard from '../../lib/SplitFlapBoard/SplitFlapBoard';
|
||||
import useWindowDimensions from '../../util/useWindowDimensions';
|
||||
|
||||
const NotFoundPage: React.FunctionComponent<RouteComponentProps> = () => {
|
||||
const { pathname } = useLocation();
|
||||
const { width } = useWindowDimensions();
|
||||
const vw1 = (2 * width) / 100;
|
||||
const size = width > 768 ? (width - 300 - 8 * vw1) / 8 : (width - 50 - 8 * vw1) / 8;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SplitFlapBoard key={`${pathname}1`} message="SEITE" duration={0.15} size={size} start />
|
||||
<SplitFlapBoard key={`${pathname}2`} message="NICHT" duration={0.15} size={size} start />
|
||||
<SplitFlapBoard key={`${pathname}3`} message="GEFUNDEN" duration={0.15} size={size} start />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFoundPage;
|
||||
8
src/pages/user/LogbookPage/LogbookPage.tsx
Normal file
8
src/pages/user/LogbookPage/LogbookPage.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import React from 'react';
|
||||
|
||||
const LogbookPage: React.FunctionComponent<RouteComponentProps> = () => {
|
||||
return <>Logbook</>;
|
||||
};
|
||||
|
||||
export default LogbookPage;
|
||||
21
src/pages/user/ProfilePage/ProfilePage.tsx
Normal file
21
src/pages/user/ProfilePage/ProfilePage.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import React from 'react';
|
||||
import useParams from '../../../util/useParams';
|
||||
|
||||
interface IProfilePageProps {
|
||||
/** Null if own profile, otherwise ID of member whose profile shall be displayed */
|
||||
memberID?: number;
|
||||
}
|
||||
|
||||
const ProfilePage: React.FunctionComponent<RouteComponentProps> = ({
|
||||
children,
|
||||
}: React.PropsWithChildren<RouteComponentProps>) => {
|
||||
const { memberID } = useParams<IProfilePageProps>();
|
||||
return (
|
||||
<>
|
||||
Hey {memberID ?? 'SELF'} {children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfilePage;
|
||||
10
src/pages/user/ProfilePages.tsx
Normal file
10
src/pages/user/ProfilePages.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import React from 'react';
|
||||
|
||||
const ProfilePages: React.FunctionComponent<RouteComponentProps> = ({
|
||||
children,
|
||||
}: React.PropsWithChildren<RouteComponentProps>) => {
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default ProfilePages;
|
||||
10
src/pages/user/UserPages.tsx
Normal file
10
src/pages/user/UserPages.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import React from 'react';
|
||||
|
||||
const UserPages: React.FunctionComponent<RouteComponentProps> = ({
|
||||
children,
|
||||
}: React.PropsWithChildren<RouteComponentProps>) => {
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default UserPages;
|
||||
63
src/routes.ts
Normal file
63
src/routes.ts
Normal file
@ -0,0 +1,63 @@
|
||||
const routes = {
|
||||
company: {
|
||||
home: '/',
|
||||
travel: '/travel',
|
||||
cargo: '/cargo',
|
||||
civil: '/civil',
|
||||
},
|
||||
|
||||
community: {
|
||||
base: '/community',
|
||||
magazine: '/magazine',
|
||||
downloads: '/downloads',
|
||||
ranking: '/ranking',
|
||||
map: '/map',
|
||||
news: '/news',
|
||||
forum: {
|
||||
base: '/forum',
|
||||
board: ':board',
|
||||
thread: ':thread',
|
||||
},
|
||||
events: '/events',
|
||||
},
|
||||
|
||||
booking: {
|
||||
base: '/booking',
|
||||
planner: '/planner',
|
||||
passenger: '/passenger',
|
||||
charter: '/charter',
|
||||
cargo: '/cargo',
|
||||
heritage: '/heritage',
|
||||
visual: '/visual',
|
||||
},
|
||||
briefing: {
|
||||
base: '/briefing',
|
||||
passenger: '/passenger',
|
||||
charter: '/charter',
|
||||
cargo: '/cargo',
|
||||
heritage: '/heritage',
|
||||
visual: '/visual',
|
||||
},
|
||||
|
||||
school: {
|
||||
base: '/schooling',
|
||||
documents: '/documents',
|
||||
|
||||
exam: {
|
||||
rank: '/rankExam',
|
||||
typerating: '/typeratingExam',
|
||||
},
|
||||
},
|
||||
|
||||
personal: {
|
||||
base: '/personal',
|
||||
logbook: '/logbook',
|
||||
profile: {
|
||||
base: '/profile',
|
||||
others: ':memberID',
|
||||
generate: (id: string): string => `/personal/profile/${id}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default routes;
|
||||
24
src/util/OnRouteChange.tsx
Normal file
24
src/util/OnRouteChange.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { Location, WindowLocation } from '@reach/router';
|
||||
import React from 'react';
|
||||
|
||||
interface IOnRouteChangeWorkerProps {
|
||||
/** Current router location */
|
||||
location: WindowLocation;
|
||||
}
|
||||
|
||||
const OnRouteChangeWorker: React.FunctionComponent<IOnRouteChangeWorkerProps> = () => {
|
||||
React.useLayoutEffect(() => {
|
||||
if (window.scrollY != 0) {
|
||||
window.scrollTo(0, 0);
|
||||
} else {
|
||||
window.scrollTo(0, 1);
|
||||
}
|
||||
}, [location.pathname]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
return null;
|
||||
};
|
||||
|
||||
const OnRouteChange = (): React.ReactElement => (
|
||||
<Location>{({ location }) => <OnRouteChangeWorker location={location} />}</Location>
|
||||
);
|
||||
|
||||
export default OnRouteChange;
|
||||
14
src/util/resolveClassName.ts
Normal file
14
src/util/resolveClassName.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import cx from 'bem-classnames';
|
||||
|
||||
const resolveClassName = (
|
||||
classNames: { [name: string]: string },
|
||||
classes: Record<string, unknown>,
|
||||
bemState: Record<string, unknown>
|
||||
): string => {
|
||||
return cx(classes, bemState)
|
||||
.split(' ')
|
||||
.map((cn) => classNames[cn])
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
export default resolveClassName;
|
||||
7
src/util/useParams.ts
Normal file
7
src/util/useParams.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { useParams as useParamsReach } from '@reach/router';
|
||||
|
||||
const useParams = <T extends unknown>(): T => {
|
||||
return useParamsReach() as T;
|
||||
};
|
||||
|
||||
export default useParams;
|
||||
29
src/util/useWindowDimensions.ts
Normal file
29
src/util/useWindowDimensions.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const getWindowDimensions = () => {
|
||||
const { innerWidth: width, innerHeight: height } = window;
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
};
|
||||
};
|
||||
|
||||
const useWindowDimensions = (): {
|
||||
width: number;
|
||||
height: number;
|
||||
} => {
|
||||
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setWindowDimensions(getWindowDimensions());
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
return windowDimensions;
|
||||
};
|
||||
|
||||
export default useWindowDimensions;
|
||||
41
tsconfig.json
Normal file
41
tsconfig.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Specify ECMAScript target version */
|
||||
"target": "es6",
|
||||
/* Specify module code generation */
|
||||
"module": "esnext",
|
||||
/* Specify library files to be included in the compilation. */
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM"
|
||||
],
|
||||
"experimentalDecorators": true,
|
||||
/* Specify JSX code generation */
|
||||
"jsx": "react",
|
||||
/* Generate corresponding .map files */
|
||||
"sourceMap": true,
|
||||
/* Enable all strict type-checking options */
|
||||
"strict": false,
|
||||
/* Specify module resolution strategy */
|
||||
"moduleResolution": "node",
|
||||
/* Base directory to resolve non-absolute module names */
|
||||
/*"baseUrl": "./",*/
|
||||
/* Maps imports to locations - e.g. ~models will go to ./src/models */
|
||||
/*"paths": {
|
||||
"*": ["./*"]
|
||||
},*/
|
||||
/* List of folders to include type definitions from */
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
/* allow import React instead of import * as React */
|
||||
"allowSyntheticDefaultImports": true,
|
||||
/* Emit interop between CommonJS and ES modules */
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["./src/**/*", "./*.d.ts"],
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"./node_modules/**/*.ts"
|
||||
]
|
||||
}
|
||||
11
workspace.code-workspace
Normal file
11
workspace.code-workspace
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user