List products on sidebar
27
client/components/Product.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* A collection of popular layouts and patterns made with CSS (https://csslayout.io)
|
||||||
|
* (c) 2019 - 2020 Nguyen Huu Phuoc <https://twitter.com/nghuuphuoc>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import './product.css';
|
||||||
|
import ProductModel from '../constants/ProductModel';
|
||||||
|
import slug from '../helpers/slug';
|
||||||
|
|
||||||
|
interface ProductProps {
|
||||||
|
product: ProductModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Product: React.FC<ProductProps> = ({ product }) => {
|
||||||
|
return (
|
||||||
|
<div className="product">
|
||||||
|
<a href={product.url}>
|
||||||
|
<img className="product__logo" src={`/assets/${slug(product.name)}.png`} alt={`${product.name} - ${product.description}`} />
|
||||||
|
<div className="product__desc">{product.description}</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Product;
|
22
client/components/product.css
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* A collection of popular layouts and patterns made with CSS (https://csslayout.io)
|
||||||
|
* (c) 2019 - 2020 Nguyen Huu Phuoc <https://twitter.com/nghuuphuoc>
|
||||||
|
*/
|
||||||
|
|
||||||
|
.product {
|
||||||
|
border: 1px solid rgba(0, 0, 0, .3);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.product a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.product__logo {
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.product__desc {
|
||||||
|
padding: 0.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
60
client/constants/ProductList.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import ProductModel from './ProductModel';
|
||||||
|
|
||||||
|
const ProductList: ProductModel[] = [
|
||||||
|
{
|
||||||
|
name: 'Blur Page',
|
||||||
|
url: 'https://blur.page',
|
||||||
|
description: 'A browser extension to hide sensitive information on a web page',
|
||||||
|
themeColor: '#4e7fb8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Form Validation',
|
||||||
|
url: 'https://formvalidation.io',
|
||||||
|
description: 'The best validation library for JavaScript',
|
||||||
|
themeColor: '#014ba6',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'React PDF Viewer',
|
||||||
|
url: 'https://react-pdf-viewer.dev',
|
||||||
|
description: 'A React component to view a PDF document',
|
||||||
|
themeColor: '#fb6303',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '1 LOC',
|
||||||
|
url: 'https://1loc.dev',
|
||||||
|
description: 'Favorite JavaScript utilities in single line of code',
|
||||||
|
themeColor: '#000200',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'CSS Layout',
|
||||||
|
url: 'https://csslayout.io',
|
||||||
|
description: 'A collection of popular layouts and patterns made with CSS',
|
||||||
|
themeColor: '#e7d900',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Front-end Tips',
|
||||||
|
url: 'https://getfrontend.tips',
|
||||||
|
description: 'Super tiny, quick tips, tricks and best practices of front-end development',
|
||||||
|
themeColor: '#2e2c74',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'HTML DOM',
|
||||||
|
url: 'https://htmldom.dev',
|
||||||
|
description: 'How to manage HTML DOM with vanilla JavaScript',
|
||||||
|
themeColor: '#5b5d8a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Responsive Design Patterns',
|
||||||
|
url: 'https://responsive.page',
|
||||||
|
description: 'A collection of patterns to create a responsive web page',
|
||||||
|
themeColor: '#43246d',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'this VS that',
|
||||||
|
url: 'https://thisthat.dev',
|
||||||
|
description: 'The differences between _ and _ in the front-end development',
|
||||||
|
themeColor: '#414293',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export { ProductList };
|
6
client/constants/ProductModel.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default interface ProductModel {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
description: string;
|
||||||
|
themeColor: string;
|
||||||
|
}
|
@@ -28,10 +28,24 @@ code {
|
|||||||
max-width: 64rem;
|
max-width: 64rem;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
.main {
|
.main {
|
||||||
margin: 4rem 0;
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.sidebar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sidebar */
|
||||||
|
.sidebar__inner {
|
||||||
|
position: sticky;
|
||||||
|
top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero */
|
||||||
.hero {
|
.hero {
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -59,3 +73,23 @@ code {
|
|||||||
margin: 2rem 0;
|
margin: 2rem 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.sidebar {
|
||||||
|
display: block;
|
||||||
|
flex: 0 0 10rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.sidebar {
|
||||||
|
flex-basis: 12rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.sidebar {
|
||||||
|
flex-basis: 16rem;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
}
|
@@ -9,7 +9,9 @@ import { Helmet } from 'react-helmet';
|
|||||||
import Ad from '../components/Ad';
|
import Ad from '../components/Ad';
|
||||||
import CoverCard from '../components/CoverCard';
|
import CoverCard from '../components/CoverCard';
|
||||||
import Heading from '../components/Heading';
|
import Heading from '../components/Heading';
|
||||||
|
import Product from '../components/Product';
|
||||||
import Pattern from '../constants/Pattern';
|
import Pattern from '../constants/Pattern';
|
||||||
|
import { ProductList } from '../constants/ProductList';
|
||||||
import useDocumentTitle from '../hooks/useDocumentTitle';
|
import useDocumentTitle from '../hooks/useDocumentTitle';
|
||||||
import Layout from '../layouts/Layout';
|
import Layout from '../layouts/Layout';
|
||||||
import './explorePage.css';
|
import './explorePage.css';
|
||||||
@@ -32,8 +34,8 @@ const ExplorePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<Ad />
|
<div className="content">
|
||||||
|
<main class="main">
|
||||||
<section>
|
<section>
|
||||||
<Heading title="Layout" />
|
<Heading title="Layout" />
|
||||||
|
|
||||||
@@ -159,6 +161,17 @@ const ExplorePage = () => {
|
|||||||
<CoverCard pattern={Pattern.ValidationIcon} />
|
<CoverCard pattern={Pattern.ValidationIcon} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div className="sidebar">
|
||||||
|
<div className="sidebar__inner">
|
||||||
|
<Ad />
|
||||||
|
{
|
||||||
|
ProductList.map(product => <Product key={product.name} product={product} />)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
BIN
public/assets/1-loc.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
public/assets/blur-page.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
public/assets/css-layout.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/assets/form-validation.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
public/assets/front-end-tips.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
public/assets/html-dom.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
public/assets/react-pdf-viewer.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
public/assets/responsive-design-patterns.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/assets/this-vs-that.png
Normal file
After Width: | Height: | Size: 24 KiB |