🧡 Initial commit with Sveltekit.
13
.eslintignore
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
20
.eslintrc.cjs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||||
|
plugins: ['svelte3', '@typescript-eslint'],
|
||||||
|
ignorePatterns: ['*.cjs'],
|
||||||
|
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||||
|
settings: {
|
||||||
|
'svelte3/typescript': () => require('typescript')
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 2020
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2017: true,
|
||||||
|
node: true
|
||||||
|
}
|
||||||
|
};
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["next/core-web-vitals"]
|
|
||||||
}
|
|
56
.gitignore
vendored
@ -1,48 +1,10 @@
|
|||||||
# dependencies ->
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
package-lock.json
|
|
||||||
|
|
||||||
# testing ->
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# next.js ->
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# production ->
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc ->
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.pem
|
node_modules
|
||||||
|
/build
|
||||||
# debug ->
|
/.svelte-kit
|
||||||
npm-debug.log*
|
/package
|
||||||
yarn-debug.log*
|
.env
|
||||||
yarn-error.log*
|
.env.*
|
||||||
.pnpm-debug.log*
|
!.env.example
|
||||||
|
vite.config.js.timestamp-*
|
||||||
# local env files ->
|
vite.config.ts.timestamp-*
|
||||||
.env*.local
|
|
||||||
|
|
||||||
# vercel ->
|
|
||||||
.vercel
|
|
||||||
|
|
||||||
# typescript ->
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# PWA files ->
|
|
||||||
**/public/sw.js
|
|
||||||
**/public/workbox-*.js
|
|
||||||
**/public/worker-*.js
|
|
||||||
**/public/sw.js.map
|
|
||||||
**/public/workbox-*.js.map
|
|
||||||
**/public/worker-*.js.map
|
|
||||||
|
|
||||||
# SWC files ->
|
|
||||||
.swc
|
|
||||||
|
|
||||||
# PNPM files ->
|
|
||||||
pnpm-lock.yaml
|
|
||||||
|
13
.prettierignore
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
9
.prettierrc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"pluginSearchDirs": ["."],
|
||||||
|
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||||
|
}
|
110
README.md
@ -1,108 +1,38 @@
|
|||||||
<p align="center">
|
# create-svelte
|
||||||
<a href="https://svgl.vercel.app/" target="_blank">
|
|
||||||
<img src="https://i.postimg.cc/1tzrP2rg/banner-corner.png" width="800px" alt="SVGL Banner" />
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## 📦 Packages:
|
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||||
|
|
||||||
- ⚡️ [Nextjs](https://nextjs.org/) - The React Framework for Production.
|
## Creating a project
|
||||||
- ⚒️ [React 18](https://reactjs.org/) - A JavaScript library for building user interfaces.
|
|
||||||
- 💙 [Typescript](https://www.typescriptlang.org/) - A superset of JavaScript.
|
|
||||||
- ✅ [Vitest](https://vitest.dev/) - A blazing fast unit test framework.
|
|
||||||
- 💅 [Chakra UI](https://chakra-ui.com/) - Create accessible React apps with speed.
|
|
||||||
- 💥 [Framer Motion](https://www.framer.com/motion/) - Production-ready motion library.
|
|
||||||
- 💖 [Phosphor Icons](https://phosphoricons.com/) - A flexible icon family for everyone.
|
|
||||||
- ⬇️ [Next-PWA](https://github.com/shadowwalker/next-pwa) - Zero config PWA plugin for Next.js, with workbox.
|
|
||||||
|
|
||||||
## 🚀 Getting started:
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
|
|
||||||
You need:
|
|
||||||
|
|
||||||
- [Node.js 16+ (recommend: 16.15.1 LTS)](https://nodejs.org/en/)
|
|
||||||
- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
|
||||||
|
|
||||||
1. Clone the repository:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git@github.com:pheralb/svgl.git
|
# create a new project in the current directory
|
||||||
|
npm create svelte@latest
|
||||||
|
|
||||||
|
# create a new project in my-app
|
||||||
|
npm create svelte@latest my-app
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install dependencies:
|
## Developing
|
||||||
|
|
||||||
```bash
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
npm install
|
|
||||||
# or
|
|
||||||
yarn install
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Run:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
# or
|
|
||||||
yarn dev
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Test & Build:
|
## Building
|
||||||
|
|
||||||
|
To create a production version of your app:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run ready
|
npm run build
|
||||||
# or
|
|
||||||
yarn ready
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [localhost:3000](localhost:3000) with your browser to see the result.
|
You can preview the production build with `npm run preview`.
|
||||||
|
|
||||||
## 🤔 Can I add my logo?
|
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
||||||
|
|
||||||
Yes! Here is a guide for you 🥳:
|
|
||||||
|
|
||||||
1. [Fork the repository](https://github.com/pheralb/svgl/fork).
|
|
||||||
|
|
||||||
2. Clone the forked repository:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git@github.com:YOUR_USERNAME/svgl.git
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Add the **.svg** logo here: [`/public/library`](https://github.com/pheralb/svgl/tree/main/public/library).
|
|
||||||
|
|
||||||
4. Add your logo information here following the structure: [`/data/svgs.json`](https://github.com/pheralb/svgl/blob/main/data/svgs.json).
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"slug": "/library/your_logo.svg",
|
|
||||||
"title": "Logo Title",
|
|
||||||
"category": "Logo Category",
|
|
||||||
"url": "Your Website / app url"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Create a commit and push:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add .
|
|
||||||
git commit -m "🥰 Added my logo"
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
6. Create a pull request with your changes and 🥳 ready.
|
|
||||||
|
|
||||||
## 🚂 Api endpoints:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
- /api/all: returns all the logos.
|
|
||||||
- /api/search?id=2: returns the logo with id 2.
|
|
||||||
- /api/search?q=logo: returns the logo with query.
|
|
||||||
```
|
|
||||||
|
|
||||||
## ⚒️ Shortcuts:
|
|
||||||
|
|
||||||
- ⭐ SVG Library: [/public/library/](https://github.com/pheralb/svgl/tree/main/public/library).
|
|
||||||
- ✍️ SVG JSON logos: [/data/](https://github.com/pheralb/svgl/tree/main/data).
|
|
||||||
|
|
||||||
## 🔑 License:
|
|
||||||
|
|
||||||
- [MIT](https://github.com/pheralb/svgl/blob/main/LICENSE).
|
|
||||||
|
5
next-env.d.ts
vendored
@ -1,5 +0,0 @@
|
|||||||
/// <reference types="next" />
|
|
||||||
/// <reference types="next/image-types/global" />
|
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
|
||||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
|
@ -1,57 +0,0 @@
|
|||||||
export default {
|
|
||||||
title: "A beautiful library with SVG logos",
|
|
||||||
titleTemplate: "%s - Svgl",
|
|
||||||
description: "Svgl is a library of free and open source SVG logos.",
|
|
||||||
defaultTitle: "svgl",
|
|
||||||
additionalLinkTags: [
|
|
||||||
{
|
|
||||||
rel: "icon",
|
|
||||||
href: "/icons/icon.ico",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rel: "apple-touch-icon",
|
|
||||||
href: "/icons/apple-touch-icon-180x180.png",
|
|
||||||
sizes: "180x180",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rel: "apple-touch-icon",
|
|
||||||
href: "/icons/apple-touch-icon-152x152.png",
|
|
||||||
sizes: "152x152",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rel: "apple-touch-icon",
|
|
||||||
href: "/icons/apple-touch-icon-114x114.png",
|
|
||||||
sizes: "114x114",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rel: "manifest",
|
|
||||||
href: "/manifest.json",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rel: "preload",
|
|
||||||
href: "/fonts/Inter-Regular.woff2",
|
|
||||||
as: "font",
|
|
||||||
type: "font/woff2",
|
|
||||||
crossOrigin: "anonymous",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
openGraph: {
|
|
||||||
site_name: "Svgl",
|
|
||||||
url: "https://svgl.vercel.app/",
|
|
||||||
type: "website",
|
|
||||||
locale: "en_IE",
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: "/images/banner.png",
|
|
||||||
width: 1920,
|
|
||||||
height: 1080,
|
|
||||||
type: "image/png",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
handle: "@pheralb_",
|
|
||||||
site: "@pheralb_",
|
|
||||||
cardType: "summary_large_image",
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,14 +0,0 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
|
||||||
const withPWA = require("next-pwa");
|
|
||||||
|
|
||||||
const nextConfig = withPWA({
|
|
||||||
reactStrictMode: true,
|
|
||||||
pwa: {
|
|
||||||
dest: "public",
|
|
||||||
register: true,
|
|
||||||
skipWaiting: true,
|
|
||||||
disable: process.env.NODE_ENV === "development",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = nextConfig;
|
|
85
package.json
@ -1,56 +1,33 @@
|
|||||||
{
|
{
|
||||||
"name": "svgl",
|
"name": "svgl",
|
||||||
"version": "2.0.1",
|
"version": "0.0.1",
|
||||||
"description": "A beautiful library with SVG logos.",
|
"private": true,
|
||||||
"private": true,
|
"scripts": {
|
||||||
"author": "@pheralb_",
|
"dev": "vite dev",
|
||||||
"license": "MIT",
|
"build": "vite build",
|
||||||
"keywords": [
|
"preview": "vite preview",
|
||||||
"svgs",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"logos",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"images",
|
"test:unit": "vitest",
|
||||||
"library"
|
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||||
],
|
"format": "prettier --plugin-search-dir . --write ."
|
||||||
"scripts": {
|
},
|
||||||
"dev": "next dev",
|
"devDependencies": {
|
||||||
"build": "next build",
|
"@sveltejs/adapter-auto": "^2.0.0",
|
||||||
"start": "next start",
|
"@sveltejs/kit": "^1.5.0",
|
||||||
"lint": "next lint",
|
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||||
"test": "vitest",
|
"@typescript-eslint/parser": "^5.45.0",
|
||||||
"ready": "vitest && next build"
|
"eslint": "^8.28.0",
|
||||||
},
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"dependencies": {
|
"eslint-plugin-svelte3": "^4.0.0",
|
||||||
"@chakra-ui/react": "2.2.4",
|
"prettier": "^2.8.0",
|
||||||
"@emotion/react": "11.10.0",
|
"prettier-plugin-svelte": "^2.8.1",
|
||||||
"@emotion/styled": "11.10.0",
|
"svelte": "^3.54.0",
|
||||||
"@uiball/loaders": "1.2.6",
|
"svelte-check": "^3.0.1",
|
||||||
"canvas-confetti": "1.5.1",
|
"tslib": "^2.4.1",
|
||||||
"downloadjs": "1.4.7",
|
"typescript": "^4.9.3",
|
||||||
"framer-motion": "6.5.1",
|
"vite": "^4.0.0",
|
||||||
"next": "12.2.3",
|
"vitest": "^0.25.3"
|
||||||
"next-pwa": "5.5.4",
|
},
|
||||||
"next-seo": "5.5.0",
|
"type": "module"
|
||||||
"nextjs-progressbar": "0.0.14",
|
|
||||||
"phosphor-react": "1.4.1",
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0",
|
|
||||||
"react-hot-toast": "2.3.0",
|
|
||||||
"react-hotkeys-hook": "3.4.7",
|
|
||||||
"swr": "1.3.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@testing-library/jest-dom": "5.16.4",
|
|
||||||
"@testing-library/react": "13.3.0",
|
|
||||||
"@types/canvas-confetti": "1.4.3",
|
|
||||||
"@types/downloadjs": "1.4.3",
|
|
||||||
"@types/node": "18.6.3",
|
|
||||||
"@types/react": "18.0.15",
|
|
||||||
"@types/react-dom": "18.0.6",
|
|
||||||
"@vitejs/plugin-react": "2.0.0",
|
|
||||||
"eslint": "8.21.0",
|
|
||||||
"eslint-config-next": "12.2.3",
|
|
||||||
"jsdom": "20.0.0",
|
|
||||||
"typescript": "4.7.4",
|
|
||||||
"vitest": "0.20.2"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { test, expect, describe } from "vitest";
|
|
||||||
import { render, screen } from "@testing-library/react";
|
|
||||||
import "@testing-library/jest-dom";
|
|
||||||
import Categories from "@/layout/header/categories";
|
|
||||||
|
|
||||||
describe("Categories", () => {
|
|
||||||
test("renders learn react link", () => {
|
|
||||||
render(<Categories />);
|
|
||||||
const showText = screen.getByText(/software/i);
|
|
||||||
expect(showText).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,24 +0,0 @@
|
|||||||
import React, { FC } from "react";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
|
|
||||||
type ShowProps = {
|
|
||||||
children: React.ReactNode;
|
|
||||||
delay?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Show = ({ children, delay }: ShowProps) => {
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
initial={{ y: 10, opacity: 0 }}
|
|
||||||
animate={{ y: 0, opacity: 1 }}
|
|
||||||
transition={{
|
|
||||||
duration: 0.4,
|
|
||||||
delay: delay,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Show;
|
|
@ -1,16 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
|
|
||||||
type TapAnimation = {
|
|
||||||
children: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Tap = ({ children } : TapAnimation) => {
|
|
||||||
return (
|
|
||||||
<motion.div whileTap={{ scale: 0.97 }}>
|
|
||||||
{children}
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Tap;
|
|
12
src/app.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
|
// for information about these interfaces
|
||||||
|
declare global {
|
||||||
|
namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
// interface Locals {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
12
src/app.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,13 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { LayoutProps } from "@/interfaces/components";
|
|
||||||
import { SimpleGrid } from "@chakra-ui/react";
|
|
||||||
|
|
||||||
const Grid = (props: LayoutProps) => {
|
|
||||||
return (
|
|
||||||
<SimpleGrid minChildWidth="160px" spacing="30px" >
|
|
||||||
{props.children}
|
|
||||||
</SimpleGrid>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Grid;
|
|
@ -1,18 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { CustomIconBtnProps } from "@/interfaces/components";
|
|
||||||
import { IconButton } from "@chakra-ui/react";
|
|
||||||
|
|
||||||
const CustomIconBtn = (props: CustomIconBtnProps) => {
|
|
||||||
return (
|
|
||||||
<IconButton
|
|
||||||
variant="ghost"
|
|
||||||
aria-label={props.title}
|
|
||||||
icon={props.icon}
|
|
||||||
onClick={props.onClick}
|
|
||||||
mr={props.mr}
|
|
||||||
ml={props.ml}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CustomIconBtn;
|
|
@ -1,23 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Link } from "@chakra-ui/react";
|
|
||||||
import NextLink from "next/link";
|
|
||||||
import { CustomLinkProps } from "@/interfaces/components";
|
|
||||||
|
|
||||||
const CustomLink = ({ href, children, external, font, mr, ml }: CustomLinkProps) => {
|
|
||||||
return (
|
|
||||||
<NextLink href={href} passHref>
|
|
||||||
<Link
|
|
||||||
isExternal={external}
|
|
||||||
_hover={{ textDecoration: "none" }}
|
|
||||||
_focus={{ border: "none" }}
|
|
||||||
fontFamily={font}
|
|
||||||
mr={mr}
|
|
||||||
ml={ml}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Link>
|
|
||||||
</NextLink>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CustomLink;
|
|
@ -1,47 +0,0 @@
|
|||||||
import { ErrorProps } from "@/interfaces/components";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Center,
|
|
||||||
Heading,
|
|
||||||
HStack,
|
|
||||||
Text,
|
|
||||||
VStack,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import { ArrowClockwise, ArrowSquareOut, Warning } from "phosphor-react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import CustomLink from "@/common/link";
|
|
||||||
|
|
||||||
const Error = (props: ErrorProps) => {
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const handleRefresh = () => {
|
|
||||||
router.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Center>
|
|
||||||
<VStack>
|
|
||||||
<Warning size={90} />
|
|
||||||
<Heading fontSize="3xl">{props.title}</Heading>
|
|
||||||
<Text>{props.description}</Text>
|
|
||||||
<HStack>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
borderWidth="1px"
|
|
||||||
leftIcon={<ArrowClockwise />}
|
|
||||||
onClick={handleRefresh}
|
|
||||||
>
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
<CustomLink href="https://github.com/pheralb/svgl/issues/new" external={true}>
|
|
||||||
<Button variant="ghost" rightIcon={<ArrowSquareOut />}>
|
|
||||||
Create issue
|
|
||||||
</Button>
|
|
||||||
</CustomLink>
|
|
||||||
</HStack>
|
|
||||||
</VStack>
|
|
||||||
</Center>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Error;
|
|
@ -1,16 +0,0 @@
|
|||||||
import { LoadingProps } from "@/interfaces/components";
|
|
||||||
import { Center, Spinner, Text, VStack } from "@chakra-ui/react";
|
|
||||||
import { LeapFrog } from "@uiball/loaders";
|
|
||||||
|
|
||||||
const Loading = (props: LoadingProps) => {
|
|
||||||
return (
|
|
||||||
<Center>
|
|
||||||
<VStack spacing={3} mt="3">
|
|
||||||
<LeapFrog size={32} speed={2.5} color="#4343E5" />
|
|
||||||
<Text>{props.text}</Text>
|
|
||||||
</VStack>
|
|
||||||
</Center>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Loading;
|
|
@ -1,125 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import {
|
|
||||||
Input,
|
|
||||||
Text,
|
|
||||||
Image,
|
|
||||||
HStack,
|
|
||||||
Box,
|
|
||||||
Center,
|
|
||||||
Spinner,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import useDebounce from "@/hooks/useDebounce";
|
|
||||||
import { SearchProps, SVGCardProps } from "@/interfaces/components";
|
|
||||||
import CustomLink from "@/common/link";
|
|
||||||
import { getSvgByQuery } from "@/services";
|
|
||||||
import CustomIconBtn from "@/common/iconBtn";
|
|
||||||
import { Trash } from "phosphor-react";
|
|
||||||
import Tap from "@/animations/tap";
|
|
||||||
|
|
||||||
const Search = ({ availableFocus = false }: SearchProps) => {
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
const [empty, setEmpty] = useState(false);
|
|
||||||
const [results, setResults] = useState<SVGCardProps[]>([]);
|
|
||||||
const debouncedSearch = useDebounce(search, 500);
|
|
||||||
const searchRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (debouncedSearch) {
|
|
||||||
fetch(getSvgByQuery + debouncedSearch).then((res) => {
|
|
||||||
if (res.ok) {
|
|
||||||
res.json().then((data) => {
|
|
||||||
setEmpty(data.length === 0);
|
|
||||||
setResults(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [debouncedSearch]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const isFocusAvailable = availableFocus && searchRef.current;
|
|
||||||
|
|
||||||
if (!isFocusAvailable) return;
|
|
||||||
|
|
||||||
const timeoutId = setTimeout(() => {
|
|
||||||
searchRef.current?.focus();
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
return () => clearTimeout(timeoutId);
|
|
||||||
}, [availableFocus]);
|
|
||||||
|
|
||||||
const handleFilter = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setEmpty(false);
|
|
||||||
setSearch(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClear = () => {
|
|
||||||
setSearch("");
|
|
||||||
setResults([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Input
|
|
||||||
width="full"
|
|
||||||
variant="flushed"
|
|
||||||
size="lg"
|
|
||||||
placeholder="Search svgs..."
|
|
||||||
value={search}
|
|
||||||
onChange={handleFilter}
|
|
||||||
ref={searchRef}
|
|
||||||
/>
|
|
||||||
{search && !empty && results.length === 0 && (
|
|
||||||
<Box pt="4">
|
|
||||||
<Spinner />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{search && empty && <Box pt="3">No results found!</Box>}
|
|
||||||
{results && results.length > 0 && (
|
|
||||||
<>
|
|
||||||
<HStack
|
|
||||||
spacing={4}
|
|
||||||
mt={4}
|
|
||||||
overflowX="auto"
|
|
||||||
overflowY="hidden"
|
|
||||||
alignItems="start"
|
|
||||||
>
|
|
||||||
{results.map((item: SVGCardProps) => (
|
|
||||||
<Tap key={item.title}>
|
|
||||||
<CustomLink href={`/svg/${item.id}`}>
|
|
||||||
<Box
|
|
||||||
mb="2"
|
|
||||||
p="3"
|
|
||||||
shadow="sm"
|
|
||||||
borderWidth="1px"
|
|
||||||
borderRadius="5px"
|
|
||||||
width="100%"
|
|
||||||
>
|
|
||||||
<Center>
|
|
||||||
<Image
|
|
||||||
width="25px"
|
|
||||||
mb="2"
|
|
||||||
src={item.slug}
|
|
||||||
alt={item.title}
|
|
||||||
/>
|
|
||||||
</Center>
|
|
||||||
<Text>{item.title}</Text>
|
|
||||||
</Box>
|
|
||||||
</CustomLink>
|
|
||||||
</Tap>
|
|
||||||
))}
|
|
||||||
</HStack>
|
|
||||||
<Box p="3">
|
|
||||||
<CustomIconBtn
|
|
||||||
title="clear"
|
|
||||||
icon={<Trash size={16} />}
|
|
||||||
onClick={handleClear}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Search;
|
|
@ -1,47 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { SVGCardProps } from "@/interfaces/components";
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Center,
|
|
||||||
Image,
|
|
||||||
Text,
|
|
||||||
useColorModeValue,
|
|
||||||
useDisclosure,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import Tap from "@/animations/tap";
|
|
||||||
import CustomLink from "@/common/link";
|
|
||||||
import { Smiley } from "phosphor-react";
|
|
||||||
|
|
||||||
const SVGCard = (props: SVGCardProps) => {
|
|
||||||
const bg = useColorModeValue("bg.light", "bg.dark");
|
|
||||||
const color = useColorModeValue("rgb(0,0,0, .1)", "rgb(255,255,255, .1)");
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Tap>
|
|
||||||
<CustomLink href={`/svg/${props.id}`}>
|
|
||||||
<Box
|
|
||||||
bg={bg}
|
|
||||||
p={4}
|
|
||||||
cursor="pointer"
|
|
||||||
borderRadius="10px"
|
|
||||||
borderWidth="1px"
|
|
||||||
mb="2"
|
|
||||||
_hover={{
|
|
||||||
border:`1px solid ${color}`,
|
|
||||||
transform: "scale(1.03)",
|
|
||||||
}}
|
|
||||||
transition="all 0.2s" >
|
|
||||||
<Center>
|
|
||||||
<Image height="40px" src={props.svg} alt={props.title} />
|
|
||||||
</Center>
|
|
||||||
<Text mt="3" fontWeight="light" textAlign="center">
|
|
||||||
{props.title}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</CustomLink>
|
|
||||||
</Tap>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SVGCard;
|
|
@ -1,108 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Flex,
|
|
||||||
Heading,
|
|
||||||
HStack,
|
|
||||||
Icon,
|
|
||||||
Image,
|
|
||||||
Link,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import { ArrowSquareOut, Copy, DownloadSimple } from "phosphor-react";
|
|
||||||
import confetti from "canvas-confetti";
|
|
||||||
import download from "downloadjs";
|
|
||||||
import { toast } from "react-hot-toast";
|
|
||||||
import { ToastTheme } from "@/theme/toast";
|
|
||||||
import { SVGCardProps } from "@/interfaces/components";
|
|
||||||
|
|
||||||
// Download SVG =>
|
|
||||||
const downloadSvg = (url?: string) => {
|
|
||||||
confetti({
|
|
||||||
particleCount: 200,
|
|
||||||
startVelocity: 30,
|
|
||||||
spread: 300,
|
|
||||||
gravity: 1.2,
|
|
||||||
origin: { y: 0 },
|
|
||||||
});
|
|
||||||
download(url || "");
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const MIMETYPE = 'text/plain';
|
|
||||||
|
|
||||||
// Return content of svg as blob =>
|
|
||||||
const getSvgContent = async (url: string | undefined, isSupported: boolean) => {
|
|
||||||
const response = await fetch(url || "");
|
|
||||||
const content = await response.text();
|
|
||||||
|
|
||||||
// It was necessary to use blob because in chrome there were issues with the copy to clipboard
|
|
||||||
const blob = new Blob([content], { type: MIMETYPE });
|
|
||||||
return isSupported ? blob : content;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy to clipboard =>
|
|
||||||
const copyToClipboard = async (url?: string) => {
|
|
||||||
const data = {
|
|
||||||
[MIMETYPE]: getSvgContent(url, true)
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
const clipboardItem = new ClipboardItem(data);
|
|
||||||
await navigator.clipboard.write([clipboardItem]);
|
|
||||||
} catch (error) {
|
|
||||||
// This section works as a fallback on Firefox
|
|
||||||
const content = await getSvgContent(url, false) as string;
|
|
||||||
await navigator.clipboard.writeText(content);
|
|
||||||
}
|
|
||||||
toast("Copied to clipboard", ToastTheme);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SVGInfo = (props: SVGCardProps) => {
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
pt="7"
|
|
||||||
pb="7"
|
|
||||||
direction="column"
|
|
||||||
align="center"
|
|
||||||
justify="center"
|
|
||||||
borderWidth="1px"
|
|
||||||
borderRadius="10px"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src={props.slug}
|
|
||||||
alt={props.title}
|
|
||||||
fit="cover"
|
|
||||||
loading="lazy"
|
|
||||||
width="85px"
|
|
||||||
/>
|
|
||||||
<Heading mt={6} mb={6} fontSize="4xl">
|
|
||||||
{props.title}
|
|
||||||
</Heading>
|
|
||||||
<Flex direction={{ base: "column", md: "row" }}>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
borderWidth="1px"
|
|
||||||
leftIcon={<Copy />}
|
|
||||||
onClick={() => copyToClipboard(props.slug)}
|
|
||||||
mb={{ base: "2", md: "0" }}
|
|
||||||
mr={{ base: "0", md: "3" }}
|
|
||||||
>
|
|
||||||
Copy to clipboard
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
leftIcon={<DownloadSimple />}
|
|
||||||
variant="primary"
|
|
||||||
onClick={() => downloadSvg(props.slug)}
|
|
||||||
mb={{ base: "2", md: "0" }}
|
|
||||||
mr={{ base: "0", md: "3" }}
|
|
||||||
>
|
|
||||||
Download .svg
|
|
||||||
</Button>
|
|
||||||
<Link href={props.url} isExternal={true}>
|
|
||||||
{props.title} website <Icon as={ArrowSquareOut} mt="2" />
|
|
||||||
</Link>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SVGInfo;
|
|
@ -1,17 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
function useDebounce<T>(value: T, delay?: number): T {
|
|
||||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timer = setTimeout(() => setDebouncedValue(value), delay || 500);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timer);
|
|
||||||
};
|
|
||||||
}, [value, delay]);
|
|
||||||
|
|
||||||
return debouncedValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useDebounce;
|
|
7
src/index.test.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
describe('sum test', () => {
|
||||||
|
it('adds 1 + 2 to equal 3', () => {
|
||||||
|
expect(1 + 2).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
@ -1,48 +0,0 @@
|
|||||||
export interface LayoutProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CustomLinkProps {
|
|
||||||
href: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
external?: boolean;
|
|
||||||
font?: string;
|
|
||||||
mr?: string;
|
|
||||||
ml?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CustomIconBtnProps {
|
|
||||||
title: string;
|
|
||||||
icon: React.ReactElement;
|
|
||||||
mr?: string;
|
|
||||||
ml?: string;
|
|
||||||
onClick?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SVGCardProps {
|
|
||||||
id: number;
|
|
||||||
svg: string;
|
|
||||||
title: string;
|
|
||||||
slug?: string;
|
|
||||||
url?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SidebarContentProps {
|
|
||||||
display?: object;
|
|
||||||
w?: string;
|
|
||||||
borderRight?: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoadingProps {
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ErrorProps {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SearchProps {
|
|
||||||
availableFocus?: boolean;
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
export interface SvgData {
|
|
||||||
id: number;
|
|
||||||
slug: string;
|
|
||||||
title: string;
|
|
||||||
category: string;
|
|
||||||
categories?: string[];
|
|
||||||
url: string;
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
import CustomLink from "@/common/link";
|
|
||||||
import { Flex, Heading, HStack, Icon, Text } from "@chakra-ui/react";
|
|
||||||
import { RocketLaunch, TwitterLogo } from "phosphor-react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
type Props = {};
|
|
||||||
|
|
||||||
const Index = (props: Props) => {
|
|
||||||
return (
|
|
||||||
<Flex direction="column" pt="8" pb="8" justifyContent="center" alignItems="center">
|
|
||||||
<HStack>
|
|
||||||
<Icon as={RocketLaunch} />
|
|
||||||
<CustomLink href="https://twitter.com/pheralb_" external={true}>
|
|
||||||
Created by Pablo
|
|
||||||
</CustomLink>
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Index;
|
|
@ -1,48 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { getCategorySvgs } from "@/services";
|
|
||||||
import CustomLink from "@/common/link";
|
|
||||||
import { Box, useColorModeValue } from "@chakra-ui/react";
|
|
||||||
import { RaceBy } from "@uiball/loaders";
|
|
||||||
import Tap from "@/animations/tap";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
const Categories = () => {
|
|
||||||
const { data, error } = useSWR(getCategorySvgs);
|
|
||||||
const color = useColorModeValue("rgb(0,0,0, .1)", "rgb(255,255,255, .1)");
|
|
||||||
const router = useRouter();
|
|
||||||
if (error) return <div>failed to load</div>;
|
|
||||||
if (!data)
|
|
||||||
return <RaceBy size={52} lineWeight={3} speed={1.4} color="#4343E5" />;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{data.map((category: string) => (
|
|
||||||
<Tap key={category}>
|
|
||||||
<CustomLink
|
|
||||||
href={`/category/${category}`}>
|
|
||||||
<Box
|
|
||||||
p={4}
|
|
||||||
borderRadius="4px"
|
|
||||||
borderWidth="1px"
|
|
||||||
__css={
|
|
||||||
router.asPath === `/category/${category}`
|
|
||||||
? {
|
|
||||||
backgroundColor: '#4343e5',
|
|
||||||
color: '#fff'
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
_hover={{
|
|
||||||
border:`1px solid ${color}`,
|
|
||||||
transform: "scale(0.98)",
|
|
||||||
}}>
|
|
||||||
{category}
|
|
||||||
</Box>
|
|
||||||
</CustomLink>
|
|
||||||
</Tap>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Categories;
|
|
@ -1,120 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
Flex,
|
|
||||||
useColorModeValue,
|
|
||||||
HStack,
|
|
||||||
Container,
|
|
||||||
Heading,
|
|
||||||
Icon,
|
|
||||||
useDisclosure,
|
|
||||||
Collapse,
|
|
||||||
} from '@chakra-ui/react'
|
|
||||||
import { ArrowSquareOut, MagnifyingGlass, Sticker, X } from 'phosphor-react'
|
|
||||||
import Theme from './theme'
|
|
||||||
import Tap from '@/animations/tap'
|
|
||||||
import Mobile from './mobile'
|
|
||||||
import { Links } from './links'
|
|
||||||
import CustomLink from '@/common/link'
|
|
||||||
import Categories from './categories'
|
|
||||||
import Search from '@/components/search'
|
|
||||||
import CustomIconBtn from '@/common/iconBtn'
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
|
||||||
|
|
||||||
const Header = () => {
|
|
||||||
const bg = useColorModeValue('bg.light', 'bg.dark')
|
|
||||||
const { isOpen, onToggle } = useDisclosure()
|
|
||||||
useHotkeys('ctrl+k', (e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
onToggle()
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Box
|
|
||||||
as='header'
|
|
||||||
position='sticky'
|
|
||||||
top='0'
|
|
||||||
bg={bg}
|
|
||||||
borderBottomWidth='1px'
|
|
||||||
w='full'
|
|
||||||
py={6}
|
|
||||||
zIndex={1}
|
|
||||||
shadow='sm'
|
|
||||||
>
|
|
||||||
<Container maxW={{ base: 'full', md: '70%' }}>
|
|
||||||
<Flex alignItems='center' justifyContent='space-between' mx='auto'>
|
|
||||||
<CustomLink href='/'>
|
|
||||||
<Tap>
|
|
||||||
<HStack spacing={3} cursor='pointer'>
|
|
||||||
<Sticker size={32} color='#4343e5' weight='bold' />
|
|
||||||
<Heading fontSize='19px'>svgl</Heading>
|
|
||||||
</HStack>
|
|
||||||
</Tap>
|
|
||||||
</CustomLink>
|
|
||||||
<HStack display='flex' alignItems='center' spacing={2}>
|
|
||||||
<Box display={{ base: 'none', md: 'inline-flex' }}>
|
|
||||||
{Links.map((link) => (
|
|
||||||
<CustomLink
|
|
||||||
key={link.title}
|
|
||||||
href={link.slug}
|
|
||||||
external={link.external}
|
|
||||||
font='Inter-Semibold'
|
|
||||||
mr='4'
|
|
||||||
ml='3'
|
|
||||||
>
|
|
||||||
<Tap>
|
|
||||||
{link.title}
|
|
||||||
{link.external ? (
|
|
||||||
<Icon as={ArrowSquareOut} ml='2' />
|
|
||||||
) : null}
|
|
||||||
</Tap>
|
|
||||||
</CustomLink>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
<HStack
|
|
||||||
spacing={1}
|
|
||||||
mr={1}
|
|
||||||
display={{ base: 'none', md: 'inline-flex' }}
|
|
||||||
>
|
|
||||||
<CustomIconBtn
|
|
||||||
title='Toggle Search bar'
|
|
||||||
icon={
|
|
||||||
isOpen ? <X size={22} /> : <MagnifyingGlass size={22} />
|
|
||||||
}
|
|
||||||
onClick={onToggle}
|
|
||||||
/>
|
|
||||||
<Theme />
|
|
||||||
</HStack>
|
|
||||||
<Box display={{ base: 'inline-flex', md: 'none' }}>
|
|
||||||
<Mobile />
|
|
||||||
</Box>
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
|
||||||
<Collapse in={isOpen} animateOpacity>
|
|
||||||
<Box mt='3' display={{ base: 'none', md: 'block' }}>
|
|
||||||
<Search availableFocus={isOpen} />
|
|
||||||
</Box>
|
|
||||||
</Collapse>
|
|
||||||
<Box mt='2' display={{ base: 'block', md: 'none' }}>
|
|
||||||
<Search />
|
|
||||||
</Box>
|
|
||||||
</Container>
|
|
||||||
</Box>
|
|
||||||
<Box p='4' overflowX='hidden' overflowY='auto'>
|
|
||||||
<HStack
|
|
||||||
justifyContent={{ base: 'none', lg: 'center' }}
|
|
||||||
flexWrap={{ base: 'initial', lg: 'wrap' }}
|
|
||||||
spacing={4}
|
|
||||||
overflowX='auto'
|
|
||||||
overflowY='hidden'
|
|
||||||
bg={bg}
|
|
||||||
pb='4'
|
|
||||||
borderBottomWidth='1px'
|
|
||||||
>
|
|
||||||
<Categories />
|
|
||||||
</HStack>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Header
|
|
@ -1,12 +0,0 @@
|
|||||||
export const Links = [
|
|
||||||
{
|
|
||||||
title: "Github",
|
|
||||||
slug: "https://github.com/pheralb/svgl",
|
|
||||||
external: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Twitter",
|
|
||||||
slug: "https://twitter.com/pheralb_",
|
|
||||||
external: true,
|
|
||||||
},
|
|
||||||
];
|
|
@ -1,59 +0,0 @@
|
|||||||
import CustomLink from "@/common/link";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
CloseButton,
|
|
||||||
IconButton,
|
|
||||||
useColorModeValue,
|
|
||||||
useDisclosure,
|
|
||||||
VStack,
|
|
||||||
} from "@chakra-ui/react";
|
|
||||||
import { List } from "phosphor-react";
|
|
||||||
import { Links } from "./links";
|
|
||||||
import Theme from "./theme";
|
|
||||||
|
|
||||||
const Mobile = () => {
|
|
||||||
const bg = useColorModeValue("bg.light", "bg.dark");
|
|
||||||
const mobileNav = useDisclosure();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Theme />
|
|
||||||
<IconButton
|
|
||||||
display={{ base: "flex", md: "none" }}
|
|
||||||
aria-label="Open menu navbar"
|
|
||||||
variant="ghost"
|
|
||||||
icon={<List size={22} />}
|
|
||||||
onClick={mobileNav.onOpen}
|
|
||||||
ml="2"
|
|
||||||
/>
|
|
||||||
<VStack
|
|
||||||
pos="absolute"
|
|
||||||
top={0}
|
|
||||||
left={0}
|
|
||||||
right={0}
|
|
||||||
display={mobileNav.isOpen ? "flex" : "none"}
|
|
||||||
flexDirection="column"
|
|
||||||
p={4}
|
|
||||||
pb={4}
|
|
||||||
bg={bg}
|
|
||||||
spacing={5}
|
|
||||||
rounded="sm"
|
|
||||||
shadow="sm"
|
|
||||||
borderWidth="1px"
|
|
||||||
zIndex={2}
|
|
||||||
>
|
|
||||||
<CloseButton aria-label="Close menu" onClick={mobileNav.onClose} />
|
|
||||||
{Links.map((link) => (
|
|
||||||
<CustomLink
|
|
||||||
key={link.title}
|
|
||||||
href={link.slug}
|
|
||||||
external={link.external}
|
|
||||||
>
|
|
||||||
{link.title}
|
|
||||||
</CustomLink>
|
|
||||||
))}
|
|
||||||
</VStack>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Mobile;
|
|
@ -1,31 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
|
||||||
import { useColorMode, useColorModeValue } from "@chakra-ui/react";
|
|
||||||
import CustomIconBtn from "@/common/iconBtn";
|
|
||||||
import { Moon, Sun } from "phosphor-react";
|
|
||||||
|
|
||||||
const Theme = () => {
|
|
||||||
const { toggleColorMode } = useColorMode();
|
|
||||||
const key = useColorModeValue("light", "dark");
|
|
||||||
const icon = useColorModeValue(<Moon size={22} />, <Sun size={22} />);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AnimatePresence exitBeforeEnter initial={false}>
|
|
||||||
<motion.div
|
|
||||||
key={key}
|
|
||||||
initial={{ y: -20, opacity: 0 }}
|
|
||||||
animate={{ y: 0, opacity: 1 }}
|
|
||||||
exit={{ y: 20, opacity: 0 }}
|
|
||||||
transition={{ duration: 0.2 }}
|
|
||||||
>
|
|
||||||
<CustomIconBtn
|
|
||||||
title="Toggle theme"
|
|
||||||
icon={icon}
|
|
||||||
onClick={toggleColorMode}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
</AnimatePresence>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Theme;
|
|
@ -1,17 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { LayoutProps } from "@/interfaces/components";
|
|
||||||
import { Container } from "@chakra-ui/react";
|
|
||||||
import Header from "./header";
|
|
||||||
import Footer from "./footer";
|
|
||||||
|
|
||||||
const Index = ({ children }: LayoutProps) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Header />
|
|
||||||
<Container maxW={{ base: "100%", md: "70%" }} mt={{ base: "1", md: "3" }}>{children}</Container>
|
|
||||||
<Footer />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Index;
|
|
@ -1,21 +0,0 @@
|
|||||||
import CustomLink from "@/common/link";
|
|
||||||
import { Flex, Center, Heading, Text, Button } from "@chakra-ui/react";
|
|
||||||
import { House } from "phosphor-react";
|
|
||||||
|
|
||||||
const Error = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Center>
|
|
||||||
<Flex direction="column" justifyContent="center" alignItems="center">
|
|
||||||
<Heading mb="2">Error 404</Heading>
|
|
||||||
<Text mb="3">The page you are trying to access does not exist.</Text>
|
|
||||||
<CustomLink href="/">
|
|
||||||
<Text fontFamily="Inter-Semibold">Go home</Text>
|
|
||||||
</CustomLink>
|
|
||||||
</Flex>
|
|
||||||
</Center>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Error;
|
|
@ -1,64 +0,0 @@
|
|||||||
import type { AppProps } from "next/app";
|
|
||||||
|
|
||||||
// Chakra UI & custom styles ->
|
|
||||||
import { ChakraProvider } from "@chakra-ui/react";
|
|
||||||
import theme from "@/theme";
|
|
||||||
import "@/styles/globals.css";
|
|
||||||
|
|
||||||
// Layout ->
|
|
||||||
import Layout from "@/layout";
|
|
||||||
|
|
||||||
// Nextjs Progressbar ->
|
|
||||||
import NextNProgress from "nextjs-progressbar";
|
|
||||||
|
|
||||||
// Framer ->
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
|
|
||||||
// SWR Config & services ->
|
|
||||||
import { SWRConfig } from "swr";
|
|
||||||
import { fetcher } from "@/services/fetcher";
|
|
||||||
|
|
||||||
// React Hot Toast ->
|
|
||||||
import { Toaster } from "react-hot-toast";
|
|
||||||
import { DefaultSeo } from "next-seo";
|
|
||||||
import nextSeoConfig from "next-seo.config";
|
|
||||||
|
|
||||||
function MyApp({ Component, pageProps, router }: AppProps) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DefaultSeo {...nextSeoConfig} />
|
|
||||||
<NextNProgress
|
|
||||||
color="#4343E5"
|
|
||||||
startPosition={0.3}
|
|
||||||
stopDelayMs={200}
|
|
||||||
height={2}
|
|
||||||
showOnShallow={true}
|
|
||||||
options={{ showSpinner: false }}
|
|
||||||
/>
|
|
||||||
<ChakraProvider theme={theme}>
|
|
||||||
<SWRConfig value={{ fetcher }}>
|
|
||||||
<Layout>
|
|
||||||
<motion.div
|
|
||||||
key={router.route}
|
|
||||||
initial="initial"
|
|
||||||
animate="animate"
|
|
||||||
variants={{
|
|
||||||
initial: {
|
|
||||||
opacity: 0,
|
|
||||||
},
|
|
||||||
animate: {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Component {...pageProps} />
|
|
||||||
</motion.div>
|
|
||||||
</Layout>
|
|
||||||
</SWRConfig>
|
|
||||||
</ChakraProvider>
|
|
||||||
<Toaster position="bottom-center" reverseOrder={false} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MyApp;
|
|
@ -1,18 +0,0 @@
|
|||||||
import { ColorModeScript } from "@chakra-ui/react";
|
|
||||||
import NextDocument, { Html, Head, Main, NextScript } from "next/document";
|
|
||||||
import theme from "@/theme";
|
|
||||||
|
|
||||||
export default class Document extends NextDocument {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Html lang="en">
|
|
||||||
<Head />
|
|
||||||
<body>
|
|
||||||
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
|
|
||||||
<Main />
|
|
||||||
<NextScript />
|
|
||||||
</body>
|
|
||||||
</Html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import db from "data/svgs.json";
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import { SvgData } from "@/interfaces/svgData";
|
|
||||||
|
|
||||||
export default function handler(
|
|
||||||
req: NextApiRequest,
|
|
||||||
res: NextApiResponse<SvgData[]>
|
|
||||||
) {
|
|
||||||
// Begin with the last id in the db:
|
|
||||||
const svgs = db.sort((a, b) => b.id - a.id);
|
|
||||||
return res.status(200).json(svgs);
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import db from "data/svgs.json";
|
|
||||||
|
|
||||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
||||||
// Get unique categories:
|
|
||||||
const categories = db
|
|
||||||
.map((svg) => svg.category)
|
|
||||||
.filter((category, index, array) => array.indexOf(category) === index);
|
|
||||||
res.status(200).json(categories);
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
import db from "data/svgs.json";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
|
|
||||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
||||||
const { id, q, c } = req.query;
|
|
||||||
|
|
||||||
// 🔎 Search by id (ex: ?id=1) ->
|
|
||||||
if (id) {
|
|
||||||
const item = db.find((item) => item.id === +id);
|
|
||||||
return res.status(200).json(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔎 Search by query (ex: ?q=d) ->
|
|
||||||
if (q) {
|
|
||||||
const results = db.filter((product) => {
|
|
||||||
const { title } = product;
|
|
||||||
return title.toLowerCase().includes(q.toString().toLowerCase());
|
|
||||||
});
|
|
||||||
return res.status(200).json(results);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔎 Search by category (ex: ?c=library) ->
|
|
||||||
if (c) {
|
|
||||||
const results = db.filter((product) => {
|
|
||||||
const { category } = product;
|
|
||||||
return category.toLowerCase().includes(c.toString().toLowerCase());
|
|
||||||
});
|
|
||||||
return res.status(200).json(results);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✖ Error ->
|
|
||||||
res.status(400).json({ info: "[/api/search] Error: api query not found." });
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
import Head from "next/head";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { getSvgByCategory } from "@/services";
|
|
||||||
import Loading from "@/components/loading";
|
|
||||||
import Grid from "@/common/grid";
|
|
||||||
import SVGCard from "@/components/svgCard";
|
|
||||||
import { SvgData } from "@/interfaces/svgData";
|
|
||||||
import { Center, Heading } from "@chakra-ui/react";
|
|
||||||
import Show from "@/animations/show";
|
|
||||||
|
|
||||||
export default function Category() {
|
|
||||||
const router = useRouter();
|
|
||||||
const { data, error } = useSWR(
|
|
||||||
() => router.query.category && `${getSvgByCategory}${router.query.category}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) router.push("/404");
|
|
||||||
if (!data) return <Loading text="Loading..." />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>{router.query.category} logos - svgl</title>
|
|
||||||
</Head>
|
|
||||||
<Show>
|
|
||||||
<Center>
|
|
||||||
<Heading mb="5">{router.query.category}</Heading>
|
|
||||||
</Center>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Grid>
|
|
||||||
{data.map((svg: SvgData) => (
|
|
||||||
<SVGCard key={svg.id} id={svg.id} svg={svg.slug} title={svg.title} />
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
import type { NextPage } from "next";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { getAllSvgs } from "@/services";
|
|
||||||
import { SvgData } from "@/interfaces/svgData";
|
|
||||||
import SVGCard from "@/components/svgCard";
|
|
||||||
import Grid from "@/common/grid";
|
|
||||||
import Loading from "@/components/loading";
|
|
||||||
import Error from "@/components/error";
|
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
|
||||||
const { data, error } = useSWR(getAllSvgs);
|
|
||||||
|
|
||||||
if (error)
|
|
||||||
return (
|
|
||||||
<Error title="Error" description="An unexpected error has occurred" />
|
|
||||||
);
|
|
||||||
if (!data) return <Loading text="Loading..." />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Grid>
|
|
||||||
{data.map((svg: SvgData) => (
|
|
||||||
<SVGCard key={svg.id} id={svg.id} svg={svg.slug} title={svg.title} />
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Home;
|
|
@ -1,29 +0,0 @@
|
|||||||
import Head from "next/head";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
import Show from "@/animations/show";
|
|
||||||
import { getSvgById } from "@/services";
|
|
||||||
import Loading from "@/components/loading";
|
|
||||||
import SVGInfo from "@/components/svgInfo";
|
|
||||||
|
|
||||||
export default function Icon() {
|
|
||||||
const router = useRouter();
|
|
||||||
const { data, error } = useSWR(
|
|
||||||
() => router.query.id && `${getSvgById}${router.query.id}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) router.push("/404");
|
|
||||||
if (!data) return <Loading text="Loading..." />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>{data.title} - svgl</title>
|
|
||||||
</Head>
|
|
||||||
<Show>
|
|
||||||
<SVGInfo {...data} />
|
|
||||||
</Show>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
2
src/routes/+page.svelte
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<h1>Welcome to SvelteKit</h1>
|
||||||
|
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
|
@ -1,10 +0,0 @@
|
|||||||
export const fetcher = async (url: string) => {
|
|
||||||
const res = await fetch(url);
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
const error = new Error("An error occurred while fetching the data.");
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.json();
|
|
||||||
};
|
|
@ -1,6 +0,0 @@
|
|||||||
export const githubVersionPackage = 'https://api.github.com/repos/pheralb/svgl/releases/latest';
|
|
||||||
export const getAllSvgs = "/api/all";
|
|
||||||
export const getCategorySvgs = "/api/categories";
|
|
||||||
export const getSvgById = "/api/search?id=";
|
|
||||||
export const getSvgByQuery = "/api/search?q=";
|
|
||||||
export const getSvgByCategory = "/api/search?c=";
|
|
@ -1,16 +0,0 @@
|
|||||||
/* Fonts -> */
|
|
||||||
@font-face {
|
|
||||||
font-family: "Inter-Regular";
|
|
||||||
src: url("/fonts/Inter-Regular.woff2") format("woff2");
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Inter-Semibold";
|
|
||||||
src: url("/fonts/Inter-SemiBold.woff2") format("woff2");
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
const baseStyle = {
|
|
||||||
borderRadius: "md",
|
|
||||||
fontWeight: "light",
|
|
||||||
};
|
|
||||||
|
|
||||||
function variantPrimary() {
|
|
||||||
const disabled = {
|
|
||||||
bg: "purple.900",
|
|
||||||
color: "white",
|
|
||||||
};
|
|
||||||
|
|
||||||
const loading = {
|
|
||||||
bg: "purple.800",
|
|
||||||
color: "white",
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
bg: "brand.purple",
|
|
||||||
color: "white",
|
|
||||||
_hover: {
|
|
||||||
bg: "purple.900",
|
|
||||||
_disabled: {
|
|
||||||
...disabled,
|
|
||||||
_loading: loading,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_active: {
|
|
||||||
bg: "purple.700",
|
|
||||||
},
|
|
||||||
_disabled: {
|
|
||||||
...disabled,
|
|
||||||
_loading: loading,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const variants = {
|
|
||||||
primary: variantPrimary,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
baseStyle,
|
|
||||||
variants,
|
|
||||||
};
|
|
@ -1,5 +0,0 @@
|
|||||||
import Button from "./button";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
Button,
|
|
||||||
};
|
|
@ -1,44 +0,0 @@
|
|||||||
import { ChakraProps, extendTheme } from "@chakra-ui/react";
|
|
||||||
import { mode } from "@chakra-ui/theme-tools";
|
|
||||||
import components from "./components";
|
|
||||||
|
|
||||||
const theme = extendTheme(
|
|
||||||
{
|
|
||||||
components,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: {
|
|
||||||
initialColorMode: "light",
|
|
||||||
useSystemColorMode: false,
|
|
||||||
},
|
|
||||||
colors: {
|
|
||||||
bg: {
|
|
||||||
light: "#F2F2F2",
|
|
||||||
dark: "#1F2023",
|
|
||||||
},
|
|
||||||
full: {
|
|
||||||
light: "#ffffff",
|
|
||||||
dark: "#000000",
|
|
||||||
},
|
|
||||||
brand: {
|
|
||||||
purple: "#4343E5",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fonts: {
|
|
||||||
body: "Inter-Regular, sans-serif",
|
|
||||||
heading: "Inter-Semibold, sans-serif",
|
|
||||||
},
|
|
||||||
styles: {
|
|
||||||
global: (props: ChakraProps) => ({
|
|
||||||
"html, body": {
|
|
||||||
height: "100%",
|
|
||||||
maxHeight: "100vh",
|
|
||||||
bg: mode("bg.light", "bg.dark")(props),
|
|
||||||
fontSize: "14px",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default theme;
|
|
@ -1,8 +0,0 @@
|
|||||||
export const ToastTheme = {
|
|
||||||
icon: "🔔",
|
|
||||||
style: {
|
|
||||||
borderRadius: "10px",
|
|
||||||
background: "#1F2023",
|
|
||||||
color: "#fff",
|
|
||||||
},
|
|
||||||
};
|
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 187 KiB |
Before Width: | Height: | Size: 633 B After Width: | Height: | Size: 633 B |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 569 B After Width: | Height: | Size: 569 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 486 B |
Before Width: | Height: | Size: 678 B After Width: | Height: | Size: 678 B |
Before Width: | Height: | Size: 1012 B After Width: | Height: | Size: 1012 B |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 532 B After Width: | Height: | Size: 532 B |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 441 B After Width: | Height: | Size: 441 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 617 B After Width: | Height: | Size: 617 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 370 B After Width: | Height: | Size: 370 B |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 494 B After Width: | Height: | Size: 494 B |
Before Width: | Height: | Size: 1013 B After Width: | Height: | Size: 1013 B |
Before Width: | Height: | Size: 802 B After Width: | Height: | Size: 802 B |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 357 B |
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 336 B |