mirror of
https://github.com/kremalicious/blog.git
synced 2025-02-14 21:10:25 +01:00
Merge pull request #498 from kremalicious/feature/css-modules-plugin-image
Gatsby v3
This commit is contained in:
commit
f976a5581a
@ -5,7 +5,7 @@
|
||||
"stylelint-prettier/recommended"
|
||||
],
|
||||
"plugins": ["stylelint-prettier"],
|
||||
"syntax": "scss",
|
||||
"syntax": "css",
|
||||
"rules": {
|
||||
"prettier/prettier": true,
|
||||
"property-no-unknown": [
|
||||
|
@ -42,9 +42,5 @@ module.exports = {
|
||||
title: '/Uses',
|
||||
link: '/uses'
|
||||
}
|
||||
],
|
||||
darkModeConfig: {
|
||||
classNameDark: 'dark',
|
||||
classNameLight: 'light'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ These icons are free for your personal use and include icons for all file types
|
||||
Get them and have fun.
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/aperturefiletypes_by_kremalicious.zip">Download</a>
|
||||
<a class="icon-download btn btn-primary" href="../media/aperturefiletypes_by_kremalicious.zip">Download</a>
|
||||
</p>
|
||||
|
||||
And don't forget to read my article about [how to change the generic image icons in Mac OS X Leopard](/changing-the-image-icons-in-mac-os-x-leopard/).
|
||||
|
@ -19,5 +19,5 @@ I have added my first wallpaper to the Goodies section on this website. It's a s
|
||||
You can get the wallpaper by browsing [my Goodies section](http://www.kremalicious.com/goodies/) and clicking on the download link for the wallpaper. The download package also includes a custom folder icon (512px as .icns, .png, .ico) with the Chives wallpaper on it.
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/chives_by_kremalicious.zip">Download Chives Wallpaper</a>
|
||||
<a class="icon-download btn btn-primary" href="../media/chives_by_kremalicious.zip">Download Chives Wallpaper</a>
|
||||
</p>
|
||||
|
@ -92,7 +92,7 @@ Brief description of Daguerres role in the invention of photography process by T
|
||||
This icon package was exclusively announced first on [MacThemes](http://macthemes2.net) and you can download it from the [Goodies section on this website](/goodies/) or directly via this link:
|
||||
|
||||
<p class="content-download">
|
||||
<a class="btn icon-download" href="../media/niepces_camera_obscura_by_kremalicious.zip">Download</a>
|
||||
<a class="btn btn-primary icon-download" href="../media/niepces_camera_obscura_by_kremalicious.zip">Download</a>
|
||||
</p>
|
||||
|
||||
In addition to these icons you can download the [associated wallpapers for your desktop or your iPhone](http://www.kremalicious.com/2008/06/new-goodie-niepces-camera-obscura-wallpaper-pack/).
|
||||
|
@ -19,7 +19,7 @@ In addition to my Niépce's Camera Obscura icons for Aperture and iPhoto I have
|
||||
All Wallpapers are using a custom designed background which imitates the look of a metal plate like it was used in Niépce's experiments although it wasn't golden. To make it more Mac style I have added a stenciled dots pattern (which is a commonly used reference to the front design of the MacPro).
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/niepces_camera_obscura_wallpaper_pack_by_kremalicious.zip">Download Niépce's Camera Obscura Wallpaper</a>
|
||||
<a class="icon-download btn btn-primary" href="../media/niepces_camera_obscura_wallpaper_pack_by_kremalicious.zip">Download Niépce's Camera Obscura Wallpaper</a>
|
||||
</p>
|
||||
|
||||
Here are the details:
|
||||
|
@ -376,7 +376,7 @@ Here you can see the icons included in the Server Displays icon pack:
|
||||
Because I've just modified Apple's standard icons these icons are just available via this blog post and they will not show up in my Goodies section. Just download the whole package directly via this link:
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/server_displays_by_kremalicious.zip">Download Server Display Icons <span>zip</span></a>
|
||||
<a class="icon-download btn btn-primary" href="../media/server_displays_by_kremalicious.zip">Download Server Display Icons <span>zip</span></a>
|
||||
</p>
|
||||
|
||||
### How to use the icons
|
||||
|
@ -20,7 +20,7 @@ As always these desktop icons are free for you personal and non-commercial use.
|
||||
The whole package includes 7 icons either packed in a nice tagged iContainer for use with Candybar or in Mac + Win + Linux compatible formats. If you have such an Icy Box case grab the icons. Have fun!
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/icybox_by_kremalicious.zip">Download Icy Box Icons <span>zip</span></a>
|
||||
<a class="icon-download btn btn-primary" href="../media/icybox_by_kremalicious.zip">Download Icy Box Icons <span>zip</span></a>
|
||||
</p>
|
||||
|
||||
- Replacement icons for the silver and black Icy Box external aluminium case with USB interface
|
||||
|
@ -18,6 +18,6 @@ Show your geeky love for extraterrestrial universities! This is a simple Futuram
|
||||
|
||||
As usual you can [grab the whole zip package from my Goodies page](http://www.kremalicious.com/goodies/#wall) and have fun.
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/mars-u-wall-by-kremalicious.zip">Download Futurama Mars U Wallpaper <span>zip</span></a>
|
||||
<p class="content-download ">
|
||||
<a class="icon-download btn btn-primary" href="../media/mars-u-wall-by-kremalicious.zip">Download Futurama Mars U Wallpaper <span>zip</span></a>
|
||||
</p>
|
||||
|
@ -22,5 +22,5 @@ I’ve just released my own coffee cup icon, enjoy:
|
||||
## Download
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/coffee_cup_by_kremalicious.zip">Download Coffee Cup Icons</a>
|
||||
<a class="icon-download btn btn-primary" href="../media/coffee_cup_by_kremalicious.zip">Download Coffee Cup Icons</a>
|
||||
</p>
|
||||
|
@ -29,5 +29,5 @@ Simple, high-resolution Futurama tribute wallpaper pack inspired by the latest F
|
||||
Seriously, the pink versions are burning my eyes but the pink is a good reference to the events in the recent movie.
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/out-of-whale-oil-wall-by-kremalicious.zip">Download</a>
|
||||
<a class="icon-download btn btn-primary" href="../media/out-of-whale-oil-wall-by-kremalicious.zip">Download</a>
|
||||
</p>
|
||||
|
@ -16,7 +16,7 @@ Here's a quick twitter icon for use on your websites which is kind of a by-produ
|
||||
This icon comes in various formats (PNG, ICNS, iContainer) and in 4 different sizes (128px, 48px, 32px, 16px) with each icon size redrawn (of course). Just head over [to my Goodies page](http://www.kremalicious.com/goodies/) or click the following download button and grab these icons while they're hot.
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/twitter-crisp-by-kremalicious.zip">Download</a>
|
||||
<a class="icon-download btn btn-primary" href="../media/twitter-crisp-by-kremalicious.zip">Download</a>
|
||||
</p>
|
||||
|
||||
Use them on any web project you like and/or [convert them into a send to twitter link](http://kremalicious.com/ultimate-coda-wordpress-share-link-bonanza/). Have fun!
|
||||
|
@ -26,7 +26,7 @@ The icon comes in various formats (iContainer, icns, png) in sizes from 512px-16
|
||||
Just head over [to my Goodies page](http://www.kremalicious.com/goodies/) or click the following download button and grab this icon while it's hot.
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/adiumeetie-by-kremalicious.zip">Download</a>
|
||||
<a class="icon-download btn btn-primary" href="../media/adiumeetie-by-kremalicious.zip">Download</a>
|
||||
</p>
|
||||
|
||||
## Adium Icon Usage
|
||||
|
@ -20,7 +20,7 @@ tags:
|
||||
Just head over [to my Goodies page](http://www.kremalicious.com/goodies/) or click the following download button and grab these replacement icons.
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/delibar-by-kremalicious.zip">Download</a>
|
||||
<a class="icon-download btn btn-primary" href="../media/delibar-by-kremalicious.zip">Download</a>
|
||||
</p>
|
||||
|
||||
## Icon Usage
|
||||
|
@ -27,7 +27,7 @@ The homescreen icons pixels in the 16px version needed all more vivid colors to
|
||||
The icons come in various formats: iContainer, ICNS, ICO and PNG files for each size. Just click the following download button to grab the whole pack:
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/ipixelpad_by_kremalicious.zip">Download</a>
|
||||
<a class="icon-download btn btn-primary" href="../media/ipixelpad_by_kremalicious.zip">Download</a>
|
||||
</p>
|
||||
|
||||
## License
|
||||
|
@ -23,6 +23,6 @@ The wallpaper comes in four versions with two color variations and two text vari
|
||||
You can grab the full zip-package with versions for Desktop, iPad, iPhone & Android included:
|
||||
|
||||
<p class="content-download">
|
||||
<a class="btn-primary icon-download" href="../media/momcorp_wall_by_kremalicious.zip">Download</a>
|
||||
<a href="http://krlc.us/givecoffee" class="icon-heart">Donate</a>
|
||||
<a class="btn btn-primary icon-download" href="../media/momcorp_wall_by_kremalicious.zip">Download</a>
|
||||
<a href="http://krlc.us/givecoffee" class="btn icon-heart">Donate</a>
|
||||
</p>
|
||||
|
@ -24,7 +24,7 @@ The plugin is localized in english, german & spanish (thanks to Andrew Kurtis fr
|
||||
You can just install the plugin via the automatic backend installer under _Plugins > Add New_, activate and enjoy the red badges.
|
||||
|
||||
<p class="content-download">
|
||||
<a href="http://wordpress.org/extend/plugins/badged" class="btn-primary icon-wordpress">Plugin Page</a> <a class="btn-primary icon-github" href="https://github.com/kremalicious/Badged">GitHub</a> <a href="http://krlc.us/givecoffee" class="icon-heart btn">Donate</a>
|
||||
<a href="http://wordpress.org/extend/plugins/badged" class="btn btn-primary icon-wordpress">Plugin Page</a> <a class="btn btn-primary icon-github" href="https://github.com/kremalicious/Badged">GitHub</a> <a href="http://krlc.us/givecoffee" class="icon-heart btn">Donate</a>
|
||||
</p>
|
||||
|
||||
The plugin is hosted on GitHub and will always be mirrored in the WordPress plugins directory. But in case you want to install the plugin manually:
|
||||
|
@ -33,7 +33,7 @@ So if you value quality and want pixel perfect icons in your admin area you need
|
||||
I’ve put the template along with the implementation examples from the next section on [github](https://github.com/kremalicious/wp-icons-template). You can just download the whole package right away:
|
||||
|
||||
<p class="content-download">
|
||||
<a href="https://github.com/kremalicious/wp-icons-template/zipball/master" class="btn-primary icon-download">Download</a> <a href="https://github.com/kremalicious/wp-icons-template" class="icon-github">GitHub</a> <a href="http://krlc.us/givecoffee" class="icon-heart">Donate</a>
|
||||
<a href="https://github.com/kremalicious/wp-icons-template/zipball/master" class="btn btn-primary icon-download">Download</a> <a href="https://github.com/kremalicious/wp-icons-template" class="icon-github btn btn-primary">GitHub</a> <a href="http://krlc.us/givecoffee" class="icon-heart btn">Donate</a>
|
||||
</p>
|
||||
|
||||
### Usage
|
||||
|
@ -21,8 +21,8 @@ The above picture might be blurry depending on the device you're using so here's
|
||||
They are completely styled with CSS3 so they're sharp on all screens no matter how high the dpi. Have a look at the [full demo](http://lab.kremalicious.com/kbdfun/) or grab the project folder with the CSS & LESS files from GitHub. The code is under the MIT license so you're free to use it in any personal or commercial project.
|
||||
|
||||
<p class="content-download">
|
||||
<a class="btn-primary icon-compass" href="http://lab.kremalicious.com/kbdfun/">Demo</a>
|
||||
<a class="icon-github" href="https://github.com/kremalicious/kbdfun/">Github</a>
|
||||
<a class="btn btn-primary icon-compass" href="http://lab.kremalicious.com/kbdfun/">Demo</a>
|
||||
<a class="icon-github btn" href="https://github.com/kremalicious/kbdfun/">Github</a>
|
||||
</p>
|
||||
|
||||
## Usage
|
||||
|
@ -30,7 +30,7 @@ The full size I designed the wallpaper in is 3200x2048px. I don't know why Apple
|
||||
Download the whole package with all the sizes included or grab the individual ones from the list, all linked to the image files:
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-download" href="../media/project-purple-kremalicious.zip">Download <span> zip</span></a>
|
||||
<a class="icon-download btn btn-primary" href="../media/project-purple-kremalicious.zip">Download <span> zip</span></a>
|
||||
</p>
|
||||
|
||||
- [Desktop/rMBP/iPad 3 (3200x2048)](../media/project-purple-kremalicious.png)
|
||||
|
@ -15,9 +15,9 @@ tags:
|
||||
The badges provided by all app store providers just don't play well together with their varying typography and different sizing. So let's make them all visually unified, infinitely scalable, with pure text for easier localization and some web interaction styles. And while we’re at it: different sizes with the same markup by using some modifier classes.
|
||||
|
||||
<p class="content-download">
|
||||
<a class="btn-primary icon-compass" href="https://lab.kremalicious.com/appstorebadges/">Demo</a>
|
||||
<a class="icon-github" href="https://github.com/kremalicious/appstorebadges/">GitHub</a>
|
||||
<a class="icon-code" href="http://codepen.io/kremalicious/details/EVVraP/">Codepen</a>
|
||||
<a class="btn btn-primary icon-compass" href="https://lab.kremalicious.com/appstorebadges/">Demo</a>
|
||||
<a class="icon-github btn" href="https://github.com/kremalicious/appstorebadges/">GitHub</a>
|
||||
<a class="icon-code btn" href="http://codepen.io/kremalicious/details/EVVraP/">Codepen</a>
|
||||
</p>
|
||||
|
||||
## Styling
|
||||
|
@ -41,5 +41,5 @@ hpm install hyper-mac-pro
|
||||
Head over to GitHub to take a peek into the code or report some issues.
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-github btn-primary" href="https://github.com/kremalicious/hyper-mac-pro">GitHub</a>
|
||||
<a class="icon-github btn btn-primary" href="https://github.com/kremalicious/hyper-mac-pro">GitHub</a>
|
||||
</p>
|
||||
|
@ -59,5 +59,5 @@ plugins: [
|
||||
Head over to GitHub for more documentation, take a peek into the code, or to report some bugs.
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-github btn-primary" href="https://github.com/kremalicious/gatsby-plugin-matomo">GitHub</a>
|
||||
<a class="icon-github btn btn-primary" href="https://github.com/kremalicious/gatsby-plugin-matomo">GitHub</a>
|
||||
</p>
|
||||
|
@ -110,5 +110,5 @@ plugins: [
|
||||
Head over to GitHub for more documentation, take a peek into the code, or to report some bugs.
|
||||
|
||||
<p class="content-download">
|
||||
<a class="icon-github btn-primary" href="https://github.com/kremalicious/gatsby-redirect-from">GitHub</a>
|
||||
<a class="icon-github btn btn-primary" href="https://github.com/kremalicious/gatsby-redirect-from">GitHub</a>
|
||||
</p>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import './src/styles/global.scss'
|
||||
import './src/global/global.css'
|
||||
|
||||
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
|
||||
export const wrapPageElement = wrapPageElementWithLayout
|
||||
|
@ -23,6 +23,7 @@ module.exports = {
|
||||
},
|
||||
plugins: [
|
||||
...sources,
|
||||
'gatsby-plugin-image',
|
||||
{
|
||||
resolve: 'gatsby-plugin-sharp',
|
||||
options: {
|
||||
@ -77,7 +78,7 @@ module.exports = {
|
||||
},
|
||||
injectStyles: false,
|
||||
extensions: [
|
||||
`${__dirname}/vendor/nord-visual-studio-code-0.15.0.vsix`,
|
||||
'nord-visual-studio-code',
|
||||
`${__dirname}/vendor/polar-0.0.6.vsix`
|
||||
],
|
||||
languageAliases: {}
|
||||
@ -86,14 +87,6 @@ module.exports = {
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-plugin-sass',
|
||||
options: {
|
||||
sassOptions: {
|
||||
includePaths: [`${__dirname}/node_modules`, `${__dirname}/src/styles`]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-plugin-svgr',
|
||||
options: {
|
||||
@ -227,13 +220,6 @@ module.exports = {
|
||||
exclude: ['/archive', '/archive/**/*', '/thanks', '/tags']
|
||||
}
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-plugin-use-dark-mode',
|
||||
options: {
|
||||
...siteConfig.darkModeConfig,
|
||||
minify: true
|
||||
}
|
||||
},
|
||||
'gatsby-plugin-react-helmet',
|
||||
'gatsby-plugin-catch-links',
|
||||
'gatsby-redirect-from',
|
||||
|
@ -64,7 +64,7 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
|
||||
}
|
||||
|
||||
archive: allMarkdownRemark(
|
||||
filter: { fields: { type: { ne: "photo" } } }
|
||||
filter: { fields: { type: { nin: "photo" } } }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
@ -95,15 +95,15 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
|
||||
// Generate post pages
|
||||
generatePostPages(createPage, all)
|
||||
|
||||
// Generate archive pages
|
||||
generateArchivePages(createPage, archiveLength)
|
||||
|
||||
// Generate photos archive pages
|
||||
generatePhotosPages(createPage, photosLength)
|
||||
|
||||
// Generate tag pages
|
||||
generateTagPages(createPage, tags)
|
||||
|
||||
// Generate archive pages
|
||||
generateArchivePages(createPage, archiveLength)
|
||||
|
||||
// Create manual redirects
|
||||
generateRedirectPages(createRedirect)
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ exports.generatePostPages = (createPage, posts) => {
|
||||
})
|
||||
}
|
||||
|
||||
function generateIndexPages(createPage, length, slug, template) {
|
||||
function generateIndexPages(createPage, length, slug, template, tag) {
|
||||
const numPages = Math.ceil(length / itemsPerPage)
|
||||
|
||||
Array.from({ length: numPages }).forEach((_, i) => {
|
||||
@ -67,7 +67,8 @@ function generateIndexPages(createPage, length, slug, template) {
|
||||
numPages: numPages,
|
||||
currentPageNumber: i + 1,
|
||||
prevPagePath,
|
||||
nextPagePath
|
||||
nextPagePath,
|
||||
...(tag && { tag })
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -90,7 +91,8 @@ exports.generateTagPages = (createPage, tags) => {
|
||||
createPage,
|
||||
totalCount,
|
||||
`/archive/${tag}/`,
|
||||
archiveTemplate
|
||||
archiveTemplate,
|
||||
tag
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -44,10 +44,10 @@ module.exports = [
|
||||
url: 'https://api.github.com/graphql',
|
||||
headers: {
|
||||
Authorization: `bearer ${process.env.GATSBY_GITHUB_TOKEN}`
|
||||
},
|
||||
}
|
||||
// Additional options to pass to node-fetch
|
||||
fetchOptions: {},
|
||||
refetchInterval: 300 // 5 min.
|
||||
// fetchOptions: {},
|
||||
// refetchInterval: 300 // 5 min.
|
||||
}
|
||||
}
|
||||
]
|
||||
|
20165
package-lock.json
generated
20165
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
101
package.json
101
package.json
@ -8,16 +8,15 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "gatsby develop --host 0.0.0.0",
|
||||
"build": "gatsby build && npm run copy",
|
||||
"build": "gatsby build",
|
||||
"ssr": "npm run build && serve -s public/",
|
||||
"test": "npm run lint && jest -c jest/jest.config.js --coverage --silent",
|
||||
"test:watch": "npm run lint && jest -c jest/jest.config.js --coverage --watch",
|
||||
"copy": "cp -R content/media/ public",
|
||||
"lint": "run-p --continue-on-error lint:js lint:css lint:md",
|
||||
"lint:js": "eslint --ignore-path .gitignore --ext .js,.jsx,.ts,.tsx .",
|
||||
"lint:css": "stylelint 'src/**/*.{css,scss}'",
|
||||
"lint:css": "stylelint 'src/**/*.css'",
|
||||
"lint:md": "markdownlint './**/*.{md,markdown}' --ignore './{node_modules,public,.cache,.git,coverage}/**/*'",
|
||||
"format": "prettier --ignore-path .gitignore --write '**/*.{js,jsx,ts,tsx,md,json,css,scss}'",
|
||||
"format": "prettier --ignore-path .gitignore --write '**/*.{js,jsx,ts,tsx,md,json,css}'",
|
||||
"tsc": "tsc --noEmit",
|
||||
"deploy": "./scripts/deploy-s3.sh",
|
||||
"new": "ts-node ./scripts/new.ts"
|
||||
@ -29,106 +28,104 @@
|
||||
"not op_mini all"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ethersproject/providers": "^5.0.23",
|
||||
"@ethersproject/units": "^5.0.10",
|
||||
"@ethersproject/providers": "^5.0.24",
|
||||
"@ethersproject/units": "^5.0.11",
|
||||
"@loadable/component": "^5.14.1",
|
||||
"@web3-react/core": "^6.1.9",
|
||||
"@web3-react/injected-connector": "^6.0.7",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^2.17.0",
|
||||
"date-fns": "^2.19.0",
|
||||
"dms2dec": "^1.1.0",
|
||||
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
||||
"fast-exif": "^1.0.1",
|
||||
"feather-icons": "^4.28.0",
|
||||
"fraction.js": "^4.0.13",
|
||||
"gatsby": "^2.32.8",
|
||||
"gatsby-image": "^2.11.0",
|
||||
"gatsby-plugin-catch-links": "^2.10.0",
|
||||
"gatsby-plugin-feed": "^2.13.1",
|
||||
"gatsby": "^3.1.1",
|
||||
"gatsby-plugin-catch-links": "^3.1.0",
|
||||
"gatsby-plugin-feed": "^3.1.0",
|
||||
"gatsby-plugin-image": "^1.1.1",
|
||||
"gatsby-plugin-lunr": "^1.5.2",
|
||||
"gatsby-plugin-manifest": "^2.12.0",
|
||||
"gatsby-plugin-manifest": "^3.1.0",
|
||||
"gatsby-plugin-matomo": "^0.9.0",
|
||||
"gatsby-plugin-meta-redirect": "^1.1.1",
|
||||
"gatsby-plugin-offline": "^3.10.2",
|
||||
"gatsby-plugin-react-helmet": "^3.10.0",
|
||||
"gatsby-plugin-sass": "^3.2.0",
|
||||
"gatsby-plugin-sharp": "^2.14.3",
|
||||
"gatsby-plugin-sitemap": "^2.12.0",
|
||||
"gatsby-plugin-svgr": "^2.1.0",
|
||||
"gatsby-plugin-use-dark-mode": "^1.2.0",
|
||||
"gatsby-plugin-webpack-size": "^1.0.0",
|
||||
"gatsby-plugin-offline": "^4.1.0",
|
||||
"gatsby-plugin-react-helmet": "^4.1.0",
|
||||
"gatsby-plugin-sharp": "^3.1.1",
|
||||
"gatsby-plugin-sitemap": "^3.1.0",
|
||||
"gatsby-plugin-svgr": "^3.0.0-beta.0",
|
||||
"gatsby-plugin-webpack-bundle-analyser-v2": "^1.1.21",
|
||||
"gatsby-plugin-webpack-size": "^2.0.1",
|
||||
"gatsby-redirect-from": "^0.3.0",
|
||||
"gatsby-remark-autolink-headers": "^2.11.0",
|
||||
"gatsby-remark-autolink-headers": "^3.1.0",
|
||||
"gatsby-remark-breaks": "^1.0.0",
|
||||
"gatsby-remark-copy-linked-files": "^2.10.0",
|
||||
"gatsby-remark-images": "^3.11.1",
|
||||
"gatsby-remark-copy-linked-files": "^3.1.0",
|
||||
"gatsby-remark-images": "^4.1.0",
|
||||
"gatsby-remark-images-medium-zoom": "^1.7.0",
|
||||
"gatsby-remark-smartypants": "^2.10.0",
|
||||
"gatsby-remark-vscode": "^3.2.0",
|
||||
"gatsby-source-filesystem": "^2.11.1",
|
||||
"gatsby-source-graphql": "^2.14.0",
|
||||
"gatsby-transformer-remark": "^2.16.1",
|
||||
"gatsby-transformer-sharp": "^2.12.0",
|
||||
"gatsby-remark-smartypants": "^3.1.0",
|
||||
"gatsby-remark-vscode": "^3.2.1",
|
||||
"gatsby-source-filesystem": "^3.1.0",
|
||||
"gatsby-source-graphql": "^3.1.0",
|
||||
"gatsby-transformer-remark": "^3.1.0",
|
||||
"gatsby-transformer-sharp": "^3.1.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"pigeon-maps": "^0.17.1",
|
||||
"nord-visual-studio-code": "github:arcticicestudio/nord-visual-studio-code",
|
||||
"pigeon-maps": "^0.19.5",
|
||||
"pigeon-marker": "^0.3.4",
|
||||
"react": "^16.14.0",
|
||||
"react": "^17.0.1",
|
||||
"react-clipboard.js": "^2.0.16",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.9",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-qr-svg": "^2.3.0",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"remark": "^13.0.0",
|
||||
"remark-react": "^8.0.0",
|
||||
"slugify": "^1.4.7",
|
||||
"use-dark-mode": "^2.3.1"
|
||||
"slugify": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"@testing-library/jest-dom": "^5.11.9",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@types/classnames": "^2.2.11",
|
||||
"@types/fs-extra": "^9.0.7",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/fs-extra": "^9.0.8",
|
||||
"@types/jest": "^26.0.21",
|
||||
"@types/loadable__component": "^5.13.3",
|
||||
"@types/lunr": "^2.3.3",
|
||||
"@types/node": "^14.14.28",
|
||||
"@types/node": "^14.14.35",
|
||||
"@types/node-fetch": "^2.5.8",
|
||||
"@types/react": "^17.0.2",
|
||||
"@types/react-dom": "^17.0.1",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-dom": "^17.0.2",
|
||||
"@types/react-helmet": "^6.1.0",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"@types/shortid": "^0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^4.15.2",
|
||||
"@typescript-eslint/parser": "^4.15.2",
|
||||
"@welldone-software/why-did-you-render": "^6.0.5",
|
||||
"eslint": "^7.21.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
"@typescript-eslint/parser": "^4.18.0",
|
||||
"@welldone-software/why-did-you-render": "^6.1.1",
|
||||
"eslint": "^7.22.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-graphql": "^4.0.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"eslint-plugin-testing-library": "^3.10.1",
|
||||
"eslint-plugin-testing-library": "^3.10.2",
|
||||
"fs-extra": "^9.1.0",
|
||||
"gatsby-plugin-webpack-bundle-analyser-v2": "^1.1.20",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^26.6.3",
|
||||
"markdownlint-cli": "^0.26.0",
|
||||
"markdownlint-cli": "^0.27.1",
|
||||
"node-iptc": "^1.0.5",
|
||||
"node-sass": "^5.0.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"ora": "^5.3.0",
|
||||
"ora": "^5.4.0",
|
||||
"postcss": "^8.2.8",
|
||||
"prettier": "^2.2.1",
|
||||
"shortid": "^2.2.16",
|
||||
"stylelint": "^13.11.0",
|
||||
"stylelint": "^13.12.0",
|
||||
"stylelint-config-css-modules": "^2.2.0",
|
||||
"stylelint-config-prettier": "^8.0.2",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
"stylelint-config-standard": "^21.0.0",
|
||||
"stylelint-prettier": "^1.2.0",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.2.2"
|
||||
"typescript": "^4.2.3",
|
||||
"typescript-plugin-css-modules": "^3.2.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
15
src/@types/Image.d.ts
vendored
15
src/@types/Image.d.ts
vendored
@ -1,17 +1,14 @@
|
||||
import { FixedObject, FluidObject } from 'gatsby-image'
|
||||
import { GatsbyImageProps, IGatsbyImageData } from 'gatsby-plugin-image'
|
||||
|
||||
export interface ImageProps {
|
||||
export interface ImageProps extends GatsbyImageProps {
|
||||
title?: string
|
||||
fluid?: FluidObject
|
||||
fixed?: FixedObject
|
||||
alt?: string
|
||||
original?: { src: string }
|
||||
className?: string
|
||||
}
|
||||
|
||||
export interface ImageNode {
|
||||
childImageSharp: ImageProps
|
||||
fields: {
|
||||
exif: Exif
|
||||
export interface ImageNode extends IGatsbyImageData {
|
||||
fields?: {
|
||||
exif?: Exif
|
||||
}
|
||||
}
|
||||
|
||||
|
4
src/@types/css.d.ts
vendored
Normal file
4
src/@types/css.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare module '*.module.css' {
|
||||
const classes: { [key: string]: string }
|
||||
export default classes
|
||||
}
|
10
src/@types/global.d.ts
vendored
10
src/@types/global.d.ts
vendored
@ -1,13 +1,3 @@
|
||||
declare module '*.module.css' {
|
||||
const classes: { [key: string]: string }
|
||||
export default classes
|
||||
}
|
||||
|
||||
declare module '*.module.scss' {
|
||||
const classes: { [key: string]: string }
|
||||
export default classes
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
import * as React from 'react'
|
||||
export const ReactComponent: React.FunctionComponent<
|
||||
|
75
src/components/Layout.module.css
Normal file
75
src/components/Layout.module.css
Normal file
@ -0,0 +1,75 @@
|
||||
.content {
|
||||
padding: 0 calc(var(--spacer) / 1.5);
|
||||
width: 100%;
|
||||
max-width: var(--maxWidthContainer);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* topbar and footer as fixed
|
||||
site background
|
||||
///////////////////////////////////// */
|
||||
|
||||
.document {
|
||||
width: 100%;
|
||||
padding-top: var(--spacer);
|
||||
background-color: var(--body-background-color);
|
||||
padding-bottom: calc(var(--spacer) * 2);
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: 0.4s var(--easing);
|
||||
transition-property: transform, background, border, box-shadow;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.85);
|
||||
box-shadow: 0 1px 10px rgba(1, 85, 101, 0.1),
|
||||
0 -1px 4px rgba(1, 85, 101, 0.05);
|
||||
}
|
||||
|
||||
:global(.has-menu-open) .document {
|
||||
transform: translate3d(0, calc(var(--spacer) * 2), 0);
|
||||
}
|
||||
|
||||
:global(.dark) .document {
|
||||
border-top-color: rgba(255, 255, 255, 0.05);
|
||||
box-shadow: 0 1px 8px rgba(0, 7, 8, 0.3), 0 -1px 4px rgba(0, 21, 25, 0.8);
|
||||
}
|
||||
|
||||
@media (min-width: 60rem) {
|
||||
.document {
|
||||
padding-top: calc(var(--spacer) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) and (min-height: 500px) {
|
||||
.document {
|
||||
margin-top: calc(var(--spacer) * 2.5);
|
||||
|
||||
/* height of footer */
|
||||
margin-bottom: calc(var(--spacer) * 12);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
min-height: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: var(--maxWidthContent);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.wide {
|
||||
composes: container;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.breakout {
|
||||
margin-left: calc(-50vw + 50%);
|
||||
margin-right: calc(-50vw + 50%);
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
.breakout {
|
||||
margin-left: -8rem;
|
||||
margin-right: -8rem;
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
#___gatsby {
|
||||
// display: flex;
|
||||
// min-height: 100vh;
|
||||
// flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 $spacer / $line-height;
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: $screen-sm) {
|
||||
padding: 0 ($spacer * 2);
|
||||
}
|
||||
}
|
||||
|
||||
// topbar and footer as fixed
|
||||
// site background
|
||||
/////////////////////////////////////
|
||||
|
||||
.document {
|
||||
width: 100%;
|
||||
padding-top: $spacer;
|
||||
background-color: $body-background-color;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.7);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.7);
|
||||
padding-bottom: $spacer * 2;
|
||||
box-shadow: 0 1px 4px rgba($brand-main, 0.1),
|
||||
0 -1px 4px rgba($brand-main, 0.2);
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: 0.4s $easing;
|
||||
transition-property: transform, background;
|
||||
|
||||
:global(.has-menu-open) & {
|
||||
transform: translate3d(0, ($spacer * 3.5), 0);
|
||||
}
|
||||
|
||||
:global(.dark) & {
|
||||
background-color: $body-background-color--dark;
|
||||
color: $text-color--dark;
|
||||
border-top-color: darken($brand-grey, 15%);
|
||||
border-bottom-color: darken($body-background-color--dark, 3%);
|
||||
box-shadow: 0 1px 8px rgba(darken($brand-main, 15%), 0.1),
|
||||
0 -1px 4px darken($brand-main, 15%);
|
||||
}
|
||||
|
||||
@media (min-width: $screen-md) {
|
||||
padding-top: $spacer * 2;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-sm) and (min-height: 500px) {
|
||||
margin-top: $spacer * 2.65;
|
||||
margin-bottom: $spacer * 18; // height of footer
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
min-height: 500px;
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import Container from './atoms/Container'
|
||||
import Typekit from './atoms/Typekit'
|
||||
import Header from './organisms/Header'
|
||||
import Footer from './organisms/Footer'
|
||||
import styles from './Layout.module.scss'
|
||||
import { document, content } from './Layout.module.css'
|
||||
|
||||
// if (process.env.NODE_ENV !== 'production') {
|
||||
// // eslint-disable-next-line
|
||||
@ -17,10 +16,8 @@ export default function Layout({ children }: { children: any }): ReactElement {
|
||||
<Typekit />
|
||||
<Header />
|
||||
|
||||
<main className={styles.document} id="document">
|
||||
<div className={styles.content}>
|
||||
<Container>{children}</Container>
|
||||
</div>
|
||||
<main className={document} id="document">
|
||||
<div className={content}>{children}</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
|
88
src/components/atoms/Changelog.module.css
Normal file
88
src/components/atoms/Changelog.module.css
Normal file
@ -0,0 +1,88 @@
|
||||
.title {
|
||||
margin-top: calc(var(--spacer) * 3);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: var(--spacer);
|
||||
padding-left: calc(var(--spacer) / 2);
|
||||
margin-left: calc(var(--spacer) / 2);
|
||||
border-left: var(--stroke-width) solid var(--border-color);
|
||||
}
|
||||
|
||||
.content h2,
|
||||
.content h3,
|
||||
.content h4 {
|
||||
position: relative;
|
||||
margin-bottom: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.content h2::before,
|
||||
.content h3::before,
|
||||
.content h4::before {
|
||||
content: '';
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
background: var(--color-headings);
|
||||
position: absolute;
|
||||
left: -1.275rem;
|
||||
top: calc(var(--font-size-large) / 3);
|
||||
}
|
||||
|
||||
.content h2 + blockquote,
|
||||
.content h3 + blockquote,
|
||||
.content h4 + blockquote {
|
||||
padding-left: 0;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.content h2 + blockquote::before,
|
||||
.content h3 + blockquote::before,
|
||||
.content h4 + blockquote::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content h2,
|
||||
.content h3 {
|
||||
font-size: var(--font-size-large);
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin-left: 0;
|
||||
margin-top: calc(var(--spacer) / 8);
|
||||
margin-bottom: calc(var(--spacer) / var(--line-height));
|
||||
}
|
||||
|
||||
.content ul {
|
||||
font-size: var(--font-size-small);
|
||||
margin-left: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
||||
.content ul li {
|
||||
margin-bottom: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
||||
.source {
|
||||
font-size: var(--font-size-mini);
|
||||
font-family: var(--font-family-base);
|
||||
font-weight: var(--font-weight-base);
|
||||
padding-bottom: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.source,
|
||||
.source a {
|
||||
color: var(--text-color-light);
|
||||
}
|
||||
|
||||
.source a {
|
||||
margin-left: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
||||
.source code {
|
||||
font-size: calc(var(--font-size-mini) * 0.9);
|
||||
}
|
||||
|
||||
.source a:hover {
|
||||
color: var(--link-color);
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
@import 'variables';
|
||||
|
||||
.title {
|
||||
margin-top: $spacer * 3;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: $spacer;
|
||||
padding-left: $spacer / 2;
|
||||
margin-left: $spacer / 2;
|
||||
border-left: 1px solid $brand-grey-dimmed;
|
||||
|
||||
:global(.dark) & {
|
||||
border-left-color: rgba($color-headings--dark, 0.2);
|
||||
}
|
||||
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
position: relative;
|
||||
margin-bottom: $spacer / 4;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
background: $color-headings;
|
||||
position: absolute;
|
||||
left: -($spacer / 1.5);
|
||||
top: $font-size-large / 3;
|
||||
|
||||
:global(.dark) & {
|
||||
background: $color-headings--dark;
|
||||
}
|
||||
}
|
||||
|
||||
+ blockquote {
|
||||
padding-left: 0;
|
||||
font-size: $font-size-small;
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
font-size: $font-size-large;
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin-left: 0;
|
||||
margin-top: $spacer / 8;
|
||||
margin-bottom: $spacer / $line-height;
|
||||
}
|
||||
|
||||
ul {
|
||||
font-size: $font-size-small;
|
||||
margin-left: $spacer / 8;
|
||||
|
||||
li {
|
||||
margin-bottom: $spacer / 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.source {
|
||||
font-size: $font-size-mini;
|
||||
font-family: $font-family-base;
|
||||
font-weight: $font-weight-base;
|
||||
padding-bottom: $spacer / 2;
|
||||
|
||||
&,
|
||||
a {
|
||||
color: $brand-grey-light;
|
||||
}
|
||||
|
||||
a {
|
||||
margin-left: $spacer / 8;
|
||||
|
||||
code {
|
||||
font-size: ($font-size-mini * 0.9);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $link-color;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ import React, { ReactElement } from 'react'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
import remark from 'remark'
|
||||
import remarkReact from 'remark-react'
|
||||
import styles from './Changelog.module.scss'
|
||||
import { title, content, source } from './Changelog.module.css'
|
||||
import { GitHub, GitHubRepo } from '../../@types/GitHub'
|
||||
|
||||
export function PureChangelog({
|
||||
@ -30,22 +30,20 @@ export function PureChangelog({
|
||||
const filePathDisplay = `${owner.login}/${repo}:CHANGELOG.md`
|
||||
|
||||
return (
|
||||
<div className={styles.changelog}>
|
||||
<h2 className={styles.title} id="changelog">
|
||||
<>
|
||||
<h2 className={title} id="changelog">
|
||||
Changelog
|
||||
</h2>
|
||||
<div className={styles.content}>
|
||||
<p className={styles.source}>
|
||||
<em>
|
||||
sourced from{' '}
|
||||
<a href={filePathUrl}>
|
||||
<code>{filePathDisplay}</code>
|
||||
</a>
|
||||
</em>
|
||||
</p>
|
||||
<div className={content}>
|
||||
{changelogHtml}
|
||||
<p className={source}>
|
||||
sourced from{' '}
|
||||
<a href={filePathUrl}>
|
||||
<code>{filePathDisplay}</code>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
.container {
|
||||
max-width: 37rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Container.module.scss'
|
||||
|
||||
export default function Container({
|
||||
children
|
||||
}: {
|
||||
children: any
|
||||
}): ReactElement {
|
||||
return <section className={styles.container}>{children}</section>
|
||||
}
|
30
src/components/atoms/Copy.module.css
Normal file
30
src/components/atoms/Copy.module.css
Normal file
@ -0,0 +1,30 @@
|
||||
.button {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
background: rgba(var(--brand-grey), 0.3);
|
||||
padding: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
||||
.button svg {
|
||||
stroke: var(--text-color-light);
|
||||
transition: 0.15s ease-out;
|
||||
}
|
||||
|
||||
.copied {
|
||||
background: green;
|
||||
}
|
||||
|
||||
.copied svg {
|
||||
stroke: var(--text-color-dimmed);
|
||||
}
|
||||
|
||||
.button:hover svg {
|
||||
stroke: var(--text-color-dimmed);
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
@import 'variables';
|
||||
|
||||
.button {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
background: rgba($brand-grey, 0.3);
|
||||
padding: $spacer / 3;
|
||||
|
||||
svg {
|
||||
stroke: $brand-grey-light;
|
||||
transition: 0.15s ease-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
stroke: $brand-grey-dimmed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.copied {
|
||||
background: green;
|
||||
|
||||
// stylelint-disable-next-line no-descending-specificity
|
||||
svg {
|
||||
stroke: $brand-grey-dimmed;
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import loadable from '@loadable/component'
|
||||
import styles from './Copy.module.scss'
|
||||
import { copied, button } from './Copy.module.css'
|
||||
import Icon from './Icon'
|
||||
|
||||
const Clipboard = loadable(() => import('react-clipboard.js'))
|
||||
|
||||
const onCopySuccess = (e: any) => {
|
||||
e.trigger.classList.add(styles.copied)
|
||||
e.trigger.classList.add(copied)
|
||||
}
|
||||
|
||||
export default function Copy({ text }: { text: string }): ReactElement {
|
||||
@ -15,7 +15,7 @@ export default function Copy({ text }: { text: string }): ReactElement {
|
||||
data-clipboard-text={text}
|
||||
button-title="Copy to clipboard"
|
||||
onSuccess={(e: ClipboardJS.Event) => onCopySuccess(e)}
|
||||
className={styles.button}
|
||||
className={button}
|
||||
>
|
||||
<Icon name="Copy" />
|
||||
</Clipboard>
|
||||
|
18
src/components/atoms/Divider.module.css
Normal file
18
src/components/atoms/Divider.module.css
Normal file
@ -0,0 +1,18 @@
|
||||
.divider {
|
||||
position: relative;
|
||||
border-bottom: 1px dashed var(--border-color);
|
||||
}
|
||||
|
||||
.divider::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
bottom: -2px;
|
||||
width: 100%;
|
||||
border-bottom: 1px dashed #fff;
|
||||
}
|
||||
|
||||
:global(.dark) .divider::before {
|
||||
border-bottom-color: var(--brand-grey);
|
||||
}
|
69
src/components/atoms/Exif.module.css
Normal file
69
src/components/atoms/Exif.module.css
Normal file
@ -0,0 +1,69 @@
|
||||
.exif {
|
||||
margin-top: -3rem;
|
||||
margin-bottom: calc(var(--spacer) * 2);
|
||||
}
|
||||
|
||||
.data {
|
||||
font-size: var(--font-size-mini);
|
||||
color: var(--text-color-light);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.data span {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
white-space: nowrap;
|
||||
padding: var(--spacer) calc(var(--spacer) / 1.5);
|
||||
border-bottom: 1px dashed var(--border-color);
|
||||
}
|
||||
|
||||
.data span svg {
|
||||
display: block;
|
||||
margin-bottom: calc(var(--spacer) / 8);
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.data span:first-child {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.data {
|
||||
margin-bottom: 0;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.data span {
|
||||
border-left: 1px dashed var(--border-color);
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.data span:first-child {
|
||||
flex-basis: auto;
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.map {
|
||||
composes: breakout from '../Layout.module.css';
|
||||
composes: frame from './Image.module.css';
|
||||
height: 180px;
|
||||
margin-top: calc(var(--spacer) * 2);
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.map {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.map img {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.map > div:first-child {
|
||||
background: none !important;
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
.exif {
|
||||
margin-top: -($spacer * $line-height);
|
||||
margin-bottom: $spacer * 2;
|
||||
}
|
||||
|
||||
.data {
|
||||
@include breakoutviewport;
|
||||
|
||||
font-size: $font-size-mini;
|
||||
color: $brand-grey-light;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
flex: 1 1 20%;
|
||||
white-space: nowrap;
|
||||
padding: $spacer;
|
||||
border-bottom: 1px dashed $brand-grey-dimmed;
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
margin-bottom: $spacer / 8;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
:global(.dark) & {
|
||||
border-bottom-color: rgba($brand-grey, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $screen-sm) {
|
||||
margin-bottom: 0;
|
||||
font-size: $font-size-small;
|
||||
|
||||
span {
|
||||
border-left: 1px dashed $brand-grey-dimmed;
|
||||
border-bottom: 0;
|
||||
|
||||
&,
|
||||
&:first-child {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
:global(.dark) & {
|
||||
border-left-color: rgba($brand-grey, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.map {
|
||||
@include breakoutviewport;
|
||||
@include media-frame;
|
||||
|
||||
overflow: hidden;
|
||||
height: 220px;
|
||||
margin-top: $spacer * 2;
|
||||
|
||||
@media (min-width: $screen-sm) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
> div:first-child {
|
||||
background: none !important;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import ExifMap from './ExifMap'
|
||||
import styles from './Exif.module.scss'
|
||||
import { exif as styleExif, data, map } from './Exif.module.css'
|
||||
import { Exif as ExifMeta } from '../../@types/Image'
|
||||
import Icon from './Icon'
|
||||
|
||||
@ -31,8 +31,8 @@ export default function Exif({ exif }: { exif: ExifMeta }): ReactElement {
|
||||
} = exif.formatted
|
||||
|
||||
return (
|
||||
<aside className={styles.exif}>
|
||||
<div className={styles.data}>
|
||||
<aside className={styleExif}>
|
||||
<div className={data}>
|
||||
{model && <ExifData title="Camera model" value={model} icon="Camera" />}
|
||||
{focalLength && (
|
||||
<ExifData title="Focal length" value={focalLength} icon="Crosshair" />
|
||||
@ -49,7 +49,7 @@ export default function Exif({ exif }: { exif: ExifMeta }): ReactElement {
|
||||
{iso && <ExifData title="ISO" value={iso} icon="Maximize" />}
|
||||
</div>
|
||||
{gps && gps.latitude && (
|
||||
<div className={styles.map}>
|
||||
<div className={map}>
|
||||
<ExifMap gps={gps} />
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import Map from 'pigeon-maps'
|
||||
import { Map } from 'pigeon-maps'
|
||||
import Marker from 'pigeon-marker'
|
||||
import useDarkMode from 'use-dark-mode'
|
||||
import useDarkMode from '../../hooks/useDarkMode'
|
||||
|
||||
const mapbox = (mapboxId: string) => (
|
||||
x: string,
|
||||
@ -23,10 +23,7 @@ export default function ExifMap({
|
||||
}: {
|
||||
gps: { latitude: string; longitude: string }
|
||||
}): ReactElement {
|
||||
const { value } = useDarkMode(false, {
|
||||
classNameDark: 'dark',
|
||||
classNameLight: 'light'
|
||||
})
|
||||
const { value } = useDarkMode()
|
||||
const isDarkMode = value
|
||||
const [zoom, setZoom] = useState(12)
|
||||
|
||||
@ -40,7 +37,7 @@ export default function ExifMap({
|
||||
<Map
|
||||
center={[latitude, longitude]}
|
||||
zoom={zoom}
|
||||
height={220}
|
||||
height={180}
|
||||
dprs={[1, 2]}
|
||||
attribution={false}
|
||||
provider={isDarkMode ? providers['dark'] : providers['light']}
|
||||
|
70
src/components/atoms/Hamburger.module.css
Normal file
70
src/components/atoms/Hamburger.module.css
Normal file
@ -0,0 +1,70 @@
|
||||
.hamburger {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
transform: rotate(0deg);
|
||||
cursor: pointer;
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.line {
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
border-bottom: var(--stroke-width) solid var(--text-color-light);
|
||||
opacity: 1;
|
||||
left: 0;
|
||||
transform: rotate(0deg);
|
||||
transition: 0.2s var(--easing);
|
||||
}
|
||||
|
||||
.line:nth-child(1) {
|
||||
top: 0;
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
.line:nth-child(2) {
|
||||
top: 7px;
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
.line:nth-child(3) {
|
||||
top: 14px;
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
/* open state */
|
||||
:global(.has-menu-open) .line:nth-child(1) {
|
||||
transform: rotate(45deg);
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
:global(.has-menu-open) .line:nth-child(2) {
|
||||
width: 0%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
:global(.has-menu-open) .line:nth-child(3) {
|
||||
transform: rotate(-45deg);
|
||||
top: 16px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: calc(var(--spacer) / 2);
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
margin-right: -1rem;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.button:hover .line,
|
||||
.button:focus .line {
|
||||
border-color: var(--link-color);
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
.hamburger {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
transform: rotate(0deg);
|
||||
cursor: pointer;
|
||||
margin-top: $spacer / 2;
|
||||
}
|
||||
|
||||
.hamburgerLine {
|
||||
@include transition;
|
||||
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
border-bottom: $stroke-width solid $brand-grey-light;
|
||||
opacity: 1;
|
||||
left: 0;
|
||||
transform: rotate(0deg);
|
||||
|
||||
&:nth-child(1) {
|
||||
top: 0;
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
top: 7px;
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
top: 14px;
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
// open state
|
||||
:global(.has-menu-open) & {
|
||||
&:nth-child(1) {
|
||||
transform: rotate(45deg);
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 0%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
transform: rotate(-45deg);
|
||||
top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hamburgerButton {
|
||||
padding: $spacer / 2;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
margin-right: -($spacer / 2);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: 0;
|
||||
|
||||
.hamburgerLine {
|
||||
border-color: $brand-cyan;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Hamburger.module.scss'
|
||||
import { button, hamburger, line } from './Hamburger.module.css'
|
||||
|
||||
export default function Hamburger({
|
||||
onClick
|
||||
@ -7,16 +7,11 @@ export default function Hamburger({
|
||||
onClick(): void
|
||||
}): ReactElement {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
title="Menu"
|
||||
className={styles.hamburgerButton}
|
||||
onClick={onClick}
|
||||
>
|
||||
<span className={styles.hamburger}>
|
||||
<span className={styles.hamburgerLine} />
|
||||
<span className={styles.hamburgerLine} />
|
||||
<span className={styles.hamburgerLine} />
|
||||
<button type="button" title="Menu" className={button} onClick={onClick}>
|
||||
<span className={hamburger}>
|
||||
<span className={line} />
|
||||
<span className={line} />
|
||||
<span className={line} />
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
|
@ -1,10 +1,8 @@
|
||||
@import 'variables';
|
||||
|
||||
.icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
stroke: currentColor;
|
||||
stroke-width: $stroke-width;
|
||||
stroke-width: var(--stroke-width);
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
fill: none;
|
@ -27,7 +27,7 @@ import {
|
||||
import { ReactComponent as Jsonfeed } from '../../images/jsonfeed.svg'
|
||||
import { ReactComponent as Bitcoin } from '../../images/bitcoin.svg'
|
||||
import { ReactComponent as Stopwatch } from '../../images/stopwatch.svg'
|
||||
import styles from './Icon.module.scss'
|
||||
import { icon } from './Icon.module.css'
|
||||
|
||||
const components: any = {
|
||||
Download: ArrowDownCircle,
|
||||
@ -55,12 +55,12 @@ const components: any = {
|
||||
Crosshair
|
||||
}
|
||||
|
||||
const Icon = ({ name }: { name: string }): ReactElement => {
|
||||
const Icon = ({ name, ...props }: { name: string }): ReactElement => {
|
||||
const IconMapped = components[name]
|
||||
// const IconFeather = (Feather as any)[name]
|
||||
if (!IconMapped) return null
|
||||
|
||||
return <IconMapped className={styles.icon} />
|
||||
return <IconMapped className={icon} {...props} />
|
||||
}
|
||||
|
||||
export default Icon
|
||||
|
53
src/components/atoms/Image.module.css
Normal file
53
src/components/atoms/Image.module.css
Normal file
@ -0,0 +1,53 @@
|
||||
.frame {
|
||||
border: var(--stroke-width) solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: var(--border-radius);
|
||||
overflow: hidden;
|
||||
transition: 0.2s ease-out;
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
a:hover .frame,
|
||||
a:focus .frame {
|
||||
border-color: var(--link-color);
|
||||
}
|
||||
|
||||
.image {
|
||||
composes: frame;
|
||||
max-width: none;
|
||||
margin-top: calc(var(--spacer) * var(--line-height));
|
||||
margin-bottom: calc(var(--spacer) * var(--line-height));
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* single photo post teasers */
|
||||
.image:only-child {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.imageTitle {
|
||||
font-size: var(--font-size-h3);
|
||||
font-family: var(--font-family-headings);
|
||||
line-height: var(--line-height-headings);
|
||||
font-weight: var(--font-weight-headings);
|
||||
font-style: normal;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
padding: calc(var(--spacer) / 3) var(--spacer);
|
||||
background: var(--link-color);
|
||||
color: #fff !important;
|
||||
text-shadow: 0 1px 0 #000;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -20px, 0);
|
||||
transition: 0.2s var(--easing);
|
||||
}
|
||||
|
||||
a:hover .imageTitle,
|
||||
a:focus .imageTitle {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
.imageTitle {
|
||||
@include transition();
|
||||
|
||||
font-size: $font-size-h3;
|
||||
font-family: $font-family-headings;
|
||||
line-height: $line-height-headings;
|
||||
font-weight: $font-weight-headings;
|
||||
font-style: normal;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
padding: $spacer / 3 $spacer;
|
||||
background: rgba($link-color, 0.85);
|
||||
color: #fff !important;
|
||||
text-shadow: 0 1px 0 #000;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -20px, 0);
|
||||
}
|
||||
|
||||
.image {
|
||||
@include media-frame;
|
||||
@include breakoutviewport;
|
||||
|
||||
max-width: none;
|
||||
margin-top: $spacer * $line-height;
|
||||
margin-bottom: $spacer * $line-height;
|
||||
display: block;
|
||||
|
||||
a & {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
// single photo post teasers
|
||||
&:only-child {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
a:hover &,
|
||||
a:focus & {
|
||||
border-color: $link-color !important;
|
||||
|
||||
.imageTitle {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +1,22 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { graphql } from 'gatsby'
|
||||
import Img from 'gatsby-image'
|
||||
import { GatsbyImage } from 'gatsby-plugin-image'
|
||||
import { ImageProps } from '../../@types/Image'
|
||||
import styles from './Image.module.scss'
|
||||
import { image as styleImage, imageTitle } from './Image.module.css'
|
||||
|
||||
export const Image = ({
|
||||
title,
|
||||
fluid,
|
||||
fixed,
|
||||
image,
|
||||
alt,
|
||||
original
|
||||
original,
|
||||
className
|
||||
}: ImageProps): ReactElement => (
|
||||
<figure className={styles.image} data-original={original && original.src}>
|
||||
<Img backgroundColor="transparent" fluid={fluid} fixed={fixed} alt={alt} />
|
||||
{title && <figcaption className={styles.imageTitle}>{title}</figcaption>}
|
||||
<figure
|
||||
className={`${styleImage} ${className ? className : ''}`}
|
||||
data-original={original?.src}
|
||||
>
|
||||
<GatsbyImage image={image} alt={alt} />
|
||||
{title && <figcaption className={imageTitle}>{title}</figcaption>}
|
||||
</figure>
|
||||
)
|
||||
|
||||
@ -22,9 +25,7 @@ export const imageSizeDefault = graphql`
|
||||
original {
|
||||
src
|
||||
}
|
||||
fluid(maxWidth: 940, quality: 85) {
|
||||
...GatsbyImageSharpFluid_withWebp_noBase64
|
||||
}
|
||||
gatsbyImageData(layout: CONSTRAINED, width: 1040, quality: 85)
|
||||
}
|
||||
`
|
||||
|
||||
@ -33,9 +34,13 @@ export const imageSizeThumb = graphql`
|
||||
original {
|
||||
src
|
||||
}
|
||||
fluid(maxWidth: 420, maxHeight: 140, quality: 85, cropFocus: CENTER) {
|
||||
...GatsbyImageSharpFluid_withWebp_noBase64
|
||||
}
|
||||
gatsbyImageData(
|
||||
layout: CONSTRAINED
|
||||
width: 480
|
||||
height: 180
|
||||
quality: 85
|
||||
transformOptions: { cropFocus: CENTER }
|
||||
)
|
||||
}
|
||||
`
|
||||
|
||||
@ -44,8 +49,12 @@ export const photoSizeThumb = graphql`
|
||||
original {
|
||||
src
|
||||
}
|
||||
fluid(maxWidth: 300, maxHeight: 300, quality: 85, cropFocus: CENTER) {
|
||||
...GatsbyImageSharpFluid_withWebp_noBase64
|
||||
}
|
||||
gatsbyImageData(
|
||||
layout: CONSTRAINED
|
||||
width: 316
|
||||
height: 316
|
||||
quality: 85
|
||||
transformOptions: { cropFocus: CENTER }
|
||||
)
|
||||
}
|
||||
`
|
||||
|
32
src/components/atoms/Input.module.css
Normal file
32
src/components/atoms/Input.module.css
Normal file
@ -0,0 +1,32 @@
|
||||
.input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--padding-base-vertical) var(--padding-base-horizontal);
|
||||
font-size: var(--input-font-size);
|
||||
font-weight: var(--input-font-weight);
|
||||
line-height: var(--line-height);
|
||||
color: var(--input-color);
|
||||
background-color: var(--input-bg);
|
||||
background-image: none;
|
||||
border: var(--stroke-width) solid transparent;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: none;
|
||||
transition: all ease-in-out 0.15s;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.input::-moz-placeholder,
|
||||
.input::-webkit-input-placeholder,
|
||||
.input:-ms-input-placeholder {
|
||||
color: var(--input-color-placeholder);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
border-color: var(--input-border-focus);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.input[disabled] {
|
||||
color: var(--text-color-dimmed);
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
.input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: $padding-base-vertical $padding-base-horizontal;
|
||||
font-size: $input-font-size;
|
||||
font-weight: $input-font-weight;
|
||||
line-height: $line-height;
|
||||
color: $input-color;
|
||||
background-color: $input-bg;
|
||||
background-image: none; // Reset unusual Firefox-on-Android default style
|
||||
border: 0;
|
||||
border-radius: $input-border-radius;
|
||||
box-shadow: none;
|
||||
transition: all ease-in-out 0.15s;
|
||||
appearance: none;
|
||||
|
||||
@include placeholder();
|
||||
|
||||
:global(.dark) & {
|
||||
color: $input-color--dark;
|
||||
background-color: $input-bg--dark;
|
||||
@include placeholder($input-color-placeholder--dark);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: $input-border-focus;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
color: $brand-grey-dimmed;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './Input.module.scss'
|
||||
import { input } from './Input.module.css'
|
||||
|
||||
export default function Input(props: any): ReactElement {
|
||||
return <input className={styles.input} {...props} />
|
||||
export default function Input({ className, ...props }: any): ReactElement {
|
||||
return <input className={`${input} ${className || ''}`} {...props} />
|
||||
}
|
||||
|
17
src/components/atoms/Qr.module.css
Normal file
17
src/components/atoms/Qr.module.css
Normal file
@ -0,0 +1,17 @@
|
||||
.qr {
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.code {
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
padding-right: 2rem;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.code code {
|
||||
padding: calc(var(--spacer) / 2);
|
||||
font-size: 0.65rem;
|
||||
text-align: center;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
@import 'variables';
|
||||
|
||||
.qr {
|
||||
margin-bottom: $spacer / 2;
|
||||
}
|
||||
|
||||
.code {
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
padding-right: 2rem;
|
||||
width: fit-content;
|
||||
|
||||
code {
|
||||
padding: $spacer / 2;
|
||||
font-size: 0.65rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import React, { Suspense } from 'react'
|
||||
import { render, fireEvent, waitForElement } from '@testing-library/react'
|
||||
import { render, fireEvent, waitFor } from '@testing-library/react'
|
||||
|
||||
import Qr from './Qr'
|
||||
|
||||
@ -10,10 +10,8 @@ describe('Qr', () => {
|
||||
<Qr address="xxx" />
|
||||
</Suspense>
|
||||
)
|
||||
const lazyElement = await waitForElement(() =>
|
||||
container.querySelector('button')
|
||||
)
|
||||
expect(lazyElement).toBeInTheDocument()
|
||||
expect(container.firstChild).toBeInTheDocument()
|
||||
await waitFor(() => container.querySelector('button'))
|
||||
fireEvent.click(container.querySelector('button'))
|
||||
})
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { QRCode } from 'react-qr-svg'
|
||||
import styles from './Qr.module.scss'
|
||||
import { qr, code } from './Qr.module.css'
|
||||
import Copy from './Copy'
|
||||
|
||||
export default function Qr({
|
||||
@ -19,10 +19,10 @@ export default function Qr({
|
||||
level="Q"
|
||||
style={{ width: 120 }}
|
||||
value={address}
|
||||
className={styles.qr}
|
||||
className={qr}
|
||||
/>
|
||||
|
||||
<pre className={styles.code}>
|
||||
<pre className={code}>
|
||||
<code>{address}</code>
|
||||
<Copy text={address} />
|
||||
</pre>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
|
||||
import { getSrc } from 'gatsby-plugin-image'
|
||||
import { useSiteMetadata } from '../../../hooks/use-site-metadata'
|
||||
import { Post } from '../../../@types/Post'
|
||||
import MetaTags from './MetaTags'
|
||||
@ -39,9 +39,7 @@ export default function SEO({
|
||||
const postMeta = post.frontmatter
|
||||
title = `${postMeta.title} ¦ ${siteTitle}`
|
||||
description = postMeta.description ? postMeta.description : post.excerpt
|
||||
image = postMeta.image
|
||||
? postMeta.image.childImageSharp.fluid.src
|
||||
: `/${logo}`
|
||||
image = postMeta.image ? getSrc(postMeta.image) : `/${logo}`
|
||||
postURL = `${siteUrl}${slug}`
|
||||
} else {
|
||||
title = `${siteTitle} ¦ ${siteDescription}`
|
||||
|
20
src/components/atoms/Tag.module.css
Normal file
20
src/components/atoms/Tag.module.css
Normal file
@ -0,0 +1,20 @@
|
||||
.tag {
|
||||
color: var(--text-color-light);
|
||||
margin-left: calc(var(--spacer) / 2);
|
||||
margin-right: calc(var(--spacer) / 2);
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tag::before {
|
||||
content: '#';
|
||||
margin-right: 1px;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: var(--font-size-small);
|
||||
margin-left: calc(var(--spacer) / 6);
|
||||
opacity: 0.65;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
@import 'variables';
|
||||
|
||||
.tag {
|
||||
color: $brand-grey-light;
|
||||
margin-left: $spacer / 2;
|
||||
margin-right: $spacer / 2;
|
||||
margin-bottom: $spacer / 2;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
|
||||
&::before {
|
||||
content: '#';
|
||||
margin-right: 1px;
|
||||
opacity: 0.65;
|
||||
}
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: $font-size-small;
|
||||
margin-left: $spacer / 6;
|
||||
opacity: 0.65;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { Link } from 'gatsby'
|
||||
import styles from './Tag.module.scss'
|
||||
import { tag, count as styleCount } from './Tag.module.css'
|
||||
|
||||
export default function Tag({
|
||||
name,
|
||||
@ -14,9 +14,9 @@ export default function Tag({
|
||||
style?: any
|
||||
}): ReactElement {
|
||||
return (
|
||||
<Link className={styles.tag} to={url} style={style}>
|
||||
<Link className={tag} to={url} style={style}>
|
||||
{name}
|
||||
{count && <span className={styles.count}>{count}</span>}
|
||||
{count && <span className={styleCount}>{count}</span>}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
45
src/components/molecules/Menu.module.css
Normal file
45
src/components/molecules/Menu.module.css
Normal file
@ -0,0 +1,45 @@
|
||||
.menu {
|
||||
margin-top: 0;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menu ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.menu li {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.menu li::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu a {
|
||||
color: var(--text-color);
|
||||
line-height: 1;
|
||||
text-transform: uppercase;
|
||||
margin: 0 calc(var(--spacer) / 4);
|
||||
font-size: var(--font-size-small);
|
||||
text-shadow: 0 1px 0 rgba(255 255 255 0.5);
|
||||
padding: var(--padding-base-horizontal);
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menu::-webkit-scrollbar,
|
||||
.menu::-moz-scrollbar,
|
||||
.menu::-webkit-scrollbar-thumb,
|
||||
.menu::-webkit-scrollbar-track {
|
||||
display: none;
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
.menu {
|
||||
@include divider-top;
|
||||
|
||||
margin-top: 0;
|
||||
padding-top: $spacer / 2;
|
||||
position: absolute;
|
||||
top: calc(100% + #{$spacer / 1.5});
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $text-color;
|
||||
line-height: 1;
|
||||
text-transform: uppercase;
|
||||
margin: 0 $spacer / 4;
|
||||
font-size: $font-size-small;
|
||||
text-shadow: 0 1px 0 rgba(#fff, 0.5);
|
||||
padding: $padding-base-horizontal;
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $link-color-hover;
|
||||
}
|
||||
|
||||
:global(.dark) & {
|
||||
color: $text-color--dark;
|
||||
text-shadow: 0 -1px 0 rgba(#000, 0.5);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $link-color-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar,
|
||||
&::-moz-scrollbar,
|
||||
&::-webkit-scrollbar-thumb,
|
||||
&::-webkit-scrollbar-track {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ import React, { ReactElement, useState } from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { Link } from 'gatsby'
|
||||
import Hamburger from '../atoms/Hamburger'
|
||||
import styles from './Menu.module.scss'
|
||||
import { menu as styleMenu } from './Menu.module.css'
|
||||
import { useSiteMetadata } from '../../hooks/use-site-metadata'
|
||||
import { MenuItem } from '../../@types/Site'
|
||||
|
||||
@ -10,17 +10,19 @@ export default function Menu(): ReactElement {
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const { menu } = useSiteMetadata()
|
||||
|
||||
const toggleMenu = () => {
|
||||
function toggleMenu(): void {
|
||||
setMenuOpen(!menuOpen)
|
||||
}
|
||||
|
||||
const MenuItems = menu.map((item: MenuItem) => (
|
||||
<li key={item.title}>
|
||||
<Link onClick={toggleMenu} to={item.link}>
|
||||
{item.title}
|
||||
</Link>
|
||||
</li>
|
||||
))
|
||||
const MenuItems = menu.map(
|
||||
(item: MenuItem): JSX.Element => (
|
||||
<li key={item.title}>
|
||||
<Link onClick={toggleMenu} to={item.link}>
|
||||
{item.title}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -28,7 +30,7 @@ export default function Menu(): ReactElement {
|
||||
<html className={menuOpen ? 'has-menu-open' : undefined} lang="en" />
|
||||
</Helmet>
|
||||
<Hamburger onClick={toggleMenu} />
|
||||
<nav className={styles.menu}>
|
||||
<nav className={styleMenu}>
|
||||
<ul>{MenuItems}</ul>
|
||||
</nav>
|
||||
</>
|
||||
|
25
src/components/molecules/Networks.module.css
Normal file
25
src/components/molecules/Networks.module.css
Normal file
@ -0,0 +1,25 @@
|
||||
.link {
|
||||
text-align: center;
|
||||
width: 2rem;
|
||||
padding: calc(var(--spacer) / 4);
|
||||
margin-left: calc(var(--spacer) / 4);
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
display: inline-block;
|
||||
color: var(--text-color-light);
|
||||
}
|
||||
|
||||
.link:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.link:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.link svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transition: stroke 0.3s ease-in-out;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
@import 'variables';
|
||||
|
||||
.link {
|
||||
text-align: center;
|
||||
width: 2rem;
|
||||
padding: $spacer / 4;
|
||||
margin-left: $spacer / 4;
|
||||
margin-right: $spacer / 4;
|
||||
display: inline-block;
|
||||
color: $text-color-light;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transition: stroke 0.3s ease-in-out;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import Icon from '../atoms/Icon'
|
||||
import styles from './Networks.module.scss'
|
||||
import { link as styleLink } from './Networks.module.css'
|
||||
|
||||
function NetworkIcon({ link }: { link: string }) {
|
||||
let IconComp
|
||||
@ -28,7 +28,7 @@ export default function IconLinks({
|
||||
return (
|
||||
<p>
|
||||
{links.map((link: string) => (
|
||||
<a key={link} className={styles.link} href={link} title={link}>
|
||||
<a key={link} className={styleLink} href={link} title={link}>
|
||||
<NetworkIcon link={link} />
|
||||
</a>
|
||||
))}
|
||||
|
41
src/components/molecules/Pagination.module.css
Normal file
41
src/components/molecules/Pagination.module.css
Normal file
@ -0,0 +1,41 @@
|
||||
.pagination {
|
||||
margin-top: calc(var(--spacer) * 3);
|
||||
margin-bottom: var(--spacer);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.number {
|
||||
text-align: center;
|
||||
padding: calc(var(--spacer) / 6) calc(var(--spacer) / 2);
|
||||
border: 1px solid var(--text-color-dimmed);
|
||||
font-variant-numeric: lining-nums;
|
||||
margin-left: -1px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2.5rem;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.number:hover,
|
||||
.number:focus {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.number:first-child {
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.number:last-child {
|
||||
border-top-right-radius: var(--border-radius);
|
||||
border-bottom-right-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.current {
|
||||
composes: number;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
color: var(--text-color-dimmed);
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
.pagination {
|
||||
@include breakoutviewport;
|
||||
|
||||
margin-top: $spacer * 3;
|
||||
margin-bottom: $spacer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.number {
|
||||
text-align: center;
|
||||
padding: $spacer / 6 $spacer / 2;
|
||||
border: 1px solid $brand-grey-dimmed;
|
||||
font-variant-numeric: lining-nums;
|
||||
margin-left: -1px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2.5rem;
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
color: $brand-grey;
|
||||
|
||||
:global(.dark) & {
|
||||
color: $brand-grey-dimmed;
|
||||
border-color: darken($body-background-color--dark, 5%);
|
||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: $border-radius;
|
||||
border-bottom-left-radius: $border-radius;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: $border-radius;
|
||||
border-bottom-right-radius: $border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
.current {
|
||||
composes: number;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
color: $brand-grey-dimmed;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
|
||||
:global(.dark) & {
|
||||
color: $brand-grey-light;
|
||||
}
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { Link } from 'gatsby'
|
||||
import styles from './Pagination.module.scss'
|
||||
import shortid from 'shortid'
|
||||
import { PageContext } from '../../@types/Post'
|
||||
import Icon from '../atoms/Icon'
|
||||
import {
|
||||
current as styleCurrent,
|
||||
number as styleNumber,
|
||||
pagination
|
||||
} from './Pagination.module.css'
|
||||
|
||||
const PageNumber = ({
|
||||
function PageNumber({
|
||||
i,
|
||||
slug,
|
||||
current
|
||||
@ -13,8 +17,8 @@ const PageNumber = ({
|
||||
i: number
|
||||
slug: string
|
||||
current?: boolean
|
||||
}) => {
|
||||
const classes = current ? styles.current : styles.number
|
||||
}): JSX.Element {
|
||||
const classes = current ? styleCurrent : styleNumber
|
||||
const link = i === 0 ? slug : `${slug}page/${i + 1}`
|
||||
|
||||
return (
|
||||
@ -30,13 +34,13 @@ function PrevNext({
|
||||
}: {
|
||||
prevPagePath?: string
|
||||
nextPagePath?: string
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
const link = prevPagePath || nextPagePath
|
||||
const rel = prevPagePath ? 'prev' : 'next'
|
||||
const title = prevPagePath ? 'Newer Posts' : 'Older Posts'
|
||||
|
||||
return (
|
||||
<Link to={link} rel={rel} title={title} className={styles.number}>
|
||||
<Link to={link} rel={rel} title={title} className={styleNumber}>
|
||||
{prevPagePath ? (
|
||||
<Icon name="ChevronLeft" />
|
||||
) : (
|
||||
@ -62,7 +66,7 @@ export default function Pagination({
|
||||
const isLast = currentPageNumber === numPages
|
||||
|
||||
return (
|
||||
<div className={styles.pagination}>
|
||||
<div className={pagination}>
|
||||
{!isFirst && <PrevNext prevPagePath={prevPagePath} />}
|
||||
{Array.from({ length: numPages }, (_, i) => (
|
||||
<PageNumber
|
||||
|
6
src/components/molecules/PostDate.module.css
Normal file
6
src/components/molecules/PostDate.module.css
Normal file
@ -0,0 +1,6 @@
|
||||
.time {
|
||||
font-style: italic;
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--text-color-light);
|
||||
margin-bottom: calc(var(--spacer) / var(--line-height));
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
@import 'variables';
|
||||
|
||||
.time {
|
||||
font-style: italic;
|
||||
font-size: $font-size-small;
|
||||
color: $text-color-light;
|
||||
margin-bottom: $spacer / $line-height;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import Time from '../atoms/Time'
|
||||
import styles from './PostDate.module.scss'
|
||||
import { time } from './PostDate.module.css'
|
||||
|
||||
export default function PostDate({
|
||||
date,
|
||||
@ -10,7 +10,7 @@ export default function PostDate({
|
||||
updated?: string
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div className={styles.time}>
|
||||
<div className={time}>
|
||||
<Time date={date} />
|
||||
{updated && ' • updated '}
|
||||
{updated && <Time date={updated} />}
|
||||
|
@ -1,40 +1,40 @@
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
.title {
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
margin-top: $spacer / 2;
|
||||
margin-top: calc(var(--spacer) / 3);
|
||||
margin-bottom: 0;
|
||||
font-size: $font-size-base;
|
||||
font-size: var(--font-size-base);
|
||||
transition: color 0.2s ease-out;
|
||||
color: $text-color-light;
|
||||
}
|
||||
|
||||
.title + div {
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
|
||||
.post {
|
||||
display: block;
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
font-style: italic;
|
||||
font-size: $font-size-small;
|
||||
color: $text-color-light;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
.post:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post:hover .title {
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
.post figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.empty {
|
||||
@include media-frame;
|
||||
|
||||
composes: frame from '../atoms/Image.module.css';
|
||||
display: block;
|
||||
min-height: 95px;
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAACaADAAQAAAABAAAACQAAAAAvQpmhAAAAHElEQVQYGWNgoBL4T8gcggoIGcBA0ASCCmhsBQBhFwX7u70C8QAAAABJRU5ErkJggg==);
|
||||
|
||||
a:hover & {
|
||||
border-color: $link-color;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover .empty {
|
||||
border-color: var(--link-color);
|
||||
}
|
@ -3,7 +3,11 @@ import { Link, graphql } from 'gatsby'
|
||||
import { Image } from '../atoms/Image'
|
||||
import { Post } from '../../@types/Post'
|
||||
import PostTitle from '../templates/Post/Title'
|
||||
import styles from './PostTeaser.module.scss'
|
||||
import {
|
||||
post as stylePost,
|
||||
empty,
|
||||
title as styleTitle
|
||||
} from './PostTeaser.module.css'
|
||||
|
||||
export const postTeaserQuery = graphql`
|
||||
fragment PostTeaser on MarkdownRemark {
|
||||
@ -42,21 +46,24 @@ export default function PostTeaser({
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={styles.post}
|
||||
className={stylePost}
|
||||
to={slug}
|
||||
onClick={toggleSearch && toggleSearch}
|
||||
>
|
||||
{image ? (
|
||||
<Image fluid={image.childImageSharp.fluid} alt={title} />
|
||||
<Image
|
||||
image={(image as any).childImageSharp.gatsbyImageData}
|
||||
alt={title}
|
||||
/>
|
||||
) : (
|
||||
<span className={styles.empty} />
|
||||
<span className={empty} />
|
||||
)}
|
||||
|
||||
<PostTitle
|
||||
title={title}
|
||||
date={hideDate ? null : date}
|
||||
updated={updated}
|
||||
className={styles.title}
|
||||
className={styleTitle}
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
|
61
src/components/molecules/RelatedPosts.module.css
Normal file
61
src/components/molecules/RelatedPosts.module.css
Normal file
@ -0,0 +1,61 @@
|
||||
.title {
|
||||
font-size: var(--font-size-h3);
|
||||
margin-bottom: var(--spacer);
|
||||
}
|
||||
|
||||
.relatedPosts ul {
|
||||
display: grid;
|
||||
gap: calc(var(--spacer) / 2);
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.relatedPosts ul {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacer);
|
||||
}
|
||||
}
|
||||
|
||||
.relatedPosts li {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.relatedPosts li::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.relatedPosts figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.relatedPosts img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.relatedPosts a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.relatedPosts a > div {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.relatedPosts a h4 {
|
||||
color: var(--text-color-light);
|
||||
}
|
||||
|
||||
.relatedPosts a:hover h4,
|
||||
.relatedPosts a:focus h4 {
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: var(--font-size-mini);
|
||||
display: inline-block;
|
||||
color: var(--text-color-light);
|
||||
text-transform: uppercase;
|
||||
margin-left: calc(var(--spacer) / 2);
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
.title {
|
||||
font-size: $font-size-h3;
|
||||
margin-bottom: $spacer;
|
||||
}
|
||||
|
||||
.relatedPosts {
|
||||
margin-top: -($spacer * 2);
|
||||
|
||||
@media (min-width: $screen-md) {
|
||||
@include breakoutviewport;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: grid;
|
||||
gap: $spacer / 2;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
@media (min-width: $screen-sm) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: $spacer;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
display: block;
|
||||
margin: 0;
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
|
||||
> div {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: $text-color-light;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
h4 {
|
||||
color: $link-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: $font-size-mini;
|
||||
display: inline-block;
|
||||
color: $brand-grey-light;
|
||||
text-transform: uppercase;
|
||||
margin-left: $spacer / 2;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
import PostTeaser from './PostTeaser'
|
||||
import styles from './RelatedPosts.module.scss'
|
||||
import { relatedPosts, title, button } from './RelatedPosts.module.css'
|
||||
import { Post, Frontmatter } from '../../@types/Post'
|
||||
import { PhotoThumb } from '../templates/Photos'
|
||||
|
||||
@ -21,7 +21,7 @@ function postsWithDataFilter(
|
||||
posts: [{ node: Post }],
|
||||
key: keyof Frontmatter,
|
||||
valuesToFind: string[]
|
||||
) {
|
||||
): { node: Post }[] {
|
||||
const newArray = posts
|
||||
.filter(({ node }: { node: Post }) => {
|
||||
const frontmatterKey = node.frontmatter[key] as []
|
||||
@ -39,7 +39,7 @@ function postsWithDataFilter(
|
||||
return newArray
|
||||
}
|
||||
|
||||
function photosWithDataFilter(posts: []) {
|
||||
function photosWithDataFilter(posts: [{ node: Post }]): { node: Post }[] {
|
||||
const newArray = posts
|
||||
.filter((post: { node: Post }) => {
|
||||
const { fileAbsolutePath } = post.node
|
||||
@ -78,10 +78,10 @@ export default function RelatedPosts({
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className={styles.relatedPosts}>
|
||||
<h1 className={styles.title}>
|
||||
<aside className={relatedPosts}>
|
||||
<h1 className={title}>
|
||||
Related {isPhotos ? 'Photos' : 'Posts'}{' '}
|
||||
<button className={styles.button} onClick={() => refreshPosts()}>
|
||||
<button className={button} onClick={() => refreshPosts()}>
|
||||
Refresh
|
||||
</button>
|
||||
</h1>
|
||||
|
22
src/components/molecules/Search/SearchButton.module.css
Normal file
22
src/components/molecules/Search/SearchButton.module.css
Normal file
@ -0,0 +1,22 @@
|
||||
.searchButton {
|
||||
padding: calc(var(--spacer) / 2);
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.searchButton:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.searchButton svg {
|
||||
stroke: var(--text-color-light);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.searchButton:hover svg,
|
||||
.searchButton:focus svg {
|
||||
stroke: var(--link-color);
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
@import 'variables';
|
||||
|
||||
.searchButton {
|
||||
padding: $spacer / 2;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
margin-right: $spacer / 4;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
stroke: $brand-grey-light;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
svg {
|
||||
stroke: $brand-cyan;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
svg {
|
||||
stroke: darken($brand-cyan, 30%);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './SearchButton.module.scss'
|
||||
import { searchButton } from './SearchButton.module.css'
|
||||
import Icon from '../../atoms/Icon'
|
||||
|
||||
const SearchButton = (props: any): ReactElement => (
|
||||
const SearchButton = ({ onClick }: { onClick: () => void }): ReactElement => (
|
||||
<button
|
||||
type="button"
|
||||
title="Search"
|
||||
className={styles.searchButton}
|
||||
{...props}
|
||||
className={searchButton}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Icon name="Search" />
|
||||
</button>
|
||||
|
20
src/components/molecules/Search/SearchInput.module.css
Normal file
20
src/components/molecules/Search/SearchInput.module.css
Normal file
@ -0,0 +1,20 @@
|
||||
.searchInput::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.searchInputClose {
|
||||
position: absolute;
|
||||
right: calc(var(--spacer) / 2);
|
||||
top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.searchInputClose svg {
|
||||
stroke: var(--brand-grey-light);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.searchInputClose:hover svg,
|
||||
.searchInputClose:focus svg {
|
||||
stroke: var(--link-color);
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
@import 'variables';
|
||||
|
||||
.searchInput {
|
||||
composes: input from '../../atoms/Input.module.scss';
|
||||
|
||||
&::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.searchInputClose {
|
||||
position: absolute;
|
||||
right: $spacer / 2;
|
||||
top: $spacer / 2;
|
||||
|
||||
svg {
|
||||
stroke: $brand-grey-light;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
svg {
|
||||
stroke: $link-color;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import Input from '../../atoms/Input'
|
||||
import Icon from '../../atoms/Icon'
|
||||
import styles from './SearchInput.module.scss'
|
||||
import { searchInput, searchInputClose } from './SearchInput.module.css'
|
||||
|
||||
export default function SearchInput({
|
||||
value,
|
||||
@ -15,7 +15,7 @@ export default function SearchInput({
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
className={styles.searchInput}
|
||||
className={searchInput}
|
||||
type="search"
|
||||
placeholder="Search everything"
|
||||
autoFocus // eslint-disable-line
|
||||
@ -23,7 +23,7 @@ export default function SearchInput({
|
||||
onChange={onChange}
|
||||
/>
|
||||
<button
|
||||
className={styles.searchInputClose}
|
||||
className={searchInputClose}
|
||||
onClick={onToggle}
|
||||
title="Close search"
|
||||
>
|
||||
|
72
src/components/molecules/Search/SearchResults.module.css
Normal file
72
src/components/molecules/Search/SearchResults.module.css
Normal file
@ -0,0 +1,72 @@
|
||||
.searchResults {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: var(--body-background-color);
|
||||
animation: fadein 0.3s;
|
||||
overflow: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
height: 91vh;
|
||||
}
|
||||
|
||||
.results {
|
||||
composes: container from '../../Layout.module.css';
|
||||
padding: var(--spacer) calc(var(--spacer) / 2);
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@media (min-width: 60rem) {
|
||||
.results {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.results li {
|
||||
display: block;
|
||||
flex: 0 0 48%;
|
||||
margin-bottom: var(--spacer);
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.results li {
|
||||
flex-basis: 31%;
|
||||
}
|
||||
}
|
||||
|
||||
.results li::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.results img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.results a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.results a > div {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.results a:hover h4,
|
||||
.results a:focus h4 {
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
|
||||
.searchResults {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: rgba($body-background-color, 0.95);
|
||||
backdrop-filter: blur(5px);
|
||||
animation: fadein 0.3s;
|
||||
overflow: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
height: 91vh;
|
||||
|
||||
:global(.dark) & {
|
||||
background: rgba($body-background-color--dark, 0.95);
|
||||
}
|
||||
|
||||
ul {
|
||||
@include breakoutviewport;
|
||||
|
||||
padding: $spacer $spacer / 2;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
@media (min-width: $screen-md) {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: block;
|
||||
flex: 0 0 48%;
|
||||
margin-bottom: $spacer;
|
||||
|
||||
@media (min-width: $screen-sm) {
|
||||
flex-basis: 31%;
|
||||
}
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
|
||||
> div {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
h4 {
|
||||
color: $link-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
import Container from '../../atoms/Container'
|
||||
import PostTeaser from '../PostTeaser'
|
||||
import SearchResultsEmpty from './SearchResultsEmpty'
|
||||
import styles from './SearchResults.module.scss'
|
||||
import {
|
||||
searchResults,
|
||||
results as styleResults
|
||||
} from './SearchResults.module.css'
|
||||
import { Post } from '../../../@types/Post'
|
||||
|
||||
export interface Results {
|
||||
@ -35,26 +37,24 @@ function SearchResultsPure({
|
||||
toggleSearch(): void
|
||||
}) {
|
||||
return (
|
||||
<div className={styles.searchResults}>
|
||||
<Container>
|
||||
{results.length > 0 ? (
|
||||
<ul>
|
||||
{results.map((page: { slug: string }) =>
|
||||
posts
|
||||
.filter(
|
||||
({ node }: { node: Post }) => node.fields.slug === page.slug
|
||||
)
|
||||
.map(({ node }: { node: Post }) => (
|
||||
<li key={page.slug}>
|
||||
<PostTeaser post={node} toggleSearch={toggleSearch} />
|
||||
</li>
|
||||
))
|
||||
)}
|
||||
</ul>
|
||||
) : (
|
||||
<SearchResultsEmpty searchQuery={searchQuery} results={results} />
|
||||
)}
|
||||
</Container>
|
||||
<div className={searchResults}>
|
||||
{results.length > 0 ? (
|
||||
<ul className={styleResults}>
|
||||
{results.map((page: { slug: string }) =>
|
||||
posts
|
||||
.filter(
|
||||
({ node }: { node: Post }) => node.fields.slug === page.slug
|
||||
)
|
||||
.map(({ node }: { node: Post }) => (
|
||||
<li key={page.slug}>
|
||||
<PostTeaser post={node} toggleSearch={toggleSearch} />
|
||||
</li>
|
||||
))
|
||||
)}
|
||||
</ul>
|
||||
) : (
|
||||
<SearchResultsEmpty searchQuery={searchQuery} results={results} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
.empty {
|
||||
padding-top: 15vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.emptyMessage {
|
||||
color: var(--text-color-light);
|
||||
}
|
||||
|
||||
.emptyMessageText {
|
||||
margin-bottom: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.emptyMessageText::after {
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
animation: ellipsis steps(4, end) 1s infinite;
|
||||
|
||||
/* ascii code for the ellipsis character */
|
||||
content: '\2026';
|
||||
width: 0;
|
||||
position: absolute;
|
||||
left: 101%;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
@keyframes ellipsis {
|
||||
to {
|
||||
width: 1rem;
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
@import 'variables';
|
||||
|
||||
.empty {
|
||||
padding-top: 15vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.emptyMessage {
|
||||
color: $brand-grey-light;
|
||||
}
|
||||
|
||||
.emptyMessageText {
|
||||
margin-bottom: 0;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
animation: ellipsis steps(4, end) 1s infinite;
|
||||
content: '\2026'; // ascii code for the ellipsis character
|
||||
width: 0;
|
||||
position: absolute;
|
||||
left: 101%;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ellipsis {
|
||||
to {
|
||||
width: 1rem;
|
||||
}
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './SearchResultsEmpty.module.scss'
|
||||
import {
|
||||
empty,
|
||||
emptyMessage,
|
||||
emptyMessageText
|
||||
} from './SearchResultsEmpty.module.css'
|
||||
import { Results } from './SearchResults'
|
||||
|
||||
const SearchResultsEmpty = ({
|
||||
@ -9,9 +13,9 @@ const SearchResultsEmpty = ({
|
||||
searchQuery: string
|
||||
results: Results[]
|
||||
}): ReactElement => (
|
||||
<div className={styles.empty}>
|
||||
<header className={styles.emptyMessage}>
|
||||
<p className={styles.emptyMessageText}>
|
||||
<div className={empty}>
|
||||
<header className={emptyMessage}>
|
||||
<p className={emptyMessageText}>
|
||||
{searchQuery.length > 0 && results.length === 0
|
||||
? 'No results found'
|
||||
: 'Awaiting your input'}
|
||||
|
46
src/components/molecules/Search/index.module.css
Normal file
46
src/components/molecules/Search/index.module.css
Normal file
@ -0,0 +1,46 @@
|
||||
.search {
|
||||
position: absolute;
|
||||
left: calc(var(--spacer) / 2);
|
||||
right: calc(var(--spacer) / 2);
|
||||
top: calc(var(--spacer) / 4);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.search input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.search {
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.appear,
|
||||
.enter {
|
||||
opacity: 0.01;
|
||||
transform: translate3d(0, -100px, 0);
|
||||
}
|
||||
|
||||
.appearActive,
|
||||
.enterActive {
|
||||
opacity: 1;
|
||||
transition: 0.2s ease-out;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.exit {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.exitActive {
|
||||
opacity: 0.01;
|
||||
transition: 0.2s ease-in;
|
||||
transform: translate3d(0, -100px, 0);
|
||||
}
|
||||
|
||||
:global(.hasSearchOpen) {
|
||||
overflow: hidden;
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
@import 'variables';
|
||||
|
||||
.search {
|
||||
position: absolute;
|
||||
left: $spacer / 2;
|
||||
right: $spacer / 2;
|
||||
top: $spacer / 4;
|
||||
z-index: 10;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-md) {
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.appear,
|
||||
.enter {
|
||||
opacity: 0.01;
|
||||
transform: translate3d(0, -100px, 0);
|
||||
|
||||
&.appearActive,
|
||||
&.enterActive {
|
||||
opacity: 1;
|
||||
transition: 0.2s ease-out;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.exit {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
|
||||
&.exitActive {
|
||||
opacity: 0.01;
|
||||
transition: 0.2s ease-in;
|
||||
transform: translate3d(0, -100px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.hasSearchOpen) {
|
||||
overflow: hidden;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user