├── .prettierrc
├── styles
├── _colors.scss
└── globals.scss
├── .eslintrc.json
├── components
├── Grid.module.scss
├── Grid.jsx
├── Intro.module.scss
├── Intro.jsx
├── SettingsBar.module.scss
├── SearchBox.module.scss
├── EmojiCard.module.scss
├── Button.module.scss
├── Button.jsx
├── Header.module.scss
├── EmojiCard.jsx
├── Header.jsx
├── SearchBox.jsx
├── Dropdown.module.scss
├── Dropdown.jsx
├── SelectedEmoji.jsx
├── SelectedEmoji.module.scss
└── SettingsBar.jsx
├── next.config.js
├── utils
├── store.js
└── getEmojiUrl.js
├── README.md
├── lib
└── gtag.js
├── .gitignore
├── package.json
└── pages
├── _app.js
└── index.js
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4
3 | }
--------------------------------------------------------------------------------
/styles/_colors.scss:
--------------------------------------------------------------------------------
1 | $dark-yellow: #ce9223;
2 | $yellow: #ffcc4d;
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/components/Grid.module.scss:
--------------------------------------------------------------------------------
1 | .grid {
2 | display: flex;
3 | flex-wrap: wrap;
4 | align-items: center;
5 | justify-content: center;
6 | gap: 8px;
7 | }
--------------------------------------------------------------------------------
/components/Grid.jsx:
--------------------------------------------------------------------------------
1 | import css from './Grid.module.scss'
2 |
3 | export default function Grid({ children }) {
4 | return (
5 |
6 | {children}
7 |
8 | )
9 | }
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | images: {
5 | domains: ['cdn.jsdelivr.net'],
6 | },
7 | }
8 |
9 | module.exports = nextConfig
10 |
--------------------------------------------------------------------------------
/components/Intro.module.scss:
--------------------------------------------------------------------------------
1 | @use "styles/colors" as *;
2 |
3 | .intro {
4 | text-align: center;
5 | font-weight: 600;
6 | margin: 24px;
7 |
8 | h1 {
9 | font-size: 3em;
10 | color: var(--text);
11 | }
12 | }
--------------------------------------------------------------------------------
/components/Intro.jsx:
--------------------------------------------------------------------------------
1 | import css from "./Intro.module.scss";
2 |
3 | export default function Intro(params) {
4 | return (
5 |
6 |
Twemoji Cheatsheet
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/utils/store.js:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 |
3 | export const useAppSettings = create((set) => ({
4 | theme: "black",
5 | skinTone: "",
6 | emojiSize: "32",
7 | setTheme: (color) => set({ theme: color }),
8 | setSkinTone: (skin) => set({ skinTone: skin }),
9 | setEmojiSize: (size) => set({ emojiSize: size }),
10 | }));
11 |
--------------------------------------------------------------------------------
/components/SettingsBar.module.scss:
--------------------------------------------------------------------------------
1 | @use "styles/colors" as *;
2 |
3 | .bar {
4 | display: flex;
5 | flex-direction: column;
6 | margin: 12px 0 32px;
7 | }
8 |
9 | .buttons {
10 | display: flex;
11 | align-items: flex-start;
12 | justify-content: center;
13 | column-gap: 24px;
14 | padding: 16px 24px;
15 | flex-wrap: wrap;
16 |
17 | input {
18 | margin-top: 10px;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/components/SearchBox.module.scss:
--------------------------------------------------------------------------------
1 | @use "styles/colors" as *;
2 |
3 | .input {
4 | background-color: var(--input);
5 | color: var(--text);
6 | border: none;
7 | padding: 8px;
8 | outline: solid 2px transparent;
9 | border-radius: 12px;
10 | text-align: center;
11 | max-width: 736px;
12 | width: 100%;
13 | margin: auto;
14 | font-size: 1em;
15 |
16 | &:focus {
17 | outline-color: var(--yellow);
18 | }
19 | }
--------------------------------------------------------------------------------
/components/EmojiCard.module.scss:
--------------------------------------------------------------------------------
1 | @use "styles/colors" as *;
2 |
3 | .card {
4 | padding: 16px;
5 | border-radius: 40%;
6 | cursor: pointer;
7 | background-color: var(--box);
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | justify-content: center;
12 | outline: solid 2px transparent;
13 |
14 | &:hover {
15 | transform: scale(1.1);
16 | }
17 |
18 | &:focus {
19 | outline-color: var(--yellow);
20 | }
21 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | **Select An Emoji & Copy the Unicode, Download the PNG, SVG or Copy the HEX Code!**
4 |
5 |
6 | ## Have an Idea for Improvement? 🤔
7 | Visit the [Discussions](https://github.com/ShahriarKh/twemoji-cheatsheet/discussions)
8 | Wanna contribute? Fine! PRs are welcome.
9 |
10 |
11 | ## License 📜
12 | Emojis By Twitter's [Twemoji](https://github.com/twitter/twemoji), Licensed Under CC-BY 4.0
13 |
--------------------------------------------------------------------------------
/lib/gtag.js:
--------------------------------------------------------------------------------
1 | export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_ID
2 |
3 | // https://developers.google.com/analytics/devguides/collection/gtagjs/pages
4 | export const pageview = (url) => {
5 | window.gtag('config', GA_TRACKING_ID, {
6 | page_path: url,
7 | })
8 | }
9 |
10 | // https://developers.google.com/analytics/devguides/collection/gtagjs/events
11 | export const event = ({ action, category, label, value }) => {
12 | window.gtag('event', action, {
13 | event_category: category,
14 | event_label: label,
15 | value: value,
16 | })
17 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env.local
30 | .env.development.local
31 | .env.test.local
32 | .env.production.local
33 |
34 | # vercel
35 | .vercel
36 |
37 | /emojis
38 | /.vscode
--------------------------------------------------------------------------------
/components/Button.module.scss:
--------------------------------------------------------------------------------
1 | @use "styles/colors" as *;
2 |
3 | .button {
4 | width: 100%;
5 | height: 2.5em;
6 | border-radius: 8px;
7 | outline: solid 2px transparent;
8 |
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 |
13 |
14 | color: black;
15 | background-color: $yellow;
16 | cursor: pointer;
17 | font-size: 0.8em;
18 | font-weight: 700;
19 | text-transform: capitalize;
20 |
21 | &:hover {
22 | opacity: 0.6;
23 | }
24 |
25 | &:focus {
26 | outline-color: $dark-yellow;
27 | }
28 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "twemoji-cheatsheet",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "downshift": "^6.1.12",
13 | "emojibase-data": "^7.0.1",
14 | "fuse.js": "^6.6.2",
15 | "next": "12.1.0",
16 | "react": "17.0.2",
17 | "react-dom": "17.0.2",
18 | "sass": "^1.57.1",
19 | "twemoji": "^14.0.2",
20 | "zustand": "^4.3.7"
21 | },
22 | "devDependencies": {
23 | "eslint": "^8.31.0",
24 | "eslint-config-next": "12.1.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/components/Button.jsx:
--------------------------------------------------------------------------------
1 | import css from "./Button.module.scss";
2 |
3 | export default function Button({ onClick, label, url, className }) {
4 | return (
5 | <>
6 | {onClick ? (
7 |
14 | ) : (
15 |
21 | {label}
22 |
23 | )}
24 | >
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/components/Header.module.scss:
--------------------------------------------------------------------------------
1 | @use "styles/colors" as *;
2 |
3 | .header {
4 | background-color: var(--nav);
5 | padding: 6px 10%;
6 | gap: 8px;
7 | display: flex;
8 | justify-content: space-between;
9 | flex-wrap: wrap;
10 |
11 | color: var(--text);
12 |
13 | p {
14 | text-align: center;
15 | flex: 1 1 240px;
16 | padding: 6px;
17 | font-weight: 600;
18 | font-size: 0.8em;
19 |
20 | &:not(:last-of-type) {
21 | border-right: solid 2px var(--bg);
22 | }
23 | }
24 |
25 | p > a {
26 | transition: all 0.1s;
27 | color: var(--yellow);
28 | outline: none;
29 | border-bottom: solid 2px transparent;
30 |
31 | &:hover,
32 | &:focus {
33 | border-bottom-color: var(--yellow);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/components/EmojiCard.jsx:
--------------------------------------------------------------------------------
1 | import css from "./EmojiCard.module.scss";
2 | import React, { memo } from "react";
3 | import Image from "next/image";
4 | import { getEmojiUrl } from "../utils/getEmojiUrl";
5 |
6 | // https://gist.github.com/chibicode/fe195d792270910226c928b69a468206?permalink_comment_id=3650172#gistcomment-3650172
7 |
8 | const Twemoji = ({ emoji, size = 24, onClick, skinTone }) => {
9 | return (
10 | {
15 | e.preventDefault();
16 | if (e.key === "Enter") {
17 | onClick();
18 | }
19 | }}
20 | >
21 |
22 |
23 | );
24 | };
25 |
26 | export default memo(Twemoji);
27 |
--------------------------------------------------------------------------------
/utils/getEmojiUrl.js:
--------------------------------------------------------------------------------
1 | export function getEmojiUrl(emoji, skinTone, format = "svg") {
2 | let url;
3 | let folder = "svg";
4 | let hex = emoji.hexcode;
5 |
6 | if (skinTone && emoji.skins) {
7 | hex = emoji.skins.filter((skin) => skin.hexcode.includes(skinTone))[0]
8 | .hexcode;
9 | }
10 |
11 | let code = hex.toLowerCase();
12 |
13 | // Fix for "copyright" and "trademark" emojis
14 | if (code.substring(0, 2) == "00") {
15 | code = code.substring(2);
16 |
17 | // Fix for keycap emojis
18 | const regex = /-fe0f/i;
19 | code = code.replace(regex, "");
20 | }
21 |
22 | // Fix for "Eye in Speech Bubble" emoji
23 | if (code.includes("1f441")) {
24 | const regex = /-fe0f/gi;
25 | code = code.replace(regex, "");
26 | }
27 |
28 | if (format == "png") {
29 | folder = "72x72";
30 | }
31 |
32 | url = `https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/${folder}/${code}.${format}`;
33 |
34 | return url;
35 | }
36 |
--------------------------------------------------------------------------------
/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import css from "./Header.module.scss";
2 |
3 | export default function Header() {
4 | return (
5 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/components/SearchBox.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import css from "./SearchBox.module.scss";
3 | import Fuse from "fuse.js";
4 |
5 | export default function SearchBox({ setAvailabeEmojis, emojis }) {
6 | const fuse = new Fuse(emojis, {
7 | threshold: 0.1,
8 | keys: ["label", "tags", "emoji", "hexcode", "emoticon"],
9 | });
10 |
11 | const [query, setQuery] = useState("");
12 |
13 | function handleSearch(query) {
14 | if (query == "") {
15 | setAvailabeEmojis(emojis);
16 | return;
17 | }
18 | let results = fuse.search(query);
19 | let flatResults = Object.keys(results).reduce(function (r, k) {
20 | return r.concat(results[k].item);
21 | }, []);
22 | setAvailabeEmojis(flatResults);
23 | }
24 |
25 | useEffect(() => {
26 | const timeout = setTimeout(() => handleSearch(query), 200);
27 | return () => clearTimeout(timeout);
28 | }, [query, handleSearch]);
29 |
30 | return (
31 | setQuery(e.target.value)}
35 | placeholder="search by name, emoticon, tags, hexcode..."
36 | />
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/components/Dropdown.module.scss:
--------------------------------------------------------------------------------
1 | @use "styles/colors" as *;
2 |
3 | .box {
4 | position: relative;
5 | width: 120px;
6 | }
7 |
8 | $rad: 8px;
9 |
10 | .selector {
11 | composes: button from "./Button.module.scss";
12 | margin-bottom: 8px;
13 | display: grid;
14 | grid-template-columns: 1fr auto;
15 | align-items: center;
16 | padding: 8px;
17 | height: 3em;
18 | }
19 |
20 | .reset {
21 | background-color: var(--button);
22 | color: var(--text);
23 | }
24 |
25 | .menu {
26 | position: absolute;
27 | min-width: 100%;
28 | width: max-content;
29 | display: flex;
30 | flex-direction: column;
31 | z-index: 1000;
32 | cursor: pointer;
33 | border-radius: $rad;
34 | box-shadow: 0px 1px 4px 2px var(--shadow);
35 | left: 50%;
36 | transform: translate(-50%, 0);
37 | }
38 |
39 | .menu__item {
40 | cursor: pointer;
41 | text-transform: capitalize;
42 | padding: 8px 16px;
43 | font-weight: 600;
44 | border-bottom: solid 1px rgba(0,0,0,0.1);
45 | background-color: var(--box);
46 | color: var(--text);
47 |
48 | &:first-child {
49 | border-radius: $rad $rad 0 0;
50 | }
51 |
52 | &:last-child {
53 | border-radius: 0 0 $rad $rad;
54 | }
55 |
56 | &:hover, &:focus {
57 | background-color: $yellow;
58 | color: black;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import "../styles/globals.scss";
2 | import { useEffect } from "react";
3 | import Script from "next/script";
4 | import { useRouter } from "next/router";
5 | import * as gtag from "../lib/gtag";
6 |
7 | function App({ Component, pageProps }) {
8 | const router = useRouter();
9 | useEffect(() => {
10 | const handleRouteChange = (url) => {
11 | gtag.pageview(url);
12 | };
13 | router.events.on("routeChangeComplete", handleRouteChange);
14 | return () => {
15 | router.events.off("routeChangeComplete", handleRouteChange);
16 | };
17 | }, [router.events]);
18 |
19 | return (
20 | <>
21 | {/* Global Site Tag (gtag.js) - Google Analytics */}
22 |
26 |
40 |
41 | >
42 | );
43 | }
44 |
45 | export default App;
46 |
--------------------------------------------------------------------------------
/styles/globals.scss:
--------------------------------------------------------------------------------
1 | @use "colors" as *;
2 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&family=JetBrains+Mono:wght@400&display=swap");
3 |
4 | *,
5 | *:after,
6 | *:before {
7 | box-sizing: border-box;
8 | padding: 0;
9 | margin: 0;
10 | font-family: "Poppins";
11 | transition: all 0.1s ease-in-out !important;
12 | }
13 |
14 | [data-theme="purple"] {
15 | --bg: hsl(280, 37%, 30%);
16 | --nav: hsl(280, 37%, 25%);
17 | --box: hsl(280, 37%, 25%);
18 | --input: hsl(280, 37%, 25%);
19 | --button: hsl(280, 37%, 20%);
20 | --shadow: hsla(280, 37%, 10%, 70%);
21 | --text: #f8ecf4;
22 | --yellow: #{$yellow};
23 | }
24 |
25 | [data-theme="black"] {
26 | --bg: hsl(0, 0%, 5%);
27 | --nav: hsl(0, 0%, 10%);
28 | --box: hsl(0, 0%, 10%);
29 | --input: hsl(0, 0%, 10%);
30 | --button: hsl(0, 0%, 15%);
31 | --shadow: hsla(0, 0%, 0%, 100%);
32 | --text: hsl(0, 0%, 100%);
33 | --yellow: #{$yellow};
34 | }
35 |
36 | [data-theme="white"] {
37 | --bg: hsl(43, 30%, 100%);
38 | --nav: hsl(43, 30%, 95%);
39 | --box: hsl(43, 31%, 95%);
40 | --input: hsl(43, 30%, 95%);
41 | --button: hsl(43, 30%, 90%);
42 | --shadow: hsla(0, 0%, 40%, 40%);
43 | --text: hsl(0, 0%, 15%);
44 | --yellow: #{$dark-yellow};
45 | }
46 |
47 | html,
48 | body,
49 | #__next {
50 | background-color: var(--bg);
51 | color: var(--text);
52 | accent-color: $yellow;
53 | }
54 |
55 | html {
56 | overflow-y: scroll;
57 | }
58 |
59 | main {
60 | position: relative;
61 | padding: 24px 10%;
62 | min-height: calc(100vh - 89px);
63 |
64 | @media screen and (max-width: 960px) {
65 | padding: 24px 5%;
66 | }
67 | }
68 |
69 | a {
70 | text-decoration: none !important;
71 | }
72 |
73 | button {
74 | outline: none;
75 | border: none;
76 | }
77 |
--------------------------------------------------------------------------------
/components/Dropdown.jsx:
--------------------------------------------------------------------------------
1 | import Downshift from "downshift";
2 | import Button from "./Button";
3 | import css from "./Dropdown.module.scss";
4 |
5 | export default function Dropdown({ onChangeFunc, onClearFunc, items, name }) {
6 | return (
7 | {
10 | selected ? onChangeFunc(selected.val) : onClearFunc();
11 | }}
12 | >
13 | {({
14 | getItemProps,
15 | isOpen,
16 | toggleMenu,
17 | clearSelection,
18 | selectedItem,
19 | }) => (
20 |
21 |
32 |
33 | {isOpen ? (
34 |
35 | {items.map((item) => (
36 |
43 | ))}
44 |
45 | ) : null}
46 | {selectedItem && onClearFunc && (
47 |
54 | )}
55 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/components/SelectedEmoji.jsx:
--------------------------------------------------------------------------------
1 | import css from "./SelectedEmoji.module.scss";
2 | import Image from "next/image";
3 | import Button from "./Button";
4 | import { getEmojiUrl } from "../utils/getEmojiUrl";
5 | import { useState, useEffect } from "react";
6 |
7 | export default function SelectedEmoji({ emoji, skinTone, closeFunc }) {
8 |
9 | const [buttonText, setButtonText] = useState(`Copy`)
10 | const [tooltipText, setTooltipText] = useState("Click to Copy")
11 |
12 | const img = getEmojiUrl(emoji, skinTone)
13 |
14 | function copyToClipboard(val, place) {
15 | navigator.clipboard.writeText(val);
16 | if (place == "button") {
17 | setButtonText("Copied!")
18 | setTimeout(() => setButtonText(`Copy`), 1000)
19 | } else {
20 | setTooltipText("Copied!")
21 | setTimeout(() => setTooltipText("Click to Copy"), 1000)
22 | }
23 | }
24 |
25 | return (
26 |
27 |
28 |
34 |
35 |
36 |
37 |
38 | {emoji.label}
39 |
42 |
43 |
44 | Unicode:{emoji.emoji}
45 |
46 | {/*
{emoji.tags.join(", ")}
*/}
47 |
48 | Since:Version {emoji.version}
49 |
50 |
51 | HEX:
52 | copyToClipboard(emoji.hexcode)}>{emoji.hexcode}
53 |
54 |
55 |
56 |
57 |
61 |
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/components/SelectedEmoji.module.scss:
--------------------------------------------------------------------------------
1 | @use "styles/colors" as *;
2 |
3 | .selected {
4 | position: fixed;
5 | // top: 0;
6 | left: 32px;
7 | bottom: 24px;
8 | min-height: 120px;
9 | width: 320px;
10 | max-width: calc(100vw - 64px);
11 | background-color: var(--box);
12 | color: var(--text);
13 | z-index: 1000;
14 | border-radius: 16px;
15 | box-shadow: 0px 0px 96px 48px var(--shadow);
16 | padding: 16px 20px;
17 |
18 | display: grid;
19 | grid-template-columns: 64px 1fr;
20 | gap: 20px;
21 | }
22 |
23 | .close {
24 | cursor: pointer;
25 | width: 24px;
26 | height: 24px;
27 | font-size: 24px;
28 | background-color: var(--modal);
29 | text-align: center;
30 | line-height: 24px;
31 | font-weight: 600;
32 | border-radius: 50%;
33 | outline: solid 2px transparent;
34 | margin: 4px 4px 0 0;
35 | color: var(--text);
36 |
37 | &:hover {
38 | background-color: var(--yellow);
39 | color: black;
40 | }
41 |
42 | &:focus {
43 | outline-color: var(--yellow);
44 | }
45 | }
46 |
47 | .col--preview {
48 | display: flex;
49 | flex-direction: column;
50 | justify-content: space-between;
51 | height: 100%;
52 | gap: 8px;
53 | }
54 |
55 | .col--info {
56 | height: 100%;
57 | overflow: clip;
58 | font-size: 0.9em;
59 |
60 | .info {
61 | font-weight: 600;
62 | width: 72px;
63 | display: inline-block;
64 | }
65 | }
66 |
67 | .title {
68 | text-transform: capitalize;
69 | font-size: 1.5em;
70 | line-height: 1.3;
71 | margin-bottom: 8px;
72 | }
73 |
74 | .header {
75 | display: grid;
76 | grid-template-columns: 1fr 26px;
77 | width: 100%;
78 | gap: 4px;
79 | }
80 |
81 | .hexcode {
82 | font-family: "Jetbrains Mono", "Consolas", monospace;
83 | display: inline-block; // for long hexcodes
84 | cursor: pointer;
85 | position: relative;
86 |
87 | &::after {
88 | opacity: 0;
89 | visibility: hidden;
90 | width: max-content;
91 | content: attr(data-tooltip);
92 | display: block;
93 | position: absolute;
94 | background-color: var(--yellow);
95 | color: black;
96 | font-size: 0.8em;
97 | padding: 0 2px;
98 | bottom: 20px;
99 | left: 50%;
100 | transform: translate(-50%, 0);
101 | text-align: center;
102 | border-radius: 4px;
103 | font-family: "Poppins";
104 | }
105 |
106 | &:hover:after {
107 | opacity: 1;
108 | visibility: visible;
109 | }
110 | }
111 |
112 | .buttons {
113 | grid-column: span 2;
114 | display: flex;
115 | gap: 8px;
116 | }
--------------------------------------------------------------------------------
/components/SettingsBar.jsx:
--------------------------------------------------------------------------------
1 | import css from "./SettingsBar.module.scss";
2 | // import { groups, subgroups } from "emojibase-data/meta/groups.json";
3 | import data from "emojibase-data/en/messages.json";
4 | import Dropdown from "./Dropdown";
5 | import SearchBox from "./SearchBox";
6 | import { useAppSettings } from "../utils/store";
7 |
8 | export default function SettingsBar({
9 | filterByGroup,
10 | filterByVersion,
11 | sizeSlider,
12 | versions,
13 | setAvailabeEmojis,
14 | emojis,
15 | }) {
16 | const groupFilters = data.groups.map(({ message, order, ...rest }) => ({
17 | label: message,
18 | val: order.toString(),
19 | ...rest,
20 | }));
21 |
22 | const versionFilters = new Array(versions.length).fill().map((e, i) => {
23 | return { val: versions[i].toString(), label: versions[i] };
24 | });
25 |
26 | const skinToneFilters = [
27 | { label: "🏿 dark", val: "1F3FF" },
28 | { label: "🏾 medium dark", val: "1F3FE" },
29 | { label: "🏽 medium", val: "1F3FD" },
30 | { label: "🏼 medium light", val: "1F3FC" },
31 | { label: "🏻 light", val: "1F3FB" },
32 | ];
33 |
34 | const themes = [
35 | { label: "⬛ black", val: "black" },
36 | { label: "⬜ white", val: "white" },
37 | { label: "🟪 purple", val: "purple" },
38 | ];
39 |
40 | const setSkinTone = useAppSettings((state) => state.setSkinTone);
41 | const setTheme = useAppSettings((state) => state.setTheme);
42 | const setEmojiSize = useAppSettings((state) => state.setEmojiSize);
43 | const emojiSize = useAppSettings((state) => state.emojiSize);
44 |
45 | return (
46 |
47 |
48 |
49 |
50 | filterByGroup(null)}
54 | name="All Groups"
55 | />
56 | filterByVersion(null)}
60 | name="All Versions"
61 | />
62 | setSkinTone("")}
66 | name="Skin Tones"
67 | />
68 |
73 |
74 | setEmojiSize(e.target.value)}
81 | />
82 |
83 |
84 | {/*
85 |
Info
86 |
Total Emojis: {totalEmojis}
87 |
*/}
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 | import EmojiCard from "../components/EmojiCard";
3 | import emojis from "emojibase-data/en/data.json";
4 | import Grid from "../components/Grid";
5 | import { useEffect, useState } from "react";
6 | import SelectedEmoji from "../components/SelectedEmoji";
7 | import SettingsBar from "../components/SettingsBar";
8 | import Intro from "../components/Intro";
9 | import Footer from "../components/Header";
10 | import { useAppSettings } from "../utils/store";
11 |
12 | export default function Home(props) {
13 | const [selectedEmoji, setSelectedEmoji] = useState();
14 | const [availableEmojis, setAvailabeEmojis] = useState(emojis);
15 |
16 | const [selectedVersion, setSelectedVersion] = useState();
17 | const [selectedGroup, setSelectedGroup] = useState();
18 |
19 | function closeSelectedEmojiCard() {
20 | setSelectedEmoji(null);
21 | }
22 |
23 | const versions = [...new Set(emojis.map((emoji) => emoji.version))];
24 | versions.sort((a, b) => b - a);
25 |
26 | const selectedSkinTone = useAppSettings((state) => state.skinTone);
27 | const appTheme = useAppSettings((state) => state.theme);
28 | const emojiSize = useAppSettings((state) => state.emojiSize);
29 |
30 | useEffect(() => {
31 | document.body.setAttribute('data-theme', appTheme)
32 | }, [appTheme]);
33 |
34 | return (
35 | <>
36 |
37 | Twemoji Cheatsheet
38 |
39 |
43 |
44 |
48 |
49 |
50 |
54 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
72 |
73 |
74 | {availableEmojis
75 | .filter(
76 | (emoji) =>
77 | (selectedVersion
78 | ? emoji.version == selectedVersion
79 | : true) &&
80 | (selectedGroup
81 | ? emoji.group == selectedGroup
82 | : true)
83 | )
84 | .map((emoji) => {
85 | return (
86 | setSelectedEmoji(emoji)}
91 | skinTone={selectedSkinTone}
92 | />
93 | );
94 | })}
95 | {availableEmojis.length == 0 && Nothing found 😑
}
96 |
97 |
98 | {selectedEmoji && (
99 |
104 | )}
105 |
106 | >
107 | );
108 | }
109 |
--------------------------------------------------------------------------------