Complete english version of site
This commit is contained in:
77
README.md
77
README.md
@@ -1,75 +1,2 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## React Compiler
|
||||
|
||||
The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information.
|
||||
|
||||
Note: This will impact Vite dev & build performances.
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default defineConfig([
|
||||
globalIgnores(["dist"]),
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
// Other configs...
|
||||
|
||||
// Remove tseslint.configs.recommended and replace with this
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
tseslint.configs.stylisticTypeChecked,
|
||||
|
||||
// Other configs...
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.ts
|
||||
import reactX from "eslint-plugin-react-x";
|
||||
import reactDom from "eslint-plugin-react-dom";
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(["dist"]),
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
// Other configs...
|
||||
// Enable lint rules for React
|
||||
reactX.configs["recommended-typescript"],
|
||||
// Enable lint rules for React DOM
|
||||
reactDom.configs.recommended,
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
# Hi
|
||||
Nice of you to check out the source of the website. Hopefully you enjoy what you see!
|
||||
BIN
assets/Squagon.pdf
Normal file
BIN
assets/Squagon.pdf
Normal file
Binary file not shown.
@@ -1,18 +1,18 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import js from '@eslint/js';
|
||||
import globals from 'globals';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import { defineConfig, globalIgnores } from 'eslint/config';
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(["dist"]),
|
||||
globalIgnores(['dist', 'node_modules']),
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs["recommended-latest"],
|
||||
reactHooks.configs['recommended-latest'],
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="stylesheet" href="/src/App.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>cblt-website</title>
|
||||
<title>Wouter van Veelen</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="background"></div>
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -2412,7 +2412,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "cblt-website",
|
||||
"name": "webhome",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -81,3 +81,10 @@ body {
|
||||
min-width: 100%;
|
||||
z-index: -100;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
+ p {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import { Content } from './layout/Content.tsx';
|
||||
import { Home } from './pages/Home.tsx';
|
||||
import { About } from './pages/About.tsx';
|
||||
import { Career } from './pages/Career.tsx';
|
||||
import { Storytelling } from './pages/Storytelling.tsx';
|
||||
import { Puzzles } from './pages/Puzzles.tsx';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -15,6 +17,8 @@ function App() {
|
||||
<Routes>
|
||||
<Route index path='home' element={<Home />} />
|
||||
<Route path='career' element={<Career />} />
|
||||
<Route path='storytelling' element={<Storytelling />} />
|
||||
<Route path='puzzles' element={<Puzzles />} />
|
||||
<Route path='about' element={<About />} />
|
||||
</Routes>
|
||||
</Content>
|
||||
|
||||
35
src/components/CareerEntry.tsx
Normal file
35
src/components/CareerEntry.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { type ReactElement, type ReactNode } from 'react';
|
||||
import { CollapsableCard, type CollapsableCardProps } from './CollapsableCard.tsx';
|
||||
import {
|
||||
TimelineConnector,
|
||||
TimelineContent,
|
||||
TimelineDot,
|
||||
TimelineItem,
|
||||
TimelineOppositeContent,
|
||||
TimelineSeparator,
|
||||
} from '@mui/lab';
|
||||
|
||||
export type CareerEntryProps = CollapsableCardProps & {
|
||||
start: ReactNode;
|
||||
finalEntry?: boolean;
|
||||
};
|
||||
export function CareerEntry({
|
||||
finalEntry,
|
||||
startCollapsed = true,
|
||||
variant = 'outlined',
|
||||
start,
|
||||
...props
|
||||
}: CareerEntryProps): ReactElement {
|
||||
return (
|
||||
<TimelineItem>
|
||||
<TimelineOppositeContent color='textPrimary'>{start}</TimelineOppositeContent>
|
||||
<TimelineSeparator>
|
||||
<TimelineDot />
|
||||
{!finalEntry && <TimelineConnector />}
|
||||
</TimelineSeparator>
|
||||
<TimelineContent color='textPrimary'>
|
||||
<CollapsableCard startCollapsed={startCollapsed} variant={variant} {...props} />
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ export function CollapsableCard({
|
||||
startCollapsed,
|
||||
...props
|
||||
}: CollapsableCardProps): ReactElement {
|
||||
const hasContent = children != null;
|
||||
const [collapsed, setCollapsed] = useState(startCollapsed ?? false);
|
||||
const toggleCollapse = () => setCollapsed((oldState) => !oldState);
|
||||
|
||||
@@ -26,16 +27,22 @@ export function CollapsableCard({
|
||||
title={header}
|
||||
subheader={subheader}
|
||||
action={
|
||||
hasContent ?
|
||||
<IconButton onClick={toggleCollapse}>
|
||||
{collapsed ?
|
||||
<ArrowDownward />
|
||||
: <ArrowUpward />}
|
||||
</IconButton>
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Collapse in={!collapsed}>
|
||||
<CardContent>{children}</CardContent>
|
||||
{hasContent && (
|
||||
<CardContent sx={{ fontSize: '14px', textAlign: 'left' }}>
|
||||
{children}
|
||||
</CardContent>
|
||||
)}
|
||||
</Collapse>
|
||||
</Card>
|
||||
);
|
||||
|
||||
12
src/components/OutsideLink.tsx
Normal file
12
src/components/OutsideLink.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { OpenInNewOutlined } from '@mui/icons-material';
|
||||
import { Link, type LinkProps } from '@mui/material';
|
||||
import type { ReactElement } from 'react';
|
||||
|
||||
export function OutsideLink({ children, target = '_blank', ...props }: LinkProps): ReactElement {
|
||||
return (
|
||||
<Link {...props} target={target}>
|
||||
{children}
|
||||
<OpenInNewOutlined fontSize='inherit' />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,14 @@ export type ContentProps = PropsWithChildren;
|
||||
|
||||
export function Content({ children }: ContentProps): ReactElement {
|
||||
return (
|
||||
<Box sx={{ width: { xs: '100%', md: '80%' }, margin: 'auto' }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: { xs: '100%', md: '80%' },
|
||||
margin: 'auto',
|
||||
height: 'calc(100vh - 25px)',
|
||||
color: (theme) => theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
<TopBar />
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
@@ -2,7 +2,13 @@ import React, { type ReactElement } from 'react';
|
||||
import { Box, Tab, Tabs } from '@mui/material';
|
||||
import { useLocation, useNavigate } from 'react-router';
|
||||
|
||||
const ValidTabs = ['home', 'career', 'about'] as const satisfies string[];
|
||||
const ValidTabs = [
|
||||
'home',
|
||||
'career',
|
||||
'storytelling',
|
||||
'puzzles',
|
||||
'about',
|
||||
] as const satisfies string[];
|
||||
|
||||
export function TopBar(): ReactElement {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
import { Fragment, type ReactElement } from 'react';
|
||||
|
||||
export function About(): ReactElement {
|
||||
return <Fragment>Hello, About.</Fragment>;
|
||||
return (
|
||||
<Fragment>
|
||||
<p />
|
||||
<p>
|
||||
This website is by and about me, Wouter van Veelen. The reason for having this
|
||||
website is three-fold. First, I want to have a place where I can experiment with web
|
||||
techniques and frameworks. Secondly, I want a place where I can publicly share
|
||||
creative works. Third, I wanted to delete my LinkedIn, without becoming completely
|
||||
unfindable for potential employers or collaborators.
|
||||
</p>
|
||||
<p>
|
||||
About the technical specs, this website built using Vite + React (compiler) and
|
||||
served by an Apache2 web server running on a self-built Arch-linux server. The
|
||||
storage on the server runs on zfs in a 3+1 HDD configuration with an NVME boot
|
||||
drive.
|
||||
</p>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,43 +1,153 @@
|
||||
import {
|
||||
Timeline,
|
||||
TimelineConnector,
|
||||
TimelineContent,
|
||||
TimelineDot,
|
||||
TimelineItem,
|
||||
TimelineOppositeContent,
|
||||
TimelineSeparator,
|
||||
} from '@mui/lab';
|
||||
import type { ReactElement } from 'react';
|
||||
import { CollapsableCard } from '../components/CollapsableCard.tsx';
|
||||
import { Timeline } from '@mui/lab';
|
||||
import { Fragment, type ReactElement } from 'react';
|
||||
import { Switch, Typography } from '@mui/material';
|
||||
import { useToggle } from '../utils.ts';
|
||||
import { OutsideLink } from '../components/OutsideLink.tsx';
|
||||
import { CareerEntry } from '../components/CareerEntry.tsx';
|
||||
|
||||
export function Career(): ReactElement {
|
||||
const [full, toggleFull] = useToggle(true);
|
||||
return (
|
||||
<Timeline position='alternate'>
|
||||
<TimelineItem>
|
||||
<TimelineOppositeContent color='textPrimary'>2014</TimelineOppositeContent>
|
||||
<TimelineSeparator>
|
||||
<TimelineDot />
|
||||
<TimelineConnector />
|
||||
</TimelineSeparator>
|
||||
<TimelineContent color='textPrimary'>
|
||||
<CollapsableCard
|
||||
startCollapsed
|
||||
variant='outlined'
|
||||
header='BSc. Computer Science'
|
||||
subheader='University of Twente.'
|
||||
<Fragment>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
Test
|
||||
</CollapsableCard>
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
<TimelineItem>
|
||||
<TimelineOppositeContent color='textPrimary'>10:00 am</TimelineOppositeContent>
|
||||
<TimelineSeparator>
|
||||
<TimelineDot />
|
||||
<TimelineConnector />
|
||||
</TimelineSeparator>
|
||||
<TimelineContent color='textPrimary'>Code</TimelineContent>
|
||||
</TimelineItem>
|
||||
<div style={{ width: '100%' }} />
|
||||
<Switch checked={full} onChange={toggleFull} />
|
||||
<div style={{ width: '100%' }}>
|
||||
<Typography color='textPrimary'>Include activism and volunteer work</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<Timeline position='alternate'>
|
||||
<CareerEntry
|
||||
key='bsc'
|
||||
start='Sep. 2014'
|
||||
header='BSc. Computer Science'
|
||||
subheader='Until 2018, University of Twente.'
|
||||
/>
|
||||
|
||||
{full && (
|
||||
<CareerEntry
|
||||
key='iapc'
|
||||
start='Oct. 2014'
|
||||
header='Volunteer and Board Member'
|
||||
subheader={
|
||||
<Fragment>
|
||||
Until 2018, <OutsideLink href='iapc.nl'>IAPC</OutsideLink>
|
||||
</Fragment>
|
||||
}
|
||||
>
|
||||
<p>
|
||||
<OutsideLink href='iapc.nl'>IAPC</OutsideLink> is a computer parts and
|
||||
service store completely run by volunteers on the campus of the
|
||||
Universtiy of Twente.
|
||||
</p>
|
||||
<p>
|
||||
During my active years I helped in the committees for logistics, RMA and
|
||||
PR, as well as being board member for logistics in 2015-2016.
|
||||
</p>
|
||||
</CareerEntry>
|
||||
)}
|
||||
{full && (
|
||||
<CareerEntry
|
||||
key='stretchers'
|
||||
start='Oct. 2015'
|
||||
header='Trainer, Volunteer and Board Member'
|
||||
subheader={
|
||||
<Fragment>
|
||||
Until 2023,{' '}
|
||||
<OutsideLink href='https://stretchers.nl'>
|
||||
D.B.V. de Stretchers
|
||||
</OutsideLink>
|
||||
</Fragment>
|
||||
}
|
||||
>
|
||||
<p>
|
||||
<OutsideLink href='https://stretchers.nl'>
|
||||
D.B.V. de Stretchers
|
||||
</OutsideLink>{' '}
|
||||
is a sports association on the University of Twente where all sports are
|
||||
practiced and the focus is on fun, learning and activities
|
||||
</p>
|
||||
<p>
|
||||
During my active years I was a trainer and assisted in the events
|
||||
committee as well as PR and the website. During the academic year
|
||||
2020-2021 I was the chairman of the association
|
||||
</p>
|
||||
</CareerEntry>
|
||||
)}
|
||||
<CareerEntry
|
||||
key='msc'
|
||||
start='Sep. 2018'
|
||||
header='Master Data Science'
|
||||
subheader='Unfinished, Until 2021, University of Twente.'
|
||||
>
|
||||
<p>
|
||||
With courses focused around algorithms, machine learning and data
|
||||
processing.
|
||||
</p>
|
||||
<p>
|
||||
Completed all required courses, but did not complete the final thesis
|
||||
because of personal reasons.
|
||||
</p>
|
||||
</CareerEntry>
|
||||
{full && (
|
||||
<CareerEntry
|
||||
key='muziekbank'
|
||||
start='Feb. 2022'
|
||||
header='Volunteer Muziekbank'
|
||||
subheader='Until 2023, Muziekbank Enschede'
|
||||
>
|
||||
<p>
|
||||
The{' '}
|
||||
<OutsideLink href='http://muziekbank.nl' target='_blank'>
|
||||
muziekbank
|
||||
</OutsideLink>{' '}
|
||||
is a library of vinyls and cds where customers can go to discover new
|
||||
and old music, experience event and rent items like a normal library.
|
||||
</p>
|
||||
<p>
|
||||
While being active I manned the counter to assist customers and was
|
||||
involved in processing newly bought cds to be added to the collection
|
||||
</p>
|
||||
</CareerEntry>
|
||||
)}
|
||||
<CareerEntry
|
||||
key='cofano'
|
||||
start='Nov. 2022'
|
||||
header='Full Stack Developer'
|
||||
subheader='Current, Cofano'
|
||||
finalEntry={!full}
|
||||
>
|
||||
<p>
|
||||
Full stack developer on a SaaS project based on Java, Spring Boot, GraphQL,
|
||||
Typescript and React.
|
||||
</p>
|
||||
<p>
|
||||
Personally dove deep in understanding the fundamentals of frameworks and
|
||||
languages and applying them for major version upgrades and developing on
|
||||
internal tools and libraries.
|
||||
</p>
|
||||
</CareerEntry>
|
||||
{full && (
|
||||
<CareerEntry
|
||||
start='Apr. 2025'
|
||||
header='Trainer and Technical Committee'
|
||||
subheader={
|
||||
<Fragment>
|
||||
Current, Volleyball association{' '}
|
||||
<OutsideLink href='https://twente05.nl'>Twente '05</OutsideLink>
|
||||
</Fragment>
|
||||
}
|
||||
finalEntry={full}
|
||||
/>
|
||||
)}
|
||||
</Timeline>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,42 @@
|
||||
import { type ReactElement } from 'react';
|
||||
import { Card, CardContent, CardHeader } from '@mui/material';
|
||||
import { Link, Stack, Tooltip } from '@mui/material';
|
||||
import { GitHub, Source } from '@mui/icons-material';
|
||||
|
||||
export function Home(): ReactElement {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title='Home'></CardHeader>
|
||||
<CardContent>Hello, others</CardContent>
|
||||
</Card>
|
||||
<Stack width='100%' justifyContent='center' textAlign='center' direction='column'>
|
||||
<h1>Welcome</h1>
|
||||
<h2>To My Website</h2>
|
||||
<p />
|
||||
<p>
|
||||
On this website you can find bits about me and bits by me. I am a software developer
|
||||
and the career tab can tell you some of my history in the world. As proof of being a
|
||||
software developer, this website is entirely self-hosted, including a git repository
|
||||
(and except the DNS servers, I suppose), on a server that I have physically built
|
||||
and maintain.
|
||||
</p>
|
||||
<p>
|
||||
Besides being a software developer, I also like stories and puzzles a whole lot, so
|
||||
you can also find tabs dedicated to those. Hope you enjoy! I hope to add more
|
||||
puzzle-focused elements to the site in the future.
|
||||
</p>
|
||||
<p>
|
||||
Use the top bar to find what you are looking for or contact me via email. No direct
|
||||
mailto link here to avoid scraper bots, but my first name and this domain name
|
||||
should do the trick.
|
||||
</p>
|
||||
<Stack marginTop='20px' direction='row' spacing={2} justifyContent='center'>
|
||||
<Link href='https://github.com/WouterLVV' target='_blank'>
|
||||
<Tooltip title='GitHub'>
|
||||
<GitHub />
|
||||
</Tooltip>
|
||||
</Link>
|
||||
<Link href='https://git.cblt.xyz/wouter/WebHome' target='_blank'>
|
||||
<Tooltip title='source'>
|
||||
<Source />
|
||||
</Tooltip>
|
||||
</Link>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Fragment, type ReactElement } from 'react';
|
||||
import { OutsideLink } from '../components/OutsideLink.tsx';
|
||||
|
||||
export function Puzzles(): ReactElement {
|
||||
return (
|
||||
<Fragment>
|
||||
<p />
|
||||
<p>
|
||||
I have a love for puzzles and puzzle events. In the past I have particapated in and
|
||||
organized the{' '}
|
||||
<OutsideLink href='https://iapandora.nl'>Pandora puzzle event</OutsideLink> at the
|
||||
University of Twente.
|
||||
</p>
|
||||
<p>As such I want to create and share more puzzles here, in time.</p>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Fragment, type ReactElement } from 'react';
|
||||
import { Card, CardContent } from '@mui/material';
|
||||
import { OutsideLink } from '../components/OutsideLink.tsx';
|
||||
|
||||
export function Storytelling(): ReactElement {
|
||||
return (
|
||||
<Fragment>
|
||||
<p />
|
||||
<p>
|
||||
I like to consume and tell stories. Besides reading and watching tv and movies, I
|
||||
also like to create my own stories.
|
||||
</p>
|
||||
<p>
|
||||
I've played my fair share of Dungeons and Dragons and have also DM'ed for a while,
|
||||
and for the{' '}
|
||||
<OutsideLink href='https://iapandora.nl'>Pandora puzzle event</OutsideLink> I have
|
||||
also been responsible for or involved with the story aspect during the two times I
|
||||
was part of the event organisation
|
||||
</p>
|
||||
<p>
|
||||
I've also participated in a small short story writing competition for{' '}
|
||||
<OutsideLink href='https://bellettrie.utwente.nl'>Bellettrie</OutsideLink>, the
|
||||
student library of the University of Twente. The theme was 'Fairytale' and you can
|
||||
read my entry <OutsideLink href='assets/Squagon.pdf'>here</OutsideLink>.
|
||||
</p>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
7
src/utils.ts
Normal file
7
src/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
export function useToggle(initialValue?: boolean): [boolean, () => void] {
|
||||
const [state, setState] = useState<boolean>(initialValue ?? false);
|
||||
const toggle = useCallback(() => setState((oldState) => !oldState), []);
|
||||
return [state, toggle];
|
||||
}
|
||||
Reference in New Issue
Block a user