Repository: carloscuesta/gitmoji
Branch: master
Commit: dc7b6915e132
Files: 118
Total size: 132.4 KB
Directory structure:
gitextract_os7qfgki/
├── .editorconfig
├── .github/
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.yml
│ │ ├── config.yml
│ │ ├── discussion.yml
│ │ ├── feature-request.yml
│ │ └── gitmoji-proposal.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci.yml
│ ├── lock.yml
│ └── npm-publish.yml
├── .gitignore
├── .husky/
│ ├── .gitignore
│ ├── pre-commit
│ └── pre-push
├── .lintstagedrc.json
├── .node-version
├── AGENTS.md
├── LICENSE
├── README.md
├── package.json
├── packages/
│ ├── gitmojis/
│ │ ├── .lintstagedrc.json
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── gitmojis.json
│ │ ├── index.d.ts
│ │ ├── index.js
│ │ └── schema.json
│ └── website/
│ ├── .lintstagedrc.json
│ ├── __mocks__/
│ │ └── svg.js
│ ├── eslint.config.mjs
│ ├── jest.config.js
│ ├── jest.d.ts
│ ├── jest.setup.js
│ ├── jsconfig.json
│ ├── next-env.d.ts
│ ├── next-sitemap.config.js
│ ├── next-sitemap.js
│ ├── next.config.js
│ ├── package.json
│ ├── public/
│ │ ├── _redirects
│ │ └── static/
│ │ ├── browserconfig.xml
│ │ ├── manifest.json
│ │ └── opensearchdescription.xml
│ ├── scripts/
│ │ └── generate-api.js
│ ├── src/
│ │ ├── __tests__/
│ │ │ ├── pages.spec.tsx
│ │ │ └── stubs.tsx
│ │ ├── app/
│ │ │ ├── about/
│ │ │ │ └── page.tsx
│ │ │ ├── contributors/
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── page.tsx
│ │ │ ├── related-tools/
│ │ │ │ └── page.tsx
│ │ │ └── specification/
│ │ │ └── page.tsx
│ │ ├── components/
│ │ │ ├── Button/
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── button.spec.tsx
│ │ │ │ │ └── stubs.ts
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.css
│ │ │ ├── CarbonAd/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── carbonAd.spec.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.css
│ │ │ ├── ContributorsList/
│ │ │ │ ├── Contributor/
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.css
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── contributorsList.spec.tsx
│ │ │ │ │ └── stubs.ts
│ │ │ │ └── index.tsx
│ │ │ ├── GitmojiList/
│ │ │ │ ├── Gitmoji/
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.css
│ │ │ │ ├── SearchParamsSync.tsx
│ │ │ │ ├── Toolbar/
│ │ │ │ │ ├── Kbd/
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ └── styles.module.css
│ │ │ │ │ ├── ListModeSelector/
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ └── styles.module.css
│ │ │ │ │ ├── ThemeSelector/
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ └── styles.module.css
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.css
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── gitmojiList.spec.tsx
│ │ │ │ │ └── stubs.ts
│ │ │ │ ├── emojiColorsMap.ts
│ │ │ │ ├── hooks/
│ │ │ │ │ ├── __tests__/
│ │ │ │ │ │ ├── stubs.ts
│ │ │ │ │ │ └── useLocalStorage.spec.tsx
│ │ │ │ │ └── useLocalStorage.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.css
│ │ │ ├── Icon/
│ │ │ │ ├── __tests__/
│ │ │ │ │ ├── icon.spec.tsx
│ │ │ │ │ └── stubs.ts
│ │ │ │ ├── definitions.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.css
│ │ │ └── Layout/
│ │ │ ├── Footer/
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.css
│ │ │ ├── Hamburger/
│ │ │ │ ├── CloseIcon/
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── MenuLink/
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.css
│ │ │ │ ├── OpenIcon/
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.css
│ │ │ ├── Header/
│ │ │ │ ├── Logo/
│ │ │ │ │ ├── Status/
│ │ │ │ │ │ ├── Joy/
│ │ │ │ │ │ │ └── index.tsx
│ │ │ │ │ │ ├── Loved/
│ │ │ │ │ │ │ └── index.tsx
│ │ │ │ │ │ ├── Sexy/
│ │ │ │ │ │ │ └── index.tsx
│ │ │ │ │ │ ├── Smiling/
│ │ │ │ │ │ │ └── index.tsx
│ │ │ │ │ │ ├── Sunglasses/
│ │ │ │ │ │ │ └── index.tsx
│ │ │ │ │ │ ├── Tongue/
│ │ │ │ │ │ │ └── index.tsx
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ └── styles.module.css
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.module.css
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.css
│ │ │ ├── __tests__/
│ │ │ │ ├── layout.spec.tsx
│ │ │ │ └── stubs.ts
│ │ │ └── index.tsx
│ │ └── utils/
│ │ └── theme/
│ │ └── theme.css
│ └── tsconfig.json
├── pnpm-workspace.yaml
└── turbo.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true
[*]
indent_style = spaces
tab_width = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.js]
indent_style = spaces
tab_width = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing to gitmoji
Hello!
Thanks for contributing on [gitmoji](https://github.com/carloscuesta/gitmoji). Before implementing new features and changes, please [submit an issue](https://github.com/carloscuesta/gitmoji/issues/new). We will discuss here! :stuck_out_tongue_winking_eye:.
If you would like to add a new emoji to gitmoji, fill the provided `ISSUE_TEMPLATE` when creating an issue and take a look at the contributing section.
## How to submit a pull request?
1. Fork [this repository](https://github.com/carloscuesta/gitmoji/fork).
2. Create a new branch with the feature name. (Eg: add-emoji-deploy, fix-website-header)
3. Make your changes.
4. Test you changes by running `pnpm turbo test`
- 4.1. If the snapshots are failing run `pnpm turbo test -- -u` and be sure that the new snapshots match your changes
5. Commit your changes. Don't forget to add a commit title with an emoji and a description.
6. Push your changes.
7. Submit your pull request.
## How to add a gitmoji
1. Open the **gitmojis.json** file located at `packages/gitmojis/src/gitmojis.json`.
2. Add your emoji using the following code inside of the `gitmojis array []`:
3. Add a new color to [the emojiColorsMap.js](https://github.com/carloscuesta/gitmoji/blob/master/packages/website/src/components/GitmojiList/emojiColorsMap.js) file. Matching the name you added at the JSON file.
4. Save the file and create a pull request.
```json
{
"emoji": "",
"entity": "entity (Ex: 👀)",
"code": ":code:",
"description": "Enter the description for the gitmoji. Use present form for verbs.",
"name": "code (same as code but without ':' replace underscores for dashes _ => - )",
"semver": "The semantic versioning effect (can be `'major'`, `'minor'`, `'patch'` or `null` if the commit has no effect on the version)"
}
```
If you want to find the hexadecimal entity of icon, search for it in this site: <a>http://graphemica.com/</a>
Every suggestion will be reviewed carefully, ⚠️ take into account that not every suggestion will be accepted!
## How to start the website
If you want to make changes to the site, follow the next steps:
1. Clone gitmoji
```bash
$ git clone https://github.com/carloscuesta/gitmoji.git
$ cd gitmoji
```
2. Install the dependencies and start the development server.
```bash
$ pnpm install && pnpm run dev
```
The project is built with [Next.js](http://nextjs.org)
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: ['carloscuesta']
custom: ['https://paypal.me/carloscuesta']
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.yml
================================================
name: 🐛 Bug report
description: Report an issue
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: bug-description
attributes:
label: Describe the bug
description: A clear description the bug. If you want to contribute to fix this issue, tell us in the description.
placeholder: Description
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction
description: A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is **required**, otherwise the issue might be closed without further notice.
placeholder: Reproduction
validations:
required: true
- type: textarea
id: system-info
attributes:
label: System Info
description: Output of `npx envinfo --system --binaries --browsers`
render: Shell
placeholder: Paste the output of the following command
validations:
required: true
- type: checkboxes
id: validations
attributes:
label: Validations
description: Before submitting the issue, please make sure you do the following
options:
- label: Follow our [Code of Conduct](https://github.com/carloscuesta/.github/blob/master/.github/CODE_OF_CONDUCT.md)
required: true
- label: Read the [Contributing Guide](https://github.com/carloscuesta/gitmoji/blob/master/.github/CONTRIBUTING.md).
required: true
- label: Check that there isn't already an issue that reports the same bug to avoid creating duplicates.
required: true
- label: Check that this is a concrete bug. For Q&A, please open a GitHub Discussion instead.
required: true
- label: The provided reproduction is a [minimal reproducible](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: 😍 Contribution Guide
url: https://github.com/carloscuesta/gitmoji/blob/master/.github/CONTRIBUTING.md#contributing-to-gitmoji
about: Please read through before making any contribution.
================================================
FILE: .github/ISSUE_TEMPLATE/discussion.yml
================================================
name: ⁉️ Discussion
description: Want to discuss something? Use this template
labels: [discussion]
body:
- type: markdown
attributes:
value: |
Thanks for your interest in the project and taking the time to fill out this discussion report!
- type: textarea
id: discussion-description
attributes:
label: Discussion
description: 'Explain the matter here!'
validations:
required: true
- type: checkboxes
id: checkboxes
attributes:
label: Validations
description: Before submitting the issue, please make sure you do the following
options:
- label: Follow our [Code of Conduct](https://github.com/carloscuesta/.github/blob/master/.github/CODE_OF_CONDUCT.md)
required: true
- label: Read the [Contributing Guide](https://github.com/carloscuesta/gitmoji/blob/master/.github/CONTRIBUTING.md).
required: true
- label: Check that there isn't already an issue requesting the same feature.
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.yml
================================================
name: 🚀 Feature proposal
description: Propose a new feature
labels: [feature]
body:
- type: markdown
attributes:
value: |
Thanks for your interest in the project and taking the time to fill out this feature report!
- type: textarea
id: feature-description
attributes:
label: Description of the problem
description: 'As a user I want [goal] so that [benefit]. If you want to contribute with a PR, tell us in the description. Thanks!'
validations:
required: true
- type: textarea
id: suggested-solution
attributes:
label: Solution
description: 'In module [xy] we could provide following implementation...'
validations:
required: true
- type: textarea
id: alternative
attributes:
label: Alternatives
description: Explain any alternative solutions or features you've considered.
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Any other context or screenshots about the feature.
- type: checkboxes
id: checkboxes
attributes:
label: Validations
description: Before submitting the issue, please make sure you do the following
options:
- label: Follow our [Code of Conduct](https://github.com/carloscuesta/.github/blob/master/.github/CODE_OF_CONDUCT.md)
required: true
- label: Read the [Contributing Guide](https://github.com/carloscuesta/gitmoji/blob/master/.github/CONTRIBUTING.md).
required: true
- label: Check that there isn't already an issue requesting the same feature.
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/gitmoji-proposal.yml
================================================
name: 😜 Gitmoji proposal
description: Suggest a new gitmoji!
labels: [emoji]
body:
- type: markdown
attributes:
value: |
Thanks for your interest in the project and taking the time to fill out this gitmoji suggestion!
- type: input
id: emoji-symbol
attributes:
label: Emoji symbol
description: The emoji symbol you want to suggest.
placeholder: "🚀"
validations:
required: true
- type: input
id: emoji-shortcode
attributes:
label: Emoji code
description: The ":shortcode:" of the emoji on GitHub.
placeholder: ":rocket:"
validations:
required: true
- type: input
id: emoji-description
attributes:
label: Emoji description
description: A short description of the emoji.
placeholder: What this emoji should be used for?
validations:
required: true
- type: textarea
id: emoji-use-case
attributes:
label: Describe the use case of your emoji
description: Explain the creation of this emoji, what this is and when it should be used
placeholder: Use case
validations:
required: true
- type: dropdown
id: emoji-use-case-covered
attributes:
label: Is this use case covered by an existing emoji?
options:
- "Yes ✅"
- "No ❌"
validations:
required: true
- type: checkboxes
id: emoji-what-how
attributes:
label: Does this emoji fall into the "how" or the "what" category?
description: |
We are trying to always describe/categorize "what" has been done in a particular commit, not the "how" it was done (the exceptions being :poop: and :beers:).
Notice here that, by the descriptions on the "how" category, you can't know what has been achieved in the commit.
| Examples of "what" commits | Examples of "how" commits |
| -------------------------- | ------------------------- |
| :white_check_mark: Add, update, or pass tests | :hankey: Write bad code that needs to be improved |
| :lock: Fix security or privacy issues | :beers: Write code drunkenly |
| :zap: Improve performance | :robot: Write an automated commit by a script |
options:
- label: This proposal do **not** describe "how" a commit was made, but does in fact describe "what" is the contents of the commit about.
required: true
validations:
required: true
- type: textarea
id: emoji-examples
attributes:
label: Examples
description: Include some examples using this emoji.
placeholder: Examples
validations:
required: true
- type: checkboxes
id: checkboxes
attributes:
label: Validations
description: Before submitting the issue, please make sure you do the following
options:
- label: Follow our [Code of Conduct](https://github.com/carloscuesta/.github/blob/master/.github/CODE_OF_CONDUCT.md)
required: true
- label: Read the [Contributing Guide](https://github.com/carloscuesta/gitmoji/blob/master/.github/CONTRIBUTING.md).
required: true
- label: Check that there isn't already an issue requesting the emoji.
required: true
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
Please, before opening a PR, first open an issue as stated in the [contributing guidelines][1],
so we can talk about features and discuss implementations.
[1]: https://github.com/carloscuesta/gitmoji/blob/master/.github/CONTRIBUTING.md#contributing-to-gitmoji
-->
## Description
<!-- Explanation about your pull request, what changes you've made -->
## Linked issues
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: monthly
time: "04:00"
open-pull-requests-limit: 10
labels:
- "dependencies"
versioning-strategy: increase
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: monthly
time: "04:00"
labels:
- "dependencies"
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
ci:
runs-on: ubuntu-latest
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- name: Install Node.js
uses: actions/setup-node@v6
with:
cache: "pnpm"
node-version-file: ".node-version"
- name: Install dependencies 📦
run: pnpm install
- name: Lint 🎨
run: pnpm turbo lint
- name: TypeScript check 🏷
run: pnpm turbo tscheck
- name: Tests ✅
run: pnpm turbo test
================================================
FILE: .github/workflows/lock.yml
================================================
name: Lock Issues and PRs
on:
schedule:
- cron: '0 0 * * *'
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v6
with:
github-token: ${{ github.token }}
issue-inactive-days: '1'
pr-inactive-days: '1'
================================================
FILE: .github/workflows/npm-publish.yml
================================================
name: NPM Publish
on:
push:
tags:
- "v*"
permissions:
id-token: write
contents: write
jobs:
npm-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
cache: "pnpm"
node-version-file: ".node-version"
- name: Install dependencies 📦
run: pnpm install
- name: Build 👷♂️
run: pnpm turbo run build --filter=gitmojis
- name: Publish package to NPM 🚀
run: pnpm turbo publishPackage
- name: Publish GitHub Release 📝
uses: softprops/action-gh-release@v2
with:
name: gitmoji ${{github.ref_name}}
generate_release_notes: true
================================================
FILE: .gitignore
================================================
.DS_Store
dist/
node_modules/
.publish/
.next
out/
coverage/
.eslintcache
*.log
.pnp.*
# next-pwa
packages/website/public/workbox-*.js
packages/website/public/workbox-*.js
packages/website/public/sw.js
packages/website/public/sw.js
packages/website/public/*.map
# next-sitemap
packages/website/public/robots.txt
packages/website/public/sitemap.xml
packages/website/public/sitemap-*.xml
# gitmojis
packages/website/public/api/
# TS
*.tsbuildinfo
# Turbo
.turbo
================================================
FILE: .husky/.gitignore
================================================
_
================================================
FILE: .husky/pre-commit
================================================
pnpm exec lint-staged
================================================
FILE: .husky/pre-push
================================================
pnpm turbo tscheck && pnpm turbo test
================================================
FILE: .lintstagedrc.json
================================================
{
"*.json": ["prettier --write"],
"*.md": ["prettier --write"],
"*.yml": ["prettier --write"]
}
================================================
FILE: .node-version
================================================
24
================================================
FILE: AGENTS.md
================================================
# Gitmoji Guide for AI Assistants
## Purpose
This guide helps AI assistants understand and use gitmoji convention when creating commits. Using emojis on commit messages provides an easy way of identifying the purpose or intention of a commit with only looking at the emojis used. Gitmoji use emojis to make commit messages more expressive and easier to understand at a glance.
## Official Specification
A gitmoji commit message is composed using the following pieces:
- **intention**: The intention you want to express with the commit, using an emoji from the gitmoji list. Either in the `:shortcode:` or unicode format.
- **scope**: An optional string that adds contextual information for the scope of the change.
- **message**: A brief explanation of the change.
### Format
```
<intention> [scope?][:?] <message>
[optional body]
```
## Gitmoji reference
Fetch all available gitmojis from: https://gitmoji.dev/api/gitmojis.
## Usage Guidelines for AI
### Selecting the correct emoji
1. **Identify the primary purpose** of the commit
2. **Choose the most specific emoji** that matches the change
3. **Use only one emoji** per commit for clarity
4. **Prioritize by impact**: Breaking changes (💥) > Features (✨) > Fixes (🐛) > Refactoring (♻️)
### Examples
```
✨ feat: Add user authentication system
Implement JWT-based authentication with login and registration endpoints.
Closes #123
```
```
🐛 Resolve null pointer exception in user service
Added null check before accessing user properties to prevent crashes.
```
```
📝 docs: Update installation instructions
Added step-by-step guide for setting up the development environment.
```
```
⚡️ Optimize user query with indexing
Reduced query time from 500ms to 50ms by adding composite index.
```
```
💥 Update API response format to REST specification
All API endpoints now return data in a standardized envelope format.
Clients must update their response parsing logic.
```
## Best Practices
1. **Be atomic**: One emoji, one purpose, one commit
2. **Write clear subjects**: Keep under 60 characters, imperative mood
3. **Use the body**: Explain "why" not "what" for complex changes
4. **Reference issues**: Include issue numbers when applicable
5. **Indicate breaking changes**: Use 💥 `:boom:`.
## Resources
- Gitmojis list: https://gitmoji.dev/api/gitmojis
- Gitmoji website: https://gitmoji.dev/
- Gitmoji specification: https://gitmoji.dev/specification
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2016-2022 Carlos Cuesta
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<p align="center">
<a href="https://gitmoji.dev">
<img src="https://cloud.githubusercontent.com/assets/7629661/20073135/4e3db2c2-a52b-11e6-85e1-661a8212045a.gif" width="456" alt="gitmoji">
</a>
</p>
<p align="center">
<a href="https://github.com/carloscuesta/gitmoji/actions?query=workflow%3ACI+branch%3Amaster">
<img src="https://img.shields.io/github/actions/workflow/status/carloscuesta/gitmoji/ci.yml?branch=master&style=flat-square"
alt="Build Status">
</a>
<a href="https://gitmoji.dev">
<img src="https://img.shields.io/badge/gitmoji-%20😜%20😍-FFDD67.svg?style=flat-square"
alt="Gitmoji">
</a>
</p>
## About
[Gitmoji](https://gitmoji.dev) is an initiative to standardize and explain **the use of emojis on GitHub commit messages**.
**Using emojis** on **commit messages** provides an **easy way** of **identifying the purpose or intention of a commit** with only looking at the emojis used. As there are a lot of different emojis I found the need of creating a guide that can help to use emojis easier.
The gitmojis are published on the [following package](https://www.npmjs.com/package/gitmojis) in order to be used as a dependency 📦.
## Using [gitmoji-cli](https://github.com/carloscuesta/gitmoji-cli)
To use gitmojis from your command line install [gitmoji-cli](https://github.com/carloscuesta/gitmoji-cli). A gitmoji interactive client for using emojis on commit messages.
```bash
npm i -g gitmoji-cli
```
## Example of usage
In case you need some ideas to integrate gitmoji in your project, here's a practical way to use it:
```
<intention> [scope?][:?] <message>
```
- `intention`: An emoji from the list.
- `scope`: An optional string that adds contextual information for the scope of the change.
- `message`: A brief explanation of the change.
## Contributing to gitmoji
Contributing to gitmoji is a piece of :cake:, read the [contributing guidelines](https://github.com/carloscuesta/gitmoji/blob/master/.github/CONTRIBUTING.md). You can discuss emojis using the [issues section](https://github.com/carloscuesta/gitmoji/issues/new). To add a new emoji to the list create an issue and send a pull request, see [how to send a pull request and add a gitmoji](https://github.com/carloscuesta/gitmoji/blob/master/.github/CONTRIBUTING.md#how-to-add-a-gitmoji).
## Spread the word
Are you using Gitmoji on your project? Set the Gitmoji badge on top of your readme using this code:
```html
<a href="https://gitmoji.dev">
<img
src="https://img.shields.io/badge/gitmoji-%20😜%20😍-FFDD67.svg?style=flat-square"
alt="Gitmoji"
/>
</a>
```
## License
The code is available under the [MIT](https://github.com/carloscuesta/gitmoji/blob/master/LICENSE) license.
================================================
FILE: package.json
================================================
{
"name": "gitmoji",
"private": true,
"engines": {
"node": "22",
"pnpm": ">=8"
},
"scripts": {
"prepare": "husky install",
"dev": "pnpm turbo --parallel dev"
},
"devDependencies": {
"husky": "^9.1.7",
"lint-staged": "^16.2.7",
"prettier": "3.8.1",
"turbo": "2.8.16"
},
"packageManager": "pnpm@8.6.2"
}
================================================
FILE: packages/gitmojis/.lintstagedrc.json
================================================
{
"./src/*.json": ["prettier --write ./src/*.json"],
"./src/*.ts": ["prettier --write ./src/*.ts"],
"./src/*.js": ["prettier --write ./src/*.js"]
}
================================================
FILE: packages/gitmojis/README.md
================================================
<p align="center">
<a href="https://gitmoji.dev">
<img src="https://cloud.githubusercontent.com/assets/7629661/20073135/4e3db2c2-a52b-11e6-85e1-661a8212045a.gif" width="456" alt="gitmoji">
</a>
</p>
<p align="center">
<a href="https://github.com/carloscuesta/gitmoji/actions?query=workflow%3ACI+branch%3Amaster">
<img src="https://img.shields.io/github/actions/workflow/status/carloscuesta/gitmoji/ci.yml?branch=master&style=flat-square"
alt="Build Status">
</a>
<a href="https://gitmoji.dev">
<img src="https://img.shields.io/badge/gitmoji-%20😜%20😍-FFDD67.svg?style=flat-square"
alt="Gitmoji">
</a>
</p>
## About
The emojis from the [gitmoji](https://gitmoji.dev) convention **bundled** into a **node module**.
## Install
```bash
npm i gitmojis
```
## Usage
```js
import { gitmojis } from 'gitmojis'
console.log(gitmojis)
/*
[
{
emoji: '🎨',
entity: '🎨',
code: ':art:',
description: 'Improve structure / format of the code.',
name: 'art',
semver: null
},
{
emoji: '⚡️',
entity: '⚡',
code: ':zap:',
description: 'Improve performance.',
name: 'zap',
semver: null
},
...
]
*/
```
## API
Alternatively you can also consume this as through HTTP using the API:
```bash
curl https://gitmoji.dev/api/gitmojis
```
## Spread the word
Are you using Gitmoji on your project? Set the Gitmoji badge on top of your readme using this code:
```html
<a href="https://gitmoji.dev">
<img
src="https://img.shields.io/badge/gitmoji-%20😜%20😍-FFDD67.svg?style=flat-square"
alt="Gitmoji"
/>
</a>
```
================================================
FILE: packages/gitmojis/package.json
================================================
{
"name": "gitmojis",
"type": "module",
"version": "3.15.0",
"description": "An emoji guide for your commit messages.",
"main": "./dist/index.cjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"files": [
"dist"
],
"scripts": {
"dev": "nodemon --exec 'pnpm run build' --watch ./src",
"build": "unbuild",
"lint:json": "ajv --spec=draft2020 validate -s ./src/schema.json -d ./src/gitmojis.json",
"lint": "pnpm run lint:json && prettier --check ./src/**/*.{js,json,ts}",
"publishPackage": "npm publish"
},
"devDependencies": {
"ajv-cli": "^5.0.0",
"lint-staged": "^16.2.7",
"nodemon": "^3.1.14",
"prettier": "3.8.1",
"unbuild": "^3.6.1"
},
"author": {
"name": "carloscuesta",
"email": "hi@carloscuesta.me",
"url": "https://carloscuesta.me"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/carloscuesta/gitmoji/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/carloscuesta/gitmoji.git"
},
"homepage": "https://gitmoji.dev",
"keywords": [
"gitmoji",
"emoji",
"carloscuesta",
"commit"
],
"prettier": {
"semi": false,
"singleQuote": true,
"arrowParens": "always"
}
}
================================================
FILE: packages/gitmojis/src/gitmojis.json
================================================
{
"$schema": "https://gitmoji.dev/api/gitmojis/schema",
"gitmojis": [
{
"emoji": "🎨",
"entity": "🎨",
"code": ":art:",
"description": "Improve structure / format of the code.",
"name": "art",
"semver": null
},
{
"emoji": "⚡️",
"entity": "⚡",
"code": ":zap:",
"description": "Improve performance.",
"name": "zap",
"semver": "patch"
},
{
"emoji": "🔥",
"entity": "🔥",
"code": ":fire:",
"description": "Remove code or files.",
"name": "fire",
"semver": null
},
{
"emoji": "🐛",
"entity": "🐛",
"code": ":bug:",
"description": "Fix a bug.",
"name": "bug",
"semver": "patch"
},
{
"emoji": "🚑️",
"entity": "🚑",
"code": ":ambulance:",
"description": "Critical hotfix.",
"name": "ambulance",
"semver": "patch"
},
{
"emoji": "✨",
"entity": "✨",
"code": ":sparkles:",
"description": "Introduce new features.",
"name": "sparkles",
"semver": "minor"
},
{
"emoji": "📝",
"entity": "📝",
"code": ":memo:",
"description": "Add or update documentation.",
"name": "memo",
"semver": null
},
{
"emoji": "🚀",
"entity": "🚀",
"code": ":rocket:",
"description": "Deploy stuff.",
"name": "rocket",
"semver": null
},
{
"emoji": "💄",
"entity": "&#ff99cc;",
"code": ":lipstick:",
"description": "Add or update the UI and style files.",
"name": "lipstick",
"semver": "patch"
},
{
"emoji": "🎉",
"entity": "🎉",
"code": ":tada:",
"description": "Begin a project.",
"name": "tada",
"semver": null
},
{
"emoji": "✅",
"entity": "✅",
"code": ":white_check_mark:",
"description": "Add, update, or pass tests.",
"name": "white-check-mark",
"semver": null
},
{
"emoji": "🔒️",
"entity": "🔒",
"code": ":lock:",
"description": "Fix security or privacy issues.",
"name": "lock",
"semver": "patch"
},
{
"emoji": "🔐",
"entity": "🔐",
"code": ":closed_lock_with_key:",
"description": "Add or update secrets.",
"name": "closed-lock-with-key",
"semver": null
},
{
"emoji": "🔖",
"entity": "🔖",
"code": ":bookmark:",
"description": "Release / Version tags.",
"name": "bookmark",
"semver": null
},
{
"emoji": "🚨",
"entity": "🚨",
"code": ":rotating_light:",
"description": "Fix compiler / linter warnings.",
"name": "rotating-light",
"semver": null
},
{
"emoji": "🚧",
"entity": "🚧",
"code": ":construction:",
"description": "Work in progress.",
"name": "construction",
"semver": null
},
{
"emoji": "💚",
"entity": "💚",
"code": ":green_heart:",
"description": "Fix CI Build.",
"name": "green-heart",
"semver": null
},
{
"emoji": "⬇️",
"entity": "⬇️",
"code": ":arrow_down:",
"description": "Downgrade dependencies.",
"name": "arrow-down",
"semver": "patch"
},
{
"emoji": "⬆️",
"entity": "⬆️",
"code": ":arrow_up:",
"description": "Upgrade dependencies.",
"name": "arrow-up",
"semver": "patch"
},
{
"emoji": "📌",
"entity": "📌",
"code": ":pushpin:",
"description": "Pin dependencies to specific versions.",
"name": "pushpin",
"semver": "patch"
},
{
"emoji": "👷",
"entity": "👷",
"code": ":construction_worker:",
"description": "Add or update CI build system.",
"name": "construction-worker",
"semver": null
},
{
"emoji": "📈",
"entity": "📈",
"code": ":chart_with_upwards_trend:",
"description": "Add or update analytics or track code.",
"name": "chart-with-upwards-trend",
"semver": "patch"
},
{
"emoji": "♻️",
"entity": "♻",
"code": ":recycle:",
"description": "Refactor code.",
"name": "recycle",
"semver": null
},
{
"emoji": "➕",
"entity": "➕",
"code": ":heavy_plus_sign:",
"description": "Add a dependency.",
"name": "heavy-plus-sign",
"semver": "patch"
},
{
"emoji": "➖",
"entity": "➖",
"code": ":heavy_minus_sign:",
"description": "Remove a dependency.",
"name": "heavy-minus-sign",
"semver": "patch"
},
{
"emoji": "🔧",
"entity": "🔧",
"code": ":wrench:",
"description": "Add or update configuration files.",
"name": "wrench",
"semver": "patch"
},
{
"emoji": "🔨",
"entity": "🔨",
"code": ":hammer:",
"description": "Add or update development scripts.",
"name": "hammer",
"semver": null
},
{
"emoji": "🌐",
"entity": "🌐",
"code": ":globe_with_meridians:",
"description": "Internationalization and localization.",
"name": "globe-with-meridians",
"semver": "patch"
},
{
"emoji": "✏️",
"entity": "",
"code": ":pencil2:",
"description": "Fix typos.",
"name": "pencil2",
"semver": "patch"
},
{
"emoji": "💩",
"entity": "",
"code": ":poop:",
"description": "Write bad code that needs to be improved.",
"name": "poop",
"semver": null
},
{
"emoji": "⏪️",
"entity": "⏪",
"code": ":rewind:",
"description": "Revert changes.",
"name": "rewind",
"semver": "patch"
},
{
"emoji": "🔀",
"entity": "🔀",
"code": ":twisted_rightwards_arrows:",
"description": "Merge branches.",
"name": "twisted-rightwards-arrows",
"semver": null
},
{
"emoji": "📦️",
"entity": "F4E6;",
"code": ":package:",
"description": "Add or update compiled files or packages.",
"name": "package",
"semver": "patch"
},
{
"emoji": "👽️",
"entity": "F47D;",
"code": ":alien:",
"description": "Update code due to external API changes.",
"name": "alien",
"semver": "patch"
},
{
"emoji": "🚚",
"entity": "F69A;",
"code": ":truck:",
"description": "Move or rename resources (e.g.: files, paths, routes).",
"name": "truck",
"semver": null
},
{
"emoji": "📄",
"entity": "F4C4;",
"code": ":page_facing_up:",
"description": "Add or update license.",
"name": "page-facing-up",
"semver": null
},
{
"emoji": "💥",
"entity": "💥",
"code": ":boom:",
"description": "Introduce breaking changes.",
"name": "boom",
"semver": "major"
},
{
"emoji": "🍱",
"entity": "F371",
"code": ":bento:",
"description": "Add or update assets.",
"name": "bento",
"semver": "patch"
},
{
"emoji": "♿️",
"entity": "♿",
"code": ":wheelchair:",
"description": "Improve accessibility.",
"name": "wheelchair",
"semver": "patch"
},
{
"emoji": "💡",
"entity": "💡",
"code": ":bulb:",
"description": "Add or update comments in source code.",
"name": "bulb",
"semver": null
},
{
"emoji": "🍻",
"entity": "🍻",
"code": ":beers:",
"description": "Write code drunkenly.",
"name": "beers",
"semver": null
},
{
"emoji": "💬",
"entity": "💬",
"code": ":speech_balloon:",
"description": "Add or update text and literals.",
"name": "speech-balloon",
"semver": "patch"
},
{
"emoji": "🗃️",
"entity": "🗃",
"code": ":card_file_box:",
"description": "Perform database related changes.",
"name": "card-file-box",
"semver": "patch"
},
{
"emoji": "🔊",
"entity": "🔊",
"code": ":loud_sound:",
"description": "Add or update logs.",
"name": "loud-sound",
"semver": null
},
{
"emoji": "🔇",
"entity": "🔇",
"code": ":mute:",
"description": "Remove logs.",
"name": "mute",
"semver": null
},
{
"emoji": "👥",
"entity": "👥",
"code": ":busts_in_silhouette:",
"description": "Add or update contributor(s).",
"name": "busts-in-silhouette",
"semver": null
},
{
"emoji": "🚸",
"entity": "🚸",
"code": ":children_crossing:",
"description": "Improve user experience / usability.",
"name": "children-crossing",
"semver": "patch"
},
{
"emoji": "🏗️",
"entity": "f3d7;",
"code": ":building_construction:",
"description": "Make architectural changes.",
"name": "building-construction",
"semver": null
},
{
"emoji": "📱",
"entity": "📱",
"code": ":iphone:",
"description": "Work on responsive design.",
"name": "iphone",
"semver": "patch"
},
{
"emoji": "🤡",
"entity": "🤡",
"code": ":clown_face:",
"description": "Mock things.",
"name": "clown-face",
"semver": null
},
{
"emoji": "🥚",
"entity": "🥚",
"code": ":egg:",
"description": "Add or update an easter egg.",
"name": "egg",
"semver": "patch"
},
{
"emoji": "🙈",
"entity": "bdfe7;",
"code": ":see_no_evil:",
"description": "Add or update a .gitignore file.",
"name": "see-no-evil",
"semver": null
},
{
"emoji": "📸",
"entity": "📸",
"code": ":camera_flash:",
"description": "Add or update snapshots.",
"name": "camera-flash",
"semver": null
},
{
"emoji": "⚗️",
"entity": "⚗",
"code": ":alembic:",
"description": "Perform experiments.",
"name": "alembic",
"semver": "patch"
},
{
"emoji": "🔍️",
"entity": "🔍",
"code": ":mag:",
"description": "Improve SEO.",
"name": "mag",
"semver": "patch"
},
{
"emoji": "🏷️",
"entity": "🏷",
"code": ":label:",
"description": "Add or update types.",
"name": "label",
"semver": "patch"
},
{
"emoji": "🌱",
"entity": "🌱",
"code": ":seedling:",
"description": "Add or update seed files.",
"name": "seedling",
"semver": null
},
{
"emoji": "🚩",
"entity": "🚩",
"code": ":triangular_flag_on_post:",
"description": "Add, update, or remove feature flags.",
"name": "triangular-flag-on-post",
"semver": "patch"
},
{
"emoji": "🥅",
"entity": "🥅",
"code": ":goal_net:",
"description": "Catch errors.",
"name": "goal-net",
"semver": "patch"
},
{
"emoji": "💫",
"entity": "💫",
"code": ":dizzy:",
"description": "Add or update animations and transitions.",
"name": "dizzy",
"semver": "patch"
},
{
"emoji": "🗑️",
"entity": "🗑",
"code": ":wastebasket:",
"description": "Deprecate code that needs to be cleaned up.",
"name": "wastebasket",
"semver": "patch"
},
{
"emoji": "🛂",
"entity": "🛂",
"code": ":passport_control:",
"description": "Work on code related to authorization, roles and permissions.",
"name": "passport-control",
"semver": "patch"
},
{
"emoji": "🩹",
"entity": "🩹",
"code": ":adhesive_bandage:",
"description": "Simple fix for a non-critical issue.",
"name": "adhesive-bandage",
"semver": "patch"
},
{
"emoji": "🧐",
"entity": "🧐",
"code": ":monocle_face:",
"description": "Data exploration/inspection.",
"name": "monocle-face",
"semver": null
},
{
"emoji": "⚰️",
"entity": "⚰",
"code": ":coffin:",
"description": "Remove dead code.",
"name": "coffin",
"semver": null
},
{
"emoji": "🧪",
"entity": "🧪",
"code": ":test_tube:",
"description": "Add a failing test.",
"name": "test-tube",
"semver": null
},
{
"emoji": "👔",
"entity": "👔",
"code": ":necktie:",
"description": "Add or update business logic.",
"name": "necktie",
"semver": "patch"
},
{
"emoji": "🩺",
"entity": "🩺",
"code": ":stethoscope:",
"description": "Add or update healthcheck.",
"name": "stethoscope",
"semver": null
},
{
"emoji": "🧱",
"entity": "🧱",
"code": ":bricks:",
"description": "Infrastructure related changes.",
"name": "bricks",
"semver": null
},
{
"emoji": "🧑💻",
"entity": "🧑‍💻",
"code": ":technologist:",
"description": "Improve developer experience.",
"name": "technologist",
"semver": null
},
{
"emoji": "💸",
"entity": "💸",
"code": ":money_with_wings:",
"description": "Add sponsorships or money related infrastructure.",
"name": "money-with-wings",
"semver": null
},
{
"emoji": "🧵",
"entity": "🧵",
"code": ":thread:",
"description": "Add or update code related to multithreading or concurrency.",
"name": "thread",
"semver": null
},
{
"emoji": "🦺",
"entity": "🦺",
"code": ":safety_vest:",
"description": "Add or update code related to validation.",
"name": "safety-vest",
"semver": null
},
{
"emoji": "✈️",
"entity": "✈",
"code": ":airplane:",
"description": "Improve offline support.",
"name": "airplane",
"semver": null
},
{
"emoji": "🦖",
"entity": "✈",
"code": ":t-rex:",
"description": "Code that adds backwards compatibility.",
"name": "t-rex",
"semver": null
}
]
}
================================================
FILE: packages/gitmojis/src/index.d.ts
================================================
declare module 'gitmojis' {
type Gitmoji = {
/**
* Gitmoji unicode character
* @example '🎨', '⚡️', '🔥', '🐛'
*/
readonly emoji: string
/**
* Gitmoji hexadecimal entity.
* @example '🎨', '⚡', '🔥', '🐛'
*/
readonly entity: `&#${string};`
/**
* Gitmoji use-case description.
*/
readonly description: string
/**
* Gitmoji name.
* @example 'art', 'zap', 'fire', 'bug'
*/
readonly name: string
/**
* Gitmoji semver range. Can be `null` if not specified.
*/
readonly semver: 'patch' | 'minor' | 'major' | null
/**
* Gitmoji character formatted as a shortcode.
* @example ':art:', ':zap:', ':fire:', ':bug:'
*/
readonly code: `:${string}:`
}
export const gitmojis: readonly Gitmoji[]
export const schema: readonly any
}
================================================
FILE: packages/gitmojis/src/index.js
================================================
import gitmojisJson from './gitmojis.json' assert { type: 'json' }
export { default as schema } from './schema.json' assert { type: 'json' }
export const gitmojis = gitmojisJson.gitmojis
================================================
FILE: packages/gitmojis/src/schema.json
================================================
{
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": ["gitmojis"],
"properties": {
"gitmojis": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "object",
"required": [
"emoji",
"entity",
"code",
"description",
"name",
"semver"
],
"properties": {
"code": {
"type": "string"
},
"entity": {
"type": "string"
},
"description": {
"type": "string"
},
"emoji": {
"type": "string"
},
"name": {
"type": "string"
},
"semver": {
"enum": ["major", "minor", "patch", null]
}
}
}
}
}
}
================================================
FILE: packages/website/.lintstagedrc.json
================================================
{
"./src/**/*.{ts,tsx,css}": [
"eslint --cache --fix",
"prettier --write ./src/**/*.{ts,tsx,css}"
]
}
================================================
FILE: packages/website/__mocks__/svg.js
================================================
module.exports = 'svg-mock'
================================================
FILE: packages/website/eslint.config.mjs
================================================
import { FlatCompat } from '@eslint/eslintrc'
import js from '@eslint/js'
import typescriptParser from '@typescript-eslint/parser'
import typescriptPlugin from '@typescript-eslint/eslint-plugin'
import reactPlugin from 'eslint-plugin-react'
import importPlugin from 'eslint-plugin-import'
import nextPlugin from '@next/eslint-plugin-next'
import prettierConfig from 'eslint-config-prettier'
import path from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const compat = new FlatCompat({
baseDirectory: __dirname,
})
export default [
js.configs.recommended,
...compat.extends('plugin:@typescript-eslint/recommended'),
prettierConfig,
{
files: ['**/*.{js,mjs,cjs,ts,tsx}'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
globals: {
React: 'readonly',
JSX: 'readonly',
window: 'readonly',
document: 'readonly',
navigator: 'readonly',
console: 'readonly',
process: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
module: 'readonly',
require: 'readonly',
jest: 'readonly',
describe: 'readonly',
it: 'readonly',
test: 'readonly',
expect: 'readonly',
beforeAll: 'readonly',
beforeEach: 'readonly',
afterAll: 'readonly',
afterEach: 'readonly',
},
},
plugins: {
'@typescript-eslint': typescriptPlugin,
react: reactPlugin,
import: importPlugin,
'@next/next': nextPlugin,
},
settings: {
react: {
version: 'detect',
},
'import/resolver': {
typescript: true,
node: true,
alias: {
map: [['src', './src']],
},
},
},
rules: {
'react/react-in-jsx-scope': 'off',
'@next/next/no-img-element': 'off',
'react/no-unknown-property': [
'error',
{
ignore: ['jsx', 'global'],
},
],
'import/order': [
'error',
{
groups: [
['builtin', 'external'],
['internal', 'parent', 'sibling', 'index'],
],
'newlines-between': 'always',
},
],
},
},
]
================================================
FILE: packages/website/jest.config.js
================================================
const nextJest = require('next/jest')
const createJestConfig = nextJest({ dir: './' })
async function jestConfig() {
const nextJestConfig = await createJestConfig({
"collectCoverageFrom": [
"src/**/*.{ts,tsx}",
],
"testMatch": [
"**/*.(spec).(ts)",
"**/*.(spec).(tsx)"
],
"moduleNameMapper": {
"src/(.*)$": "<rootDir>/src/$1",
"\\.svg$": "<rootDir>/__mocks__/svg.js"
},
"testEnvironment": "jsdom",
"setupFilesAfterEnv": ["<rootDir>/jest.setup.js"],
"reporters": [
"default",
"github-actions"
]
})()
// Add ignores for specific ESM packages so they are transformed by Jest
// See: https://github.com/vercel/next.js/issues/35634
nextJestConfig.transformIgnorePatterns[0] = '/node_modules/(!@vercel/analytics)/'
return nextJestConfig
}
module.exports = jestConfig
================================================
FILE: packages/website/jest.d.ts
================================================
/// <reference types="@testing-library/jest-dom" />
================================================
FILE: packages/website/jest.setup.js
================================================
import '@testing-library/jest-dom'
import React from 'react'
// Mock next/dynamic to load components synchronously in tests
jest.mock('next/dynamic', () => ({
__esModule: true,
default: (...args) => {
const dynamicModule = jest.requireActual('next/dynamic')
const dynamicActualComp = dynamicModule.default
const RequiredComponent = dynamicActualComp(args[0])
RequiredComponent.preload ? RequiredComponent.preload() : RequiredComponent.render.preload()
return RequiredComponent
},
}))
// Mock matchMedia for components that use it
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
})
// Silence known React/JSDOM warnings in tests that don't affect functionality
const originalError = console.error
beforeAll(() => {
console.error = (...args) => {
// Suppress SVG element warnings - these are JSDOM limitations, not actual errors
// The SVG elements work fine in real browsers
if (
typeof args[0] === 'string' &&
(args[0].includes('The tag <') && args[0].includes('is unrecognized in this browser'))
) {
return
}
originalError.call(console, ...args)
}
})
afterAll(() => {
console.error = originalError
})
// Mock SVG namespace attributes for JSDOM
// JSDOM doesn't fully support SVG namespaced attributes like xlink:href
const originalCreateElement = document.createElement.bind(document)
const originalCreateElementNS = document.createElementNS.bind(document)
document.createElement = function (tagName, options) {
const element = originalCreateElement(tagName, options)
if (tagName.toLowerCase() === 'svg') {
element.setAttribute = function (name, value) {
if (name === 'xlinkHref') {
this.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', value)
} else {
Element.prototype.setAttribute.call(this, name, value)
}
}
}
return element
}
document.createElementNS = function (namespaceURI, qualifiedName) {
const element = originalCreateElementNS(namespaceURI, qualifiedName)
if (namespaceURI === 'http://www.w3.org/2000/svg') {
element.setAttribute = function (name, value) {
if (name === 'xlinkHref') {
this.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', value)
} else {
Element.prototype.setAttribute.call(this, name, value)
}
}
}
return element
}
================================================
FILE: packages/website/jsconfig.json
================================================
{
"compilerOptions": {
"baseUrl": "."
}
}
================================================
FILE: packages/website/next-env.d.ts
================================================
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
================================================
FILE: packages/website/next-sitemap.config.js
================================================
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: 'https://gitmoji.dev',
generateRobotsTxt: true,
}
================================================
FILE: packages/website/next-sitemap.js
================================================
module.exports = {
siteUrl: 'https://gitmoji.dev',
generateRobotsTxt: true,
}
================================================
FILE: packages/website/next.config.js
================================================
module.exports = {
reactStrictMode: true,
output: 'export',
}
================================================
FILE: packages/website/package.json
================================================
{
"name": "website",
"private": true,
"version": "1.0.0",
"engines": {
"node": "24"
},
"scripts": {
"build": "node scripts/generate-api.js && next build && next-sitemap",
"tscheck": "pnpm exec tsc --noEmit",
"dev": "next dev",
"lint": "eslint ./src && prettier --check ./src/**/*.{ts,tsx,css}",
"start": "next start",
"test": "FORCE_COLOR=1 jest --coverage"
},
"devDependencies": {
"@eslint/eslintrc": "3.3.3",
"@eslint/js": "9.39.2",
"@next/eslint-plugin-next": "16.1.6",
"@testing-library/jest-dom": "6.9.1",
"@testing-library/react": "16.3.2",
"@testing-library/user-event": "14.6.1",
"@types/fetch-mock": "^7.3.8",
"@types/jest": "^29.5.14",
"@types/react": "^19.2.14",
"@typescript-eslint/eslint-plugin": "^8.52.0",
"@typescript-eslint/parser": "^8.53.0",
"clipboard": "^2.0.11",
"eslint": "^9.39.2",
"eslint-config-next": "^16.1.6",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.12.1",
"eslint-plugin-react": "^7.37.5",
"focus-trap-react": "^12.0.0",
"gitmojis": "workspace:*",
"jest": "^29.7.0",
"jest-environment-jsdom": "^30.2.0",
"jest-fetch-mock": "^3.0.3",
"lint-staged": "^16.2.7",
"next": "^16.1.6",
"next-sitemap": "^4.2.3",
"next-themes": "^0.4.6",
"node-mocks-http": "^1.17.2",
"prettier": "3.8.1",
"prop-types": "^15.8.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-hot-toast": "^2.5.2",
"typescript": "^5.9.3"
},
"author": {
"name": "carloscuesta",
"email": "hi@carloscuesta.me",
"url": "https://carloscuesta.me"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/carloscuesta/gitmoji/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/carloscuesta/gitmoji.git"
},
"homepage": "https://gitmoji.dev",
"keywords": [
"gitmoji",
"emoji",
"carloscuesta",
"commit"
],
"prettier": {
"semi": false,
"singleQuote": true,
"arrowParens": "always"
}
}
================================================
FILE: packages/website/public/_redirects
================================================
/api/gitmojis /api/gitmojis/index.json 200
================================================
FILE: packages/website/public/static/browserconfig.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/static/ms-icon-70x70.png"/><square150x150logo src="/static/ms-icon-150x150.png"/><square310x310logo src="/static/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
================================================
FILE: packages/website/public/static/manifest.json
================================================
{
"name": "Gitmoji",
"display": "minimal-ui",
"start_url": "/",
"theme_color": "#FFDD67",
"background_color": "#FFF",
"icons": [
{
"src": "/static/android-icon-36x36.png",
"sizes": "36x36",
"type": "image/png",
"density": "0.75"
},
{
"src": "/static/android-icon-48x48.png",
"sizes": "48x48",
"type": "image/png",
"density": "1.0"
},
{
"src": "/static/android-icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"density": "1.5"
},
{
"src": "/static/android-icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"density": "2.0"
},
{
"src": "/static/android-icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"density": "3.0"
},
{
"src": "/static/android-icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"density": "4.0"
},
{
"src": "/static/android-icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"density": "4.0"
},
{
"src": "/static/maskable_icon.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
}
]
}
================================================
FILE: packages/website/public/static/opensearchdescription.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Gitmoji</ShortName>
<Description>An emoji guide for your commit messages.</Description>
<Tags>gitmoji emoji git</Tags>
<Image height="16" width="16" type="image/png">https://gitmoji.dev/static/favicon-16x16.png</Image>
<Image height="32" width="32" type="image/png">https://gitmoji.dev/static/favicon-32x32.png</Image>
<Image height="96" width="96" type="image/png">https://gitmoji.dev/static/favicon-96x96.png</Image>
<Url type="text/html" template="https://gitmoji.dev/?search={searchTerms}"/>
</OpenSearchDescription>
================================================
FILE: packages/website/scripts/generate-api.js
================================================
const { gitmojis } = require('gitmojis')
const fs = require('fs')
const path = require('path')
const outputDir = path.join(__dirname, '../public/api/gitmojis')
fs.mkdirSync(outputDir, { recursive: true })
fs.writeFileSync(
path.join(outputDir, 'index.json'),
JSON.stringify({ gitmojis }, null, 2)
)
console.log('Generated static API at public/api/gitmojis/index.json')
================================================
FILE: packages/website/src/__tests__/pages.spec.tsx
================================================
import { render, screen } from '@testing-library/react'
import RootLayout from '../app/layout'
import Home from '../app/page'
import About from '../app/about/page'
import Specification from '../app/specification/page'
import RelatedTools from '../app/related-tools/page'
jest.mock('next/navigation', () => ({
useRouter: jest.fn(() => ({
push: jest.fn(),
replace: jest.fn(),
prefetch: jest.fn(),
})),
useSearchParams: jest.fn(() => new URLSearchParams()),
usePathname: jest.fn(() => '/'),
}))
jest.mock('next-themes', () => ({
ThemeProvider: ({ children }: { children: React.ReactNode }) => (
<div>{children}</div>
),
useTheme: jest.fn(() => ({
resolvedTheme: 'light',
setTheme: jest.fn(),
})),
}))
describe('Pages', () => {
beforeAll(() => {
Math.random = jest.fn().mockReturnValue(1)
})
describe('RootLayout', () => {
it('should include theme provider and layout wrapper', () => {
expect(RootLayout).toBeDefined()
expect(typeof RootLayout).toBe('function')
})
})
describe('Home', () => {
it('should render the page', () => {
const { container } = render(<Home />)
expect(container.firstChild).toBeInTheDocument()
})
})
describe('About', () => {
it('should render the page with about content', () => {
render(<About />)
expect(screen.getByText(/About/i)).toBeInTheDocument()
})
})
describe('Specification', () => {
it('should render the page with specification content', () => {
render(<Specification />)
const heading = screen.getByRole('heading', {
name: /Specification/i,
level: 1,
})
expect(heading).toBeInTheDocument()
})
})
describe('Related tools', () => {
it('should render the page', () => {
render(<RelatedTools />)
expect(screen.getByText(/Related Tools/i)).toBeInTheDocument()
})
})
})
================================================
FILE: packages/website/src/__tests__/stubs.tsx
================================================
export const appProps = {
Component: (props: object) => <div {...props}>Component</div>,
pageProps: { test: '' },
}
export const contributors = [
{
url: 'https://github.com/profile',
avatar: 'https://github.com/avatar',
id: 'contributor-id-123',
},
]
export const contributorsMock = [
{
html_url: 'https://github.com/profile',
avatar_url: 'https://github.com/avatar',
id: 'contributor-id-123',
login: 'carloscuesta',
},
]
================================================
FILE: packages/website/src/app/about/page.tsx
================================================
import type { Metadata } from 'next'
import Link from 'next/link'
import CarbonAd from 'src/components/CarbonAd'
export const metadata: Metadata = {
title: 'gitmoji | About | An emoji guide for your commit messages',
alternates: {
canonical: '/about',
},
}
export default function About() {
return (
<main>
<CarbonAd />
<section>
<h1>About</h1>
<p>
<strong>Gitmoji is an emoji guide for GitHub commit messages</strong>.
Aims to be a standarization cheatsheet - guide for using emojis on
GitHub's commit messages.
</p>
<p>
<strong>Using emojis</strong> on <strong>commit messages</strong>{' '}
provides an <strong>easy way</strong> of{' '}
<strong>identifying the purpose or intention of a commit</strong> with
only looking at the emojis used. As there are a lot of different
emojis I found the need of creating a guide that can help to use
emojis easier.
</p>
<p>
This project is Open Source, that means everyone can participate,
suggesting, discussing and adding new emojis. Take a look at the{' '}
<Link href="#contributing-gitmoji">contributing section</Link> and{' '}
<a href="https://github.com/carloscuesta/gitmoji/blob/master/.github/CONTRIBUTING.md">
guidelines for contributing
</a>
.
</p>
</section>
<section>
<h1>
Using gitmoji with{' '}
<a href="https://github.com/carloscuesta/gitmoji-cli">gitmoji-cli</a>
</h1>
<p>
An easy solution for using gitmoji from your command line, is to
install{' '}
<a href="https://github.com/carloscuesta/gitmoji-cli">gitmoji-cli</a>.
A gitmoji interactive client for using emojis on commit messages.
</p>
<pre className="overflow-x-adjust">
<code>$ npm i -g gitmoji-cli</code>
</pre>
</section>
<section>
<h1 id="specification">Specification</h1>
<p>
To understand how to use gitmoji properly, please check the official
specification <Link href="/specification">here</Link> 👈.
</p>
</section>
<section>
<h1 id="contributing-gitmoji">Contributing to gitmoji</h1>
<p>
Contributing to gitmoji is a piece of 🍰! This project is a static
website built with <i>Next.js</i>. All the gitmojis displayed are
rendered from a JSON file. Before submitting any pull request, please
follow these steps:
</p>
<ol>
<li>
<a href="https://github.com/carloscuesta/gitmoji/issues/new">
Create an issue
</a>{' '}
filling the template.
</li>
<li>
After discussing the idea, feature or suggestion,{' '}
<a href="https://github.com/carloscuesta/gitmoji/blob/master/.github/CONTRIBUTING.md">
read the contribution docs.
</a>
</li>
<li>
<a href="https://github.com/carloscuesta/gitmoji/fork">
Create a fork{' '}
</a>
of gitmoji.
</li>
<li>
Create a new branch with the feature name. (Eg: add-emoji-deploy,
fix-website-header)
</li>
<li>
Make your changes and send a{' '}
<a href="https://help.github.com/articles/creating-a-pull-request/">
pull request{' '}
</a>
.
</li>
</ol>
</section>
</main>
)
}
================================================
FILE: packages/website/src/app/contributors/page.tsx
================================================
import type { Metadata } from 'next'
import ContributorsList from 'src/components/ContributorsList'
import CarbonAd from 'src/components/CarbonAd'
export const metadata: Metadata = {
title: 'gitmoji | Contributors | An emoji guide for your commit messages',
alternates: {
canonical: '/contributors',
},
}
type Contributor = {
avatar: string
id: string
url: string
}
type GitHubContributor = {
avatar_url: string
id: string
html_url: string
login: string
}
async function getContributors(): Promise<Contributor[]> {
const response = await fetch(
'https://api.github.com/repos/carloscuesta/gitmoji/contributors',
{ next: { revalidate: 3600 * 3 } },
)
const contributors: GitHubContributor[] = await response.json()
return contributors
.filter((contributor) => !contributor.login.includes('bot'))
.map((contributor) => ({
avatar: contributor.avatar_url,
id: contributor.id,
url: contributor.html_url,
}))
}
export default async function Contributors() {
const contributors = await getContributors()
return (
<main>
<CarbonAd />
<section>
<h1>Contributors</h1>
<ContributorsList contributors={contributors} />
</section>
</main>
)
}
================================================
FILE: packages/website/src/app/layout.tsx
================================================
import type { Metadata } from 'next'
import { ThemeProvider } from 'next-themes'
import Layout from 'src/components/Layout'
import 'src/utils/theme/theme.css'
export const metadata: Metadata = {
title: 'gitmoji | An emoji guide for your commit messages',
description:
"Gitmoji is an emoji guide for your commit messages. Aims to be a standarization cheatsheet for using emojis on GitHub's commit messages.",
authors: [{ name: 'Carlos Cuesta', url: 'https://carloscuesta.me' }],
keywords: ['gitmoji', 'emoji', 'carloscuesta', 'commit'],
metadataBase: new URL('https://gitmoji.dev'),
alternates: {
canonical: '/',
},
robots: 'index, follow',
openGraph: {
title: 'gitmoji',
description: 'An emoji guide for your commit messages.',
url: 'https://gitmoji.dev',
images: [
{
url: 'https://gitmoji.dev/static/gitmoji.gif',
},
],
},
twitter: {
card: 'summary',
title: 'gitmoji',
description: 'An emoji guide for your commit messages.',
creator: '@crloscuesta',
images: ['https://gitmoji.dev/static/gitmoji.gif'],
},
icons: {
icon: [
{ url: '/static/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
{ url: '/static/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
{ url: '/static/favicon-96x96.png', sizes: '96x96', type: 'image/png' },
{
url: '/static/android-icon-192x192.png',
sizes: '192x192',
type: 'image/png',
},
],
apple: [
{ url: '/static/apple-icon-57x57.png', sizes: '57x57' },
{ url: '/static/apple-icon-60x60.png', sizes: '60x60' },
{ url: '/static/apple-icon-72x72.png', sizes: '72x72' },
{ url: '/static/apple-icon-76x76.png', sizes: '76x76' },
{ url: '/static/apple-icon-114x114.png', sizes: '114x114' },
{ url: '/static/apple-icon-120x120.png', sizes: '120x120' },
{ url: '/static/apple-icon-144x144.png', sizes: '144x144' },
{ url: '/static/apple-icon-152x152.png', sizes: '152x152' },
{ url: '/static/apple-icon-180x180.png', sizes: '180x180' },
],
},
manifest: '/static/manifest.json',
other: {
'msapplication-TileColor': '#FFDD67',
'msapplication-TileImage': '/ms-icon-144x144.png',
'google-site-verification': '78vmlhi_erc-UGybxcGwHyiUtf04wzYExTLa-4LoWio',
},
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<meta name="theme-color" content="#FFDD67" />
<link
rel="search"
type="application/opensearchdescription+xml"
href="/static/opensearchdescription.xml"
/>
<script
type="text/javascript"
dangerouslySetInnerHTML={{
__html: `(function(a,e,f,g,b,c,d){a.GoogleAnalyticsObject=b;a[b]=a[b]||function(){(a[b].q=a[b].q||[]).push(arguments)};a[b].l=1*new Date;c=e.createElement(f);d=e.getElementsByTagName(f)[0];c.async=1;c.src=g;d.parentNode.insertBefore(c,d)})(window,document,"script","//www.google-analytics.com/analytics.js","ga");ga("create","UA-67824860-7","auto");ga("send","pageview");`,
}}
/>
</head>
<body>
<ThemeProvider attribute="data-theme" defaultTheme="light">
<Layout>{children}</Layout>
</ThemeProvider>
</body>
</html>
)
}
================================================
FILE: packages/website/src/app/page.tsx
================================================
import { Toaster } from 'react-hot-toast'
import { gitmojis } from 'gitmojis'
import GitmojiList from 'src/components/GitmojiList'
import CarbonAd from 'src/components/CarbonAd'
export default function Home() {
return (
<main>
<CarbonAd />
<GitmojiList gitmojis={gitmojis} />
<Toaster position="top-left" />
</main>
)
}
================================================
FILE: packages/website/src/app/related-tools/page.tsx
================================================
import type { Metadata } from 'next'
import CarbonAd from 'src/components/CarbonAd'
export const metadata: Metadata = {
title: 'gitmoji | Related tools | An emoji guide for your commit messages',
alternates: {
canonical: '/related-tools',
},
}
const tools = [
{
name: 'gitmoji-changelog',
description: 'A changelog generator for gitmoji.',
link: 'https://github.com/frinyvonnick/gitmoji-changelog/',
},
{
name: 'gitmemoji',
description: 'A game to learn gitmojis.',
link: 'https://github.com/ImBIOS/gitmemoji/',
},
{
name: 'gitmoji-browser-extension',
description: 'The Gitmoji extension to easily search and copy gitmojis.',
link: 'https://github.com/johannchopin/gitmoji-browser-extension',
},
{
name: 'gitmoji-vscode',
description: 'Gitmoji tool for git commit messages in VS Code',
link: 'https://github.com/seatonjiang/gitmoji-vscode',
},
{
name: 'gitmoji-intellij-plugin',
description:
'A Jetbrains suite plugin to easily add gitmoji when committing',
link: 'https://plugins.jetbrains.com/plugin/12383-gitmoji-plus-commit-button',
},
{
name: 'gitmoji-sublimetext',
description: 'A Sublime Text plugin to add emojis in git commit messages.',
link: 'https://packagecontrol.io/packages/Gitmoji',
},
{
name: 'gitimoji',
description: 'A Gitmoji App for macOS.',
link: 'https://github.com/TimoZacherl/gitimoji',
},
{
name: 'gitmoji-atom',
description: 'Gitmoji for Atom',
link: 'https://github.com/ThatXliner/gitmoji-atom',
},
{
name: 'gitmoji-regex',
description: 'A Gitmoji::Regex for Ruby.',
link: 'https://github.com/pboling/gitmoji-regex',
},
{
name: 'traymoji',
description: 'A Electron Tray App for Gitmojis',
link: 'https://github.com/CoenWarmer/traymoji',
},
{
name: 'alfred-gitmoji',
description: 'Gitmoji Workflow for Alfred',
link: 'https://github.com/techouse/alfred-gitmoji',
},
{
name: 'gitmojiapp',
description: 'A Flutter Gitmoji App for macOS, Linux, and Windows',
link: 'https://github.com/patrick-fu/GitmojiApp',
},
{
name: 'githubmoji',
description:
'A Firefox addon that adds a predictive gitmoji picker to GitHub commit message inputs.',
link: 'https://github.com/ma1ted/githubmoji',
},
{
name: 'gitmoji-changelog-action',
description: 'GitHub Action for gitmoji-changelog',
link: 'https://github.com/sercanuste/gitmoji-changelog-action',
},
{
name: 'raycast-gitmoji-search',
description: 'Gitmoji extension for Raycast',
link: 'https://www.raycast.com/ricoberger/gitmoji',
},
{
name: 'gitmoji-fuzzy-hook',
description:
'Fuzzy finder git-commit-hook for prepending a gitmoji (cmd & GUI).',
link: 'https://gitlab.com/raabf/gitmoji-fuzzy-hook',
},
{
name: 'genmoji',
description: 'Gitmoji commit message generation using AI',
link: 'https://github.com/segersniels/genmoji',
},
{
name: 'gitmoji-plus-flow-launcher',
description: 'A flow launcher plugin to search and copy gitmojis',
link: 'https://github.com/tho-myr/Flow.Launcher.Plugin.Gitmoji_Plus',
},
]
export default function RelatedTools() {
return (
<main>
<CarbonAd />
<section>
<h1>Related tools</h1>
<p>
This is a list of tools which are related with the <b>gitmoji</b>{' '}
convention.
</p>
<ul>
{tools.map((tool) => (
<li key={tool.name}>
<a href={tool.link} target="_blank" rel="noopener noreferrer">
<b>{tool.name}</b>
</a>
{`: ${tool.description}`}
</li>
))}
</ul>
</section>
</main>
)
}
================================================
FILE: packages/website/src/app/specification/page.tsx
================================================
import type { Metadata } from 'next'
import Link from 'next/link'
import CarbonAd from 'src/components/CarbonAd'
export const metadata: Metadata = {
title: 'gitmoji | Specification | An emoji guide for your commit messages',
alternates: {
canonical: '/specification',
},
}
export default function Specification() {
return (
<main>
<CarbonAd />
<section>
<h1 id="specification">Specification</h1>
<p>
You can extend Gitmoji and make it your own, but in case you want to
follow the official specification, please continue reading 👀
</p>
<p>
A gitmoji commit message consists is composed using the following
pieces:
</p>
<ul>
<li>
<b>intention</b>: The intention you want to express with the commit,
using an emoji from the <Link href="/">list</Link>. Either in the
:shortcode: or unicode format.
</li>
<li>
<b>scope</b>: An optional string that adds contextual information
for the scope of the change.
</li>
<li>
<b>message</b>: A brief explanation of the change.
</li>
</ul>
<pre className="overflow-x-adjust">
<code><intention> [scope?][:?] <message></code>
</pre>
</section>
<section>
<h1 id="specification-examples">Examples</h1>
<ul>
<li>⚡️ Lazyload home screen images.</li>
<li>🐛 Fix `onClick` event handler</li>
<li>🔖 Bump version `1.2.0`</li>
<li>♻️ (components): Transform classes to hooks</li>
<li>📈 Add analytics to the dashboard</li>
<li>🌐 Support Japanese language</li>
<li>♿️ (account): Improve modals a11y</li>
</ul>
</section>
<section>
<h1 id="shortcode-vs-unicode-format">Shortcode vs Unicode format</h1>
<p>
You'll notice that when using emojis in commits, it's possible
to use either the shortcode or the unicode format.
</p>
<p>
The difference between both is that the unicode represents the emoji
itself while the shortcode is a text representation of the emoji that
will be converted to the unicode character when rendered on a Git
platform, such as GitHub, GitLab etc.
</p>
<p>
Both approaches are completely fine, you can choose the one you're
most comfortable and suits you best. Let's understand the pros and
cons of each approach so you can decide on it:
</p>
<h2>Unicode</h2>
<h3>Pros ✅</h3>
<ul>
<li>
It represents the actual emoji no external systems are needed.
</li>
<li>Better git log.</li>
<li>Easier to type.</li>
<li>Takes less characters of the commit title.</li>
</ul>
<h3>Cons ❌</h3>
<ul>
<li>Might not be supported in all terminals / operating systems.</li>
</ul>
<h2>Shortcode</h2>
<h3>Pros ✅</h3>
<ul>
<li>
Supported everywhere as it's a text representation of the emoji.
</li>
</ul>
<h3>Cons ❌</h3>
<ul>
<li>
You'll need a platform / system that knows how to properly
render the shortcode.
</li>
<li>
Different platforms / systems might use different shortcode namings,
eg: GitHub and GitLab have some differences.
</li>
<li>Takes more characters of the commit title.</li>
</ul>
</section>
</main>
)
}
================================================
FILE: packages/website/src/components/Button/__tests__/button.spec.tsx
================================================
import { render, screen } from '@testing-library/react'
import Button from '../index'
import * as stubs from './stubs'
describe('Button', () => {
it('should render the component with correct attributes', () => {
render(<Button {...stubs.props} />)
const link = screen.getByRole('link', { name: /GitHub/i })
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute('href', '/')
expect(link).toHaveAttribute('target', '_blank')
})
it('should render without icon when icon prop is not provided', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { icon, ...propsWithoutIcon } = stubs.props
const { container } = render(<Button {...propsWithoutIcon} />)
const link = screen.getByRole('link', { name: /GitHub/i })
expect(link).toBeInTheDocument()
expect(container.querySelector('svg')).not.toBeInTheDocument()
})
})
================================================
FILE: packages/website/src/components/Button/__tests__/stubs.ts
================================================
export const props = {
target: '_blank',
icon: 'star',
text: 'GitHub',
link: '/',
}
================================================
FILE: packages/website/src/components/Button/index.tsx
================================================
import Icon from 'src/components/Icon'
import styles from './styles.module.css'
type Props = { target?: string; icon?: string; text: string; link: string }
const Button = (props: Props) => (
<a
className={styles.button}
target={props.target && props.target}
href={props.link}
>
{props.icon && <Icon name={props.icon} />}
{props.text}
</a>
)
export default Button
================================================
FILE: packages/website/src/components/Button/styles.module.css
================================================
.button {
border-radius: 4px;
cursor: pointer;
display: inline-block;
font-weight: 600;
margin: 0.25em 0;
padding: 0.75em 1em;
position: relative;
transition: none;
background-color: var(--secondary);
color: var(--textInSecondary);
box-shadow: 0 4px var(--secondaryShadow);
}
.button:hover {
top: 2px;
box-shadow: 0 2px var(--secondaryShadow);
animation: none;
}
.button:active {
box-shadow: 0 0 var(--secondary);
top: 4px;
}
================================================
FILE: packages/website/src/components/CarbonAd/__tests__/carbonAd.spec.tsx
================================================
import { render, waitFor } from '@testing-library/react'
import CarbonAd from '../index'
import styles from '../styles.module.css'
describe('CarbonAd', () => {
it('should render the component with carbon ads script container', async () => {
const { container } = render(<CarbonAd />)
await waitFor(() => {
const carbonAdContainer = container.querySelector(
`.${styles.carbonContainer}`,
)
expect(carbonAdContainer).toBeInTheDocument()
})
})
})
================================================
FILE: packages/website/src/components/CarbonAd/index.tsx
================================================
'use client'
import { useRef, useEffect } from 'react'
import styles from './styles.module.css'
const CarbonAd = () => {
const adsContainer = useRef<HTMLDivElement>(null)
useEffect(() => {
if (!adsContainer.current || typeof window === 'undefined') {
return
}
const existingScript = document.getElementById('_carbonads_js')
if (existingScript) {
const existingAd = document.getElementById('carbonads')
if (existingAd && adsContainer.current) {
adsContainer.current.appendChild(existingAd)
}
return
}
const carbonAdsScript = document.createElement('script')
carbonAdsScript.src =
'//cdn.carbonads.com/carbon.js' + '?serve=CE7DL5QJ&placement=gitmojidev'
carbonAdsScript.async = true
carbonAdsScript.id = '_carbonads_js'
adsContainer.current.appendChild(carbonAdsScript)
return () => {}
}, [])
return (
<div className="col-xs-12">
<div
ref={adsContainer}
className={`${styles.carbonContainer} row center-xs`}
/>
</div>
)
}
export default CarbonAd
================================================
FILE: packages/website/src/components/CarbonAd/styles.module.css
================================================
.carbonContainer {
height: 100px;
}
:global(#carbonads) {
display: block;
overflow: hidden;
max-width: 728px;
border-radius: 4px;
position: relative;
box-shadow: 0 1px 2px 0 var(--cardShadow);
}
:global(#carbonads > span) {
display: block;
}
:global(#carbonads a) {
color: inherit;
text-decoration: none;
}
:global(#carbonads a:hover) {
color: inherit;
animation: none;
}
:global(.carbon-wrap) {
display: flex;
align-items: center;
}
:global(.carbon-img) {
display: block;
margin: 0;
line-height: 1;
}
:global(.carbon-img img) {
display: block;
height: 90px;
width: auto;
}
:global(.carbon-text) {
display: block;
padding: 0 1em;
line-height: 1.35;
text-align: center;
width: 100%;
font-size: 14px;
}
:global(.carbon-poweredby) {
display: block;
position: absolute;
bottom: 0;
right: 0;
padding: 6px 10px;
background: var(--carbonAdBadgeBackground);
text-align: center;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
font-size: 8px;
border-top-left-radius: 4px;
line-height: 1;
}
================================================
FILE: packages/website/src/components/ContributorsList/Contributor/index.tsx
================================================
import styles from './styles.module.css'
type Props = { avatar: string; url: string }
const Contributor = (props: Props) => (
<article className="col-xs-3 col-sm-2">
<a href={props.url} target="_blank" rel="noreferrer">
<img className={styles.picture} src={props.avatar} />
</a>
</article>
)
export default Contributor
================================================
FILE: packages/website/src/components/ContributorsList/Contributor/styles.module.css
================================================
.picture {
max-width: 100%;
border-radius: 50%;
padding: 0.5em;
}
================================================
FILE: packages/website/src/components/ContributorsList/__tests__/contributorsList.spec.tsx
================================================
import { render } from '@testing-library/react'
import ContributorsList from '../index'
import Contributor from '../Contributor'
import * as stubs from './stubs'
describe('ContributorsList', () => {
describe('Contributor', () => {
it('should render the contributor with link and avatar', () => {
const { container } = render(<Contributor {...stubs.contributor} />)
const link = container.querySelector('a')
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute('href', stubs.contributor.url)
expect(link).toHaveAttribute('target', '_blank')
expect(link).toHaveAttribute('rel', 'noreferrer')
const img = container.querySelector('img')
expect(img).toBeInTheDocument()
expect(img).toHaveAttribute('src', stubs.contributor.avatar)
})
})
it('should render the list of contributors', () => {
const { container } = render(<ContributorsList {...stubs.props} />)
const links = container.querySelectorAll('a')
expect(links).toHaveLength(stubs.props.contributors.length)
expect(links[0]).toHaveAttribute('href', stubs.contributor.url)
})
})
================================================
FILE: packages/website/src/components/ContributorsList/__tests__/stubs.ts
================================================
export const contributor = {
url: 'https://github.com/profile',
avatar: 'https://github.com/avatar',
id: 'contributor-id-123',
}
export const props = {
contributors: [contributor],
}
================================================
FILE: packages/website/src/components/ContributorsList/index.tsx
================================================
import Contributor from './Contributor'
type Props = {
contributors: Array<{
avatar: string
id: string
url: string
}>
}
const ContributorsList = (props: Props) => (
<div className="row center-xs">
{props.contributors.map((contributor) => (
<Contributor
key={contributor.id}
url={contributor.url}
avatar={contributor.avatar}
/>
))}
</div>
)
export default ContributorsList
================================================
FILE: packages/website/src/components/GitmojiList/Gitmoji/index.tsx
================================================
import emojiColorsMap from '../emojiColorsMap'
import styles from './styles.module.css'
type Props = {
code: string
description: string
emoji: string
isListMode: boolean
name: keyof typeof emojiColorsMap
}
const Gitmoji = (props: Props) => {
const style = {
'--emojiColor': emojiColorsMap[props.name],
} as React.CSSProperties
return (
<article
style={style}
className={`${styles.emoji} col-xs-12 col-sm-6 ${
props.isListMode ? 'col-md-4' : 'col-md-3'
}`}
>
<div
className={`${styles.card} ${props.isListMode ? styles.cardList : ''}`}
>
<header className={`${styles.cardHeader}`}>
<button
type="button"
className={`gitmoji-clipboard-emoji ${styles.gitmoji}`}
data-clipboard-text={props.emoji}
>
{props.emoji}
</button>
</header>
<div className={styles.gitmojiInfo}>
<button
className={`gitmoji-clipboard-code ${styles.gitmojiCode}`}
data-clipboard-text={props.code}
tabIndex={-1}
type="button"
>
<code>
{replaceWithJSX(
props.code,
'_',
<>
_<wbr />
</>,
)}
</code>
</button>
<p>{props.description}</p>
</div>
</div>
</article>
)
}
const replaceWithJSX = (
text: string,
find: string,
replace: React.JSX.Element,
) => {
const nodes: (string | React.JSX.Element)[] = text.split(find)
const first = nodes.shift()
return nodes
.reduce((newNodes, part) => [...newNodes, replace, part], [first])
.map((el, index) => <span key={index}>{el}</span>)
}
export default Gitmoji
================================================
FILE: packages/website/src/components/GitmojiList/Gitmoji/styles.module.css
================================================
.emoji {
display: flex;
box-sizing: border-box;
}
.card {
background-color: var(--cardBackground);
border-radius: 4px;
box-shadow: 0 1px 2px 0 var(--cardShadow);
flex: 1;
margin: 1em 0;
transition: all 0.25s ease-out;
text-align: center;
overflow: hidden;
}
.cardList {
display: flex;
}
.cardList .gitmoji {
font-size: 3.5em;
}
.cardList .cardHeader {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 0 1rem;
}
.cardList .gitmojiInfo {
text-align: left;
padding: 1rem;
}
.cardList .gitmojiCode {
text-align: left;
}
.cardList .gitmojiInfo p {
padding: 0;
margin: 0;
padding-top: 0.5rem;
}
.card:hover {
box-shadow: 0 10px 20px 0 var(--cardShadow);
transform: translateY(-1px);
}
[data-theme='dark'] .card:hover {
box-shadow: none;
background-color: #3b3b3b;
}
.cardHeader {
background-color: var(--emojiColor);
align-self: flex-start;
padding-top: 2em;
padding-bottom: 0.85em;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.gitmoji,
.gitmojiCode {
background-color: transparent;
border: none;
color: inherit;
font: inherit;
padding: 0;
}
.gitmoji {
border-radius: none;
cursor: pointer;
display: inline-block;
font-size: 5em;
font-family:
'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji',
'Segoe UI Symbol', 'Android Emoji', 'EmojiSymbols';
}
.gitmoji:hover,
.gitmoji:focus {
animation-name: bounce;
animation-duration: 0.5s;
}
.gitmojiCode {
display: inline-block;
position: relative;
border-radius: 4px;
transition-duration: 0.3s;
cursor: pointer;
white-space: nowrap;
}
.gitmojiCode::after {
content: '';
position: absolute;
left: 0;
bottom: 0;
width: 0;
height: 0.2em;
border-radius: 4px;
transition: width 0.15s;
background-color: var(--emojiColor);
}
.gitmojiCode:hover::after {
width: 100%;
}
.gitmojiInfo {
padding: 1.5em;
word-break: break-all;
color: var(--emojiCodeText);
}
.gitmojiInfo p {
color: var(--cardText);
word-break: normal;
}
@media (max-width: 768px) {
.gitmoji,
.cardList .gitmoji {
font-size: 50px;
}
.cardHeader {
padding-bottom: 1em;
}
}
/*
This code has been obtained from:
https://github.com/daneden/animate.css/blob/master/source/attention_seekers/bounce.css
*/
@keyframes bounce {
from,
20%,
53%,
80%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
transform: translate3d(0, 0, 0);
}
40%,
43% {
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
transform: translate3d(0, -9px, 0);
}
70% {
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
transform: translate3d(0, -5px, 0);
}
90% {
transform: translate3d(0, -2px, 0);
}
}
================================================
FILE: packages/website/src/components/GitmojiList/SearchParamsSync.tsx
================================================
'use client'
import { useEffect } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
type Props = {
searchInput: string
setSearchInput: (value: string) => void
}
/**
* Small client component that syncs URL search params with the search input state.
* Wrapped in Suspense to avoid CSR bailout while keeping the main list static.
*/
export default function SearchParamsSync({
searchInput,
setSearchInput,
}: Props) {
const router = useRouter()
const searchParams = useSearchParams()
useEffect(() => {
const search = searchParams.get('search')
if (search) {
setSearchInput(search)
}
}, [searchParams, setSearchInput])
useEffect(() => {
const search = searchParams.get('search')
if (search && !searchInput) {
router.push('/')
}
}, [searchInput, searchParams, router])
return null
}
================================================
FILE: packages/website/src/components/GitmojiList/Toolbar/Kbd/index.tsx
================================================
import styles from './styles.module.css'
const isMacOs = () => {
return (
typeof window !== 'undefined' &&
window.navigator.platform.toUpperCase().includes('MAC')
)
}
const Kbd = () => {
return <kbd className={styles.kbd}>{isMacOs() ? '⌘' : 'Ctrl'} K</kbd>
}
export default Kbd
================================================
FILE: packages/website/src/components/GitmojiList/Toolbar/Kbd/styles.module.css
================================================
.kbd {
right: 0;
align-items: center;
border-radius: 3px;
border: solid 1px #999;
color: #595959;
display: flex;
font-family: system-ui;
margin-right: 0.5rem;
padding: 0.25rem 0.5rem;
}
[data-theme='dark'] .kbd {
color: #b8b8b8;
}
@media (max-width: 568px) {
.kbd {
display: none;
}
}
================================================
FILE: packages/website/src/components/GitmojiList/Toolbar/ListModeSelector/index.tsx
================================================
import Icon from 'src/components/Icon'
import styles from './styles.module.css'
type Props = {
isListMode: boolean
setIsListMode: (isListMode: boolean) => void
}
const ListModeSelector = (props: Props) => (
<div className={styles.container}>
<button
className={`${styles.button} ${
!props.isListMode ? styles.buttonActive : ''
}`}
disabled={!props.isListMode}
onClick={() => props.setIsListMode(false)}
>
<Icon name="grid" />
</button>
<button
className={`${styles.button} ${
props.isListMode ? styles.buttonActive : ''
}`}
disabled={props.isListMode}
onClick={() => props.setIsListMode(true)}
>
<Icon name="list" />
</button>
</div>
)
export default ListModeSelector
================================================
FILE: packages/website/src/components/GitmojiList/Toolbar/ListModeSelector/styles.module.css
================================================
.container {
display: flex;
}
.button {
align-items: center;
border-radius: 4px;
border: none;
color: var(--text);
cursor: pointer;
display: flex;
background-color: var(--cardBackground);
box-shadow: 0 2px 4px 0 var(--cardShadow);
font-size: 16px;
height: 48px;
justify-content: center;
margin: 8px;
width: 48px;
}
.button svg {
margin: 0;
}
.buttonActive {
color: var(--secondary);
}
================================================
FILE: packages/website/src/components/GitmojiList/Toolbar/ThemeSelector/index.tsx
================================================
'use client'
import { useEffect, useState } from 'react'
import { useTheme } from 'next-themes'
import Icon from 'src/components/Icon'
import styles from './styles.module.css'
const ThemeSelector = () => {
const [isMounted, setIsMounted] = useState(false)
const { resolvedTheme, setTheme } = useTheme()
const nextTheme = resolvedTheme === 'light' ? 'dark' : 'light'
useEffect(() => setIsMounted(true), [])
if (!isMounted) {
return (
<div className={styles.container}>
<button disabled className={`${styles.button}`} />
</div>
)
}
return (
<button className={`${styles.button}`} onClick={() => setTheme(nextTheme)}>
<Icon name={nextTheme} />
</button>
)
}
export default ThemeSelector
================================================
FILE: packages/website/src/components/GitmojiList/Toolbar/ThemeSelector/styles.module.css
================================================
.button {
display: flex;
align-items: center;
border-radius: 4px;
border: none;
color: var(--text);
cursor: pointer;
background-color: var(--cardBackground);
box-shadow: 0 2px 4px 0 var(--cardShadow);
font-size: 16px;
height: 48px;
justify-content: center;
margin: 8px;
width: 48px;
}
.button svg {
margin: 0;
}
@media (max-width: 568px) {
.button {
margin-left: 0;
}
}
================================================
FILE: packages/website/src/components/GitmojiList/Toolbar/index.tsx
================================================
'use client'
import { useEffect, useRef } from 'react'
import dynamic from 'next/dynamic'
import ListModeSelector from './ListModeSelector'
import ThemeSelector from './ThemeSelector'
const Kbd = dynamic(() => import('./Kbd'), { ssr: false })
import styles from './styles.module.css'
type Props = {
isListMode: boolean
searchInput?: string
setIsListMode: (searchInput: boolean) => void
setSearchInput: (searchInput: string) => void
}
const Toolbar = (props: Props) => {
const searchInputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
const keyboardEventListener = (event: KeyboardEvent) => {
const searchInput = searchInputRef.current
if (
searchInput &&
(event.ctrlKey || event.metaKey) &&
event.key === 'k'
) {
event.preventDefault()
searchInput.focus()
}
}
if (typeof window !== 'undefined') {
document.addEventListener('keydown', keyboardEventListener, false)
}
return () => {
document.removeEventListener('keydown', keyboardEventListener, false)
}
}, [])
return (
<div className={styles.container}>
<div className={styles.inputWrapper}>
<input
className={styles.searchInput}
ref={searchInputRef}
name="searchInput"
onChange={(event) => props.setSearchInput(event.target.value)}
placeholder="Search your gitmoji..."
type="search"
value={props.searchInput}
/>
<Kbd />
</div>
<div className={styles.actionsContainer}>
<ThemeSelector />
<ListModeSelector
isListMode={props.isListMode}
setIsListMode={props.setIsListMode}
/>
</div>
</div>
)
}
export default Toolbar
================================================
FILE: packages/website/src/components/GitmojiList/Toolbar/styles.module.css
================================================
.container {
align-items: center;
display: flex;
flex-direction: row;
margin-bottom: 0.5rem;
margin-top: 1.5rem;
}
.actionsContainer {
display: flex;
}
.inputWrapper {
flex: 1;
display: flex;
align-items: center;
border-radius: 4px;
border: 0;
box-shadow: 0 2px 4px 0 var(--cardShadow);
margin-right: 1rem;
background-color: var(--cardBackground);
}
.inputWrapper:focus-within {
outline: -webkit-focus-ring-color auto 1px;
}
.searchInput {
background-color: transparent;
border: none;
flex-grow: 1;
font-size: 1rem;
padding: 1rem;
}
.searchInput:focus-visible {
outline: none;
}
.searchInput:focus {
outline: none;
}
[data-theme='dark'] .searchInput {
color: var(--text);
}
@media (max-width: 568px) {
.searchInput {
margin: 0;
margin-top: 0.5rem;
}
.container {
flex-direction: column-reverse;
align-items: stretch;
}
}
================================================
FILE: packages/website/src/components/GitmojiList/__tests__/gitmojiList.spec.tsx
================================================
import { useRouter, useSearchParams } from 'next/navigation'
import { render, screen, fireEvent } from '@testing-library/react'
import GitmojiList from '../index'
import * as stubs from './stubs'
jest.mock('next/navigation', () => ({
useRouter: jest.fn(),
useSearchParams: jest.fn(),
usePathname: jest.fn(() => '/'),
}))
const useRouterMock = useRouter as jest.Mock
const useSearchParamsMock = useSearchParams as jest.Mock
describe('GitmojiList', () => {
beforeEach(() => {
useRouterMock.mockReturnValue(stubs.appRouterMock())
useSearchParamsMock.mockReturnValue(stubs.searchParamsMock())
})
describe('when is not list mode', () => {
it('should render the component in grid mode by default', () => {
const { container } = render(<GitmojiList {...stubs.props} />)
const articles = container.querySelectorAll('article')
expect(articles.length).toBeGreaterThan(0)
})
})
describe('when is list mode', () => {
it('should switch to list mode when clicking the list button', () => {
const { container } = render(<GitmojiList {...stubs.props} />)
const buttons = screen.getAllByRole('button')
const listModeButton = buttons[1]
fireEvent.click(listModeButton)
const articles = container.querySelectorAll('article')
expect(articles.length).toBeGreaterThan(0)
})
})
describe('when user search the fire gitmoji', () => {
beforeEach(() => {
useRouterMock.mockReturnValue(stubs.appRouterMock())
useSearchParamsMock.mockReturnValue(stubs.searchParamsMock())
})
it('should find the fire gitmoji by code', () => {
const { container } = render(<GitmojiList {...stubs.props} />)
const input = screen.getByRole('searchbox')
const query = 'Fire'
fireEvent.change(input, { target: { value: query } })
const articles = container.querySelectorAll('article')
expect(articles.length).toEqual(1)
})
it('should find the fire gitmoji by description', () => {
const { container } = render(<GitmojiList {...stubs.props} />)
const input = screen.getByRole('searchbox')
const query = 'remove'
fireEvent.change(input, { target: { value: query } })
const articles = container.querySelectorAll('article')
expect(articles.length).toEqual(1)
})
it('should find the fire gitmoji by emoji', () => {
const { container } = render(<GitmojiList {...stubs.props} />)
const input = screen.getByRole('searchbox')
const query = '🔥'
fireEvent.change(input, { target: { value: query } })
const articles = container.querySelectorAll('article')
expect(articles.length).toEqual(1)
})
})
describe('when search is provided by query string', () => {
beforeEach(() => {
useRouterMock.mockReturnValue(stubs.appRouterMock())
useSearchParamsMock.mockReturnValue(stubs.searchParamsMock('fire'))
})
it('should set the search input value to query.search', () => {
render(<GitmojiList {...stubs.props} />)
const query = 'fire'
const input = screen.getByRole('searchbox')
expect(input).toHaveValue(query)
})
describe('when the user deletes the search input', () => {
it('should clear the query string', () => {
const mockRouter = stubs.appRouterMock()
useRouterMock.mockReturnValue(mockRouter)
useSearchParamsMock.mockReturnValue(stubs.searchParamsMock('fire'))
render(<GitmojiList {...stubs.props} />)
const input = screen.getByRole('searchbox')
fireEvent.change(input, { target: { value: '' } })
expect(mockRouter.push).toHaveBeenCalledWith('/')
})
})
})
})
================================================
FILE: packages/website/src/components/GitmojiList/__tests__/stubs.ts
================================================
import { gitmojis } from 'gitmojis'
export const props = {
gitmojis: gitmojis.slice(0, 6),
}
export const routerMock = (query = {}) => ({
query,
push: jest.fn(),
})
export const appRouterMock = () => ({
push: jest.fn(),
replace: jest.fn(),
prefetch: jest.fn(),
back: jest.fn(),
forward: jest.fn(),
refresh: jest.fn(),
})
export const searchParamsMock = (searchValue?: string) => {
const params = new URLSearchParams()
if (searchValue) {
params.set('search', searchValue)
}
return params
}
================================================
FILE: packages/website/src/components/GitmojiList/emojiColorsMap.ts
================================================
export default {
'adhesive-bandage': '#fbcfb7',
alembic: '#7f39fb',
alien: '#c5e763',
ambulance: '#fb584a',
dizzy: '#ffdb3a',
'arrow-down': '#ef5350',
'arrow-up': '#00e676',
art: '#ff7281',
beers: '#fbb64b',
bento: '#ff5864',
bookmark: '#80deea',
boom: '#f94f28',
bug: '#8cd842',
'building-construction': '#ffe55f',
bulb: '#ffce49',
'busts-in-silhouette': '#ffce49',
'camera-flash': '#00a9f0',
'card-file-box': '#c5e763',
'chart-with-upwards-trend': '#cedae6',
'children-crossing': '#ffce49',
'clown-face': '#ff7281',
'construction-worker': '#64b5f6',
construction: '#ffb74d',
egg: '#77e856',
fire: '#ff9d44',
'globe-with-meridians': '#e7f4ff',
'goal-net': '#c7cb12',
'green-heart': '#c5e763',
hammer: '#ffc400',
coffin: '#d9e3e8',
'heavy-minus-sign': '#ef5350',
'heavy-plus-sign': '#00e676',
iphone: '#40c4ff',
label: '#cb63e6',
lipstick: '#80deea',
lock: '#ffce49',
'closed-lock-with-key': '#83beec',
'loud-sound': '#23b4d2',
mag: '#ffe55f',
memo: '#00e676',
mute: '#e6ebef',
'ok-hand': '#c5e763',
package: '#fdd0ae',
'page-facing-up': '#d9e3e8',
'passport-control': '#4dc6dc',
pencil: '#ffce49',
pencil2: '#ffce49',
poop: '#a78674',
pushpin: '#39c2f1',
recycle: '#77e856',
rewind: '#56d1d8',
rocket: '#00a9f0',
'rotating-light': '#536dfe',
'safety-vest': '#f2ad52',
'see-no-evil': '#8bdfe7',
seedling: '#c5e763',
sparkles: '#ffe55f',
'speech-balloon': '#cedae6',
stethoscope: '#77e856',
tada: '#f74d5f',
'test-tube': '#fb584a',
'triangular-flag-on-post': '#ffce49',
truck: '#ef584a',
'twisted-rightwards-arrows': '#56d1d8',
wastebasket: '#d9e3e8',
wheelchair: '#00b1fb',
'white-check-mark': '#77e856',
wrench: '#ffc400',
zap: '#40c4ff',
'monocle-face': '#ffe55f',
necktie: '#83beec',
bricks: '#ff6723',
technologist: '#86B837',
'money-with-wings': '#b3c0b1',
thread: '#ffbe7b',
airplane: '#74d4ec',
't-rex': '#56d1d8',
} as const
================================================
FILE: packages/website/src/components/GitmojiList/hooks/__tests__/stubs.ts
================================================
export const localStorageMock = {
key: 'gitmojiTestKey',
value: 'gitmojiTestValue',
}
================================================
FILE: packages/website/src/components/GitmojiList/hooks/__tests__/useLocalStorage.spec.tsx
================================================
import { render } from '@testing-library/react'
import useLocalStorage from '../useLocalStorage'
import * as stubs from './stubs'
const TestComponent = ({
storageKey,
storageValue,
}: {
storageKey: string
storageValue: string
}) => {
useLocalStorage(storageKey, storageValue)
return null
}
Object.defineProperty(window, 'localStorage', {
writable: true,
value: { setItem: jest.fn(), getItem: jest.fn() },
})
const getItem = window.localStorage.getItem as jest.Mock
const setItem = window.localStorage.setItem as jest.Mock
describe('useLocalStorage', () => {
beforeEach(() => {
jest.clearAllMocks()
})
describe('when value is not persisted', () => {
beforeEach(() => {
getItem.mockReturnValue(null)
})
it('should call localStorage.setItem', () => {
const { rerender } = render(
<TestComponent
storageKey={stubs.localStorageMock.key}
storageValue={stubs.localStorageMock.value}
/>,
)
rerender(
<TestComponent
storageKey={stubs.localStorageMock.key}
storageValue={stubs.localStorageMock.value}
/>,
)
expect(setItem).toHaveBeenCalledWith(
stubs.localStorageMock.key,
stubs.localStorageMock.value,
)
})
})
describe('when there is an error', () => {
const consoleError = console.error
beforeEach(() => {
getItem.mockImplementation(() => {
throw new Error('Test')
})
Object.defineProperty(console, 'error', {
writable: true,
value: jest.fn(),
})
})
afterEach(() => {
Object.defineProperty(console, 'error', {
writable: true,
value: consoleError,
})
})
it('should call console.error', () => {
const { rerender } = render(
<TestComponent
storageKey={stubs.localStorageMock.key}
storageValue={stubs.localStorageMock.value}
/>,
)
rerender(
<TestComponent
storageKey={stubs.localStorageMock.key}
storageValue={stubs.localStorageMock.value}
/>,
)
expect(console.error).toHaveBeenCalledWith(expect.any(String))
})
})
})
================================================
FILE: packages/website/src/components/GitmojiList/hooks/useLocalStorage.tsx
================================================
import { useState, useEffect } from 'react'
export default function useLocalStorage<T>(key: string, defaultValue: T) {
const [state, setState] = useState(defaultValue)
useEffect(() => {
try {
const localValue = window.localStorage.getItem(key)
if (localValue !== null) {
setState(JSON.parse(localValue))
}
} catch (error) {
console.error(`ERROR: Loading ${key} from localStorage – ${error}`)
}
}, [])
useEffect(() => {
window.localStorage.setItem(key, `${state}`)
}, [state])
return [state, setState] as const
}
================================================
FILE: packages/website/src/components/GitmojiList/index.tsx
================================================
'use client'
import { Suspense, useEffect, useState } from 'react'
import Clipboard from 'clipboard'
import type { Gitmoji as GitmojiType } from 'gitmojis'
import toast from 'react-hot-toast'
import Gitmoji from './Gitmoji'
import Toolbar from './Toolbar'
import SearchParamsSync from './SearchParamsSync'
import useLocalStorage from './hooks/useLocalStorage'
import styles from './styles.module.css'
type Props = {
gitmojis: readonly GitmojiType[]
}
const GitmojiList = (props: Props) => {
const [searchInput, setSearchInput] = useState('')
const [isListMode, setIsListMode] = useLocalStorage('isListMode', false)
const gitmojis = searchInput
? props.gitmojis.filter(({ emoji, code, description }) => {
const lowerCasedSearch = searchInput.toLowerCase()
return (
code.includes(lowerCasedSearch) ||
description.toLowerCase().includes(lowerCasedSearch) ||
emoji == searchInput
)
})
: props.gitmojis
useEffect(() => {
const clipboard = new Clipboard(
'.gitmoji-clipboard-emoji, .gitmoji-clipboard-code',
)
clipboard.on('success', function (e) {
toast(
(t) => (
<span className={styles.notification}>
<p>
Hey! Gitmoji <span className={styles.gitmojiCode}>{e.text}</span>{' '}
copied to the clipboard 😜
</p>
<span
className={styles.closeButton}
onClick={() => toast.dismiss(t.id)}
/>
</span>
),
{
id: 'clipboard',
style: {
background: '#ff5a79',
color: '#ffffff',
fontWeight: 600,
fontSize: '90%',
},
},
)
})
return () => clipboard.destroy()
}, [])
return (
<div className="row" id="gitmoji-list">
<Suspense fallback={null}>
<SearchParamsSync
searchInput={searchInput}
setSearchInput={setSearchInput}
/>
</Suspense>
<div className="col-xs-12">
<Toolbar
isListMode={isListMode}
searchInput={searchInput}
setIsListMode={setIsListMode}
setSearchInput={setSearchInput}
/>
</div>
{gitmojis.length === 0 ? (
<h2>No gitmojis found for search: {searchInput}</h2>
) : (
gitmojis.map((gitmoji, index) => (
<Gitmoji
code={gitmoji.code}
description={gitmoji.description}
emoji={gitmoji.emoji}
isListMode={isListMode}
key={index}
// @ts-expect-error: This should be replaced with something like:
// typeof gitmojis[number]['name'] but JSON can't be exported `as const`
name={gitmoji.name}
/>
))
)}
</div>
)
}
export default GitmojiList
================================================
FILE: packages/website/src/components/GitmojiList/styles.module.css
================================================
.gitmojiCode {
padding: 0 4px;
border-radius: 4px;
background-color: var(--notificationEmojiCodeColor);
color: var(--notificationText);
}
.closeButton {
width: 20px;
height: 20px;
position: absolute;
right: 4px;
top: 4px;
overflow: hidden;
text-indent: 100%;
cursor: pointer;
backface-visibility: hidden;
}
.closeButton:hover,
.closeButton:focus {
outline: none;
}
.closeButton::before,
.closeButton::after {
content: '';
position: absolute;
width: 3px;
height: 60%;
top: 50%;
left: 50%;
background: var(--notificationText);
}
.closeButton::before {
transform: translate(-50%, -50%) rotate(45deg);
}
.closeButton::after {
transform: translate(-50%, -50%) rotate(-45deg);
}
================================================
FILE: packages/website/src/components/Icon/__tests__/icon.spec.tsx
================================================
import { render } from '@testing-library/react'
import Icon, { IconDefinitions } from '../index'
import * as stubs from './stubs'
describe('Icon', () => {
it('should render the component with correct icon reference', () => {
const { container } = render(<Icon {...stubs.props} />)
const svg = container.querySelector('svg')
expect(svg).toBeInTheDocument()
expect(svg).toHaveClass(`icon-${stubs.props.name}`)
const use = container.querySelector('use')
expect(use).toBeInTheDocument()
expect(use).toHaveAttribute('xlink:href', `#icon-${stubs.props.name}`)
})
it('should render IconDefinitions with all symbols', () => {
const { container } = render(<IconDefinitions />)
const svg = container.querySelector('svg')
expect(svg).toBeInTheDocument()
const defs = container.querySelector('defs')
expect(defs).toBeInTheDocument()
})
})
================================================
FILE: packages/website/src/components/Icon/__tests__/stubs.ts
================================================
export const props = {
name: 'star',
}
================================================
FILE: packages/website/src/components/Icon/definitions.tsx
================================================
export const IconDefinitions = () => (
<svg
style={{ position: 'absolute', width: 0, height: 0 }}
width={0}
height={0}
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<defs>
<symbol id="icon-heart" viewBox="0 0 64 64">
<title>heart</title>
<path
className="heart"
d="m61.1 18.2c-6.4-17-27.2-9.4-29.1-.9-2.6-9-22.9-15.7-29.1.9-6.9 18.5 26.7 35.1 29.1 37.8 2.4-2.2 36-19.6 29.1-37.8"
fill="#ff5a79"
/>
</symbol>
<symbol id="icon-star" viewBox="0 0 64 64">
<title>star</title>
<path
className="twitter"
d="M62,25.2H39.1L32,3l-7.1,22.2H2l18.5,13.7l-7,22.1L32,47.3L50.5,61l-7.1-22.2L62,25.2z"
fill="#FFDD67"
/>
</symbol>
<symbol id="icon-twitter" viewBox="0 0 64 64">
<title>twitter</title>
<g fill="#42ade2">
<path d="m59.8 24.3c0 0 1.1-6.2-3.5-3.4 0 0-.4-6.3-4.3-1.9 0 0-2.1-3.9-4.4-.3-3.1 4.8-5.2 12.4-3.2 25l3.8-2.5c2.7-7.9 12.4-8.8 13.7-13.1.9-3-2.1-3.8-2.1-3.8" />
<path d="m22.1 17.6l-9.9 3.6c2.2-12 16.6-11.2 16.6-11.2s-6.8 3.2-6.7 7.6" />
<path d="m23.7 19.8l-10.5 1.4c4.8-11.2 18.7-7.3 18.7-7.3s-7.3 1.6-8.2 5.9" />
</g>
<g fill="#ffd93b">
<path d="m2 29l5.4-1.4v3.6c0-.1-3.3-.6-5.4-2.2" />
<path d="M7.4,27.5L2,24.8c3.6-2.8,7.7-1.9,7.7-1.9L7.4,27.5z" />
</g>
<g fill="#e08828">
<path d="m33.8 53h-2.1v7.9c-.3.1-2.1-.1-2.9-.1-1.8 0-3.3 1.3-3.3 1.3h8.3v-9.1" />
<path d="m25 53h-2.1v7.9c-.3.1-2.1-.1-2.9-.1-1.8 0-3.3 1.3-3.3 1.3h8.3v-9.1" />
<path
d="m54 36.2c3.9 0-4.1 17.5-23.3 17.5-13 0-23.9-5.2-23.9-21.5 0-10.1 6.4-18.3 19.5-15 13.3 3.5 6.5 19 27.7 19"
fill="#42ade2"
/>
<path
d="m37.6 51.7c-15.6 0-14-12-27.9-11.2 5.1 15.8 27.9 11.2 27.9 11.2"
fill="#fff"
/>
<path
d="m39.1 29.2c-10-9.8-20.2 6.2-7.9 12.6 12.1 6.2 20.4-4.8 20.4-4.8s-6.1-1.5-12.5-7.8"
fill="#297b9d"
/>
</g>
<circle cx="15.1" cy="24.9" r="2.5" fill="#3e4347" />
</symbol>
<symbol id="icon-twitter-x" viewBox="0 0 48 35">
<title>twitter-x</title>
<path d="M12.91 5.477l14.813 19.882-14.907 16.164h3.356l13.05-14.152 10.544 14.152h11.418l-15.645-21L49.414 5.477H46.06l-12.02 13.035-9.71-13.035zm4.934 2.48h5.242L46.25 39.043h-5.246zm0 0"></path>
</symbol>
<symbol id="icon-list" x="0px" y="0px" viewBox="0 0 512 512">
<title>list</title>
<path
fill="currentColor"
d="M149.333 216v80c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24v-80c0-13.255 10.745-24 24-24h101.333c13.255 0 24 10.745 24 24zM0 376v80c0 13.255 10.745 24 24 24h101.333c13.255 0 24-10.745 24-24v-80c0-13.255-10.745-24-24-24H24c-13.255 0-24 10.745-24 24zM125.333 32H24C10.745 32 0 42.745 0 56v80c0 13.255 10.745 24 24 24h101.333c13.255 0 24-10.745 24-24V56c0-13.255-10.745-24-24-24zm80 448H488c13.255 0 24-10.745 24-24v-80c0-13.255-10.745-24-24-24H205.333c-13.255 0-24 10.745-24 24v80c0 13.255 10.745 24 24 24zm-24-424v80c0 13.255 10.745 24 24 24H488c13.255 0 24-10.745 24-24V56c0-13.255-10.745-24-24-24H205.333c-13.255 0-24 10.745-24 24zm24 264H488c13.255 0 24-10.745 24-24v-80c0-13.255-10.745-24-24-24H205.333c-13.255 0-24 10.745-24 24v80c0 13.255 10.745 24 24 24z"
className=""
></path>
</symbol>
<symbol id="icon-grid" viewBox="0 0 512 512">
<title>grid</title>
<path
fill="currentColor"
d="M149.333 56v80c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V56c0-13.255 10.745-24 24-24h101.333c13.255 0 24 10.745 24 24zm181.334 240v-80c0-13.255-10.745-24-24-24H205.333c-13.255 0-24 10.745-24 24v80c0 13.255 10.745 24 24 24h101.333c13.256 0 24.001-10.745 24.001-24zm32-240v80c0 13.255 10.745 24 24 24H488c13.255 0 24-10.745 24-24V56c0-13.255-10.745-24-24-24H386.667c-13.255 0-24 10.745-24 24zm-32 80V56c0-13.255-10.745-24-24-24H205.333c-13.255 0-24 10.745-24 24v80c0 13.255 10.745 24 24 24h101.333c13.256 0 24.001-10.745 24.001-24zm-205.334 56H24c-13.255 0-24 10.745-24 24v80c0 13.255 10.745 24 24 24h101.333c13.255 0 24-10.745 24-24v-80c0-13.255-10.745-24-24-24zM0 376v80c0 13.255 10.745 24 24 24h101.333c13.255 0 24-10.745 24-24v-80c0-13.255-10.745-24-24-24H24c-13.255 0-24 10.745-24 24zm386.667-56H488c13.255 0 24-10.745 24-24v-80c0-13.255-10.745-24-24-24H386.667c-13.255 0-24 10.745-24 24v80c0 13.255 10.745 24 24 24zm0 160H488c13.255 0 24-10.745 24-24v-80c0-13.255-10.745-24-24-24H386.667c-13.255 0-24 10.745-24 24v80c0 13.255 10.745 24 24 24zM181.333 376v80c0 13.255 10.745 24 24 24h101.333c13.255 0 24-10.745 24-24v-80c0-13.255-10.745-24-24-24H205.333c-13.255 0-24 10.745-24 24z"
></path>
</symbol>
<symbol id="icon-light" viewBox="0 0 24 24">
<title>light</title>
<path
fill="currentColor"
d="M3.563 18.563l1.781-1.828 1.406 1.406-1.781 1.828zM11.016 22.453v-2.953h1.969v2.953h-1.969zM12 5.484q2.484 0 4.242 1.758t1.758 4.242-1.758 4.242-4.242 1.758-4.242-1.758-1.758-4.242 1.758-4.242 4.242-1.758zM20.016 10.5h3v2.016h-3v-2.016zM17.25 18.141l1.406-1.359 1.781 1.781-1.406 1.406zM20.438 4.453l-1.781 1.781-1.406-1.406 1.781-1.781zM12.984 0.563v2.953h-1.969v-2.953h1.969zM3.984 10.5v2.016h-3v-2.016h3zM6.75 4.828l-1.406 1.406-1.781-1.781 1.406-1.406z"
></path>
</symbol>
<symbol id="icon-dark" viewBox="0 0 24 24">
<title>dark</title>
<path
fill="currentColor"
d="M9.984 2.016q4.172 0 7.102 2.93t2.93 7.055-2.93 7.055-7.102 2.93q-2.719 0-4.969-1.313 2.297-1.313 3.633-3.633t1.336-5.039-1.336-5.039-3.633-3.633q2.25-1.313 4.969-1.313z"
></path>
</symbol>
</defs>
</svg>
)
================================================
FILE: packages/website/src/components/Icon/index.tsx
================================================
export { IconDefinitions } from './definitions'
import styles from './styles.module.css'
type Props = { name: string }
const Icon = (props: Props) => (
<svg className={`${styles.icon} icon-${props.name}`}>
<use xlinkHref={`#icon-${props.name}`} />
</svg>
)
export default Icon
================================================
FILE: packages/website/src/components/Icon/styles.module.css
================================================
.icon {
width: 1em;
height: 1em;
margin-right: 0.25em;
}
.icon:global(.icon-heart) {
margin: 0;
}
================================================
FILE: packages/website/src/components/Layout/Footer/index.tsx
================================================
import Link from 'next/link'
import Icon from 'src/components/Icon'
import styles from './styles.module.css'
const Footer = () => (
<footer className={styles.footer}>
<div className="wrap">
<div className="row middle-xs">
<div className={`col-sm-6 ${styles.madeWithLove}`}>
<h3>
Made with <Icon name="heart" /> by{' '}
<a href="https://carloscuesta.me">Carlos Cuesta</a>
</h3>
</div>
<div className={`col-sm-6 ${styles.footerNav}`}>
<nav>
<Link href="/about">About</Link>
<Link href="/contributors">Contributors</Link>
<a href="https://github.com/carloscuesta/gitmoji">GitHub</a>
</nav>
</div>
</div>
</div>
</footer>
)
export default Footer
================================================
FILE: packages/website/src/components/Layout/Footer/styles.module.css
================================================
.footer {
padding: 1.5em;
background-color: var(--footerBackground);
color: var(--textInSecondary);
}
.footerNav {
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
text-align: end;
}
.footerNav a:after {
content: '·';
color: var(--textInSecondary);
margin: 0 0.75em;
}
.footerNav a:last-child:after {
content: '';
margin: 0;
}
@media (max-width: 768px) {
.footer,
.footerNav {
justify-content: center;
text-align: center;
}
.madeWithLove,
.footerNav {
flex-basis: 100%;
max-width: 100%;
}
}
================================================
FILE: packages/website/src/components/Layout/Hamburger/CloseIcon/index.tsx
================================================
const CloseIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="48"
height="48"
fill="currentColor"
>
<path d="M37.968 12.844L26.812 24L37.968 35.156L35.156 37.968L24 26.812L12.844 37.968L10.032 35.156L21.188 24L10.032 12.844L12.844 10.032L24 21.188L35.156 10.032L37.968 12.844Z" />
</svg>
)
export default CloseIcon
================================================
FILE: packages/website/src/components/Layout/Hamburger/MenuLink/index.tsx
================================================
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import styles from './styles.module.css'
type Props = { href: string; text: string }
const MenuLink = (props: Props) => {
const pathname = usePathname()
const isUserOnLinkPage: boolean = props.href === pathname
if (!props.href.startsWith('/')) {
return (
<a
className={styles.link}
href={props.href}
rel="noopener noreferrer"
target="_blank"
>
{props.text}
</a>
)
}
return (
<Link
className={[styles.link, isUserOnLinkPage && styles.linkActive].join(' ')}
href={props.href}
>
{props.text}
</Link>
)
}
export default MenuLink
================================================
FILE: packages/website/src/components/Layout/Hamburger/MenuLink/styles.module.css
================================================
.link {
color: var(--text);
text-decoration: none;
font-weight: bold;
}
.link:hover {
text-decoration: underline;
}
.linkActive {
text-decoration: underline;
}
================================================
FILE: packages/website/src/components/Layout/Hamburger/OpenIcon/index.tsx
================================================
const OpenIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="48"
height="48"
fill="currentColor"
>
<path d="M6 12H42V16.032H6V12ZM6 25.968V22.03H42V25.968H6ZM6 36V31.968H42V36H6Z" />
</svg>
)
export default OpenIcon
================================================
FILE: packages/website/src/components/Layout/Hamburger/index.tsx
================================================
'use client'
import { useState, useEffect } from 'react'
import { usePathname } from 'next/navigation'
import FocusTrap from 'focus-trap-react'
import MenuLink from './MenuLink'
import OpenIcon from './OpenIcon'
import CloseIcon from './CloseIcon'
import styles from './styles.module.css'
const Hamburger = () => {
const pathname = usePathname()
const [isOpen, setIsOpen] = useState(false)
useEffect(() => {
setIsOpen(false)
}, [pathname])
useEffect(() => {
if (isOpen) {
document.body.classList.add('overflow-hidden')
} else {
document.body.classList.remove('overflow-hidden')
}
}, [isOpen])
return (
<div className={styles.hamburger}>
<button
aria-label="Open navigation menu"
className={styles.button}
onClick={() => setIsOpen(true)}
>
<OpenIcon />
</button>
{isOpen && (
<FocusTrap active={isOpen}>
<nav className={styles.menu}>
<div className={styles.closeContainer}>
<button
aria-label="Close navigation menu"
className={styles.button}
onClick={() => setIsOpen(false)}
>
<CloseIcon />
</button>
</div>
<ul className={styles.links}>
<li>
<MenuLink href="/" text="Home" />
</li>
<li>
<MenuLink href="/about" text="About" />
</li>
<li>
<MenuLink href="/specification" text="Specification" />
</li>
<li>
<MenuLink href="/contributors" text="Contributors" />
</li>
<li>
<MenuLink href="/related-tools" text="Related tools" />
</li>
</ul>
</nav>
</FocusTrap>
)}
</div>
)
}
export default Hamburger
================================================
FILE: packages/website/src/components/Layout/Hamburger/styles.module.css
================================================
.hamburger {
position: fixed;
right: 0;
z-index: 1;
}
.menu {
background: var(--menuBackground);
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
}
.button {
background-color: transparent;
border: none;
color: inherit;
color: var(--secondary);
cursor: pointer;
display: flex;
font: inherit;
margin: 0;
padding: 0.75em;
}
.closeContainer {
position: absolute;
right: 0;
}
.closeContainer .button {
color: var(--textInSecondary);
}
.links {
align-items: center;
color: var(--textInSecondary);
display: flex;
flex-direction: column;
flex: 1;
font-size: 3em;
height: 100%;
justify-content: center;
list-style-type: none;
margin: 0;
padding: 0;
}
.links li {
padding: 0.5em 0;
}
================================================
FILE: packages/website/src/components/Layout/Header/Logo/Status/Joy/index.tsx
================================================
export const Joy = () => (
<g id="joy" transform="translate(304 32)">
<g id="Group">
<circle id="Oval" cy={39} cx={39} r={39} fill="#FFDD67" />
<path
id="Shape"
fill="#664E27"
d="m62 42.2c-0.5-0.7-1.5-0.6-2.5-0.6h-41c-1 0-2-0.1-2.5 0.6-5.1 6.4 0.9 25.4 23 25.4s28.1-19 23-25.4z"
/>
<path
id="Shape"
fill="#4C3526"
d="m41.4 51.7c-0.8-0.1-1.9 0.6-1.5 2.5 0.2 0.9 1.6 2.1 1.6 3.6 0 3.1-5 3.1-5 0 0-1.5 1.4-2.7 1.6-3.6 0.4-1.9-0.7-2.6-1.5-2.5-2 0-5.4 2.2-5.4 5.9 0 4.2 3.5 7.6 7.8 7.6s7.8-3.4 7.8-7.6c0-3.7-3.4-5.9-5.4-5.9z"
/>
<path
id="Shape"
fill="#FF717F"
d="m29 63.3c2.9 1.2 6.2 1.9 10 1.9s7.1-0.7 10-1.9c-2.8-1.4-6.1-2.2-10-2.2s-7.2 0.8-10 2.2z"
/>
<path
id="Shape"
fill="#fff"
d="m58.4 44.2h-38.8c-2.7 0-2.7 5.2-0.1 5.2h39c2.6 0 2.6-5.2-0.1-5.2z"
/>
<g id="Shape" fill="#65B1EF" transform="translate(0 37.7)">
<path d="m74.7 7.64c9.5 9.96-3.4 23.6-12.9 13.6-7-7.3-7.3-21.2-7.3-21.2 0 0.013 13.2 0.347 20.2 7.64zm-58.5 13.6c-9.46 10-22.4-3.6-12.9-13.6 7-7.25 20.2-7.59 20.2-7.59 0 0.003-0.3 13.9-7.3 21.2z" />
</g>
<g id="Shape" fill="#664E27" transform="translate(14.3 24.7)">
<path d="m20.2 9.97c-2.4-6.64-6.1-9.97-9.7-9.97-3.66 0-7.3 3.33-9.71 9.97-0.243 0.63 1 1.83 1.63 1.23 2.34-2.48 5.14-3.47 8.08-3.47 2.9 0 5.7 0.99 8.1 3.47 0.6 0.6 1.8-0.6 1.6-1.23zm28.4 0c-2.4-6.64-6-9.97-9.7-9.97-3.6 0-7.3 3.33-9.7 9.97-0.2 0.63 1 1.83 1.6 1.23 2.4-2.48 5.2-3.47 8.1-3.47s5.7 0.99 8.1 3.47c0.6 0.6 1.9-0.6 1.6-1.23z" />
</g>
</g>
</g>
)
export default Joy
================================================
FILE: packages/website/src/components/Layout/Header/Logo/Status/Loved/index.tsx
================================================
export const Loved = () => (
<g id="loved" transform="translate(304 32)">
<g id="Group">
<path
id="Shape"
fill="#FFDD67"
d="m78 39c0 21.5-17.5 39-39 39s-39-17.5-39-39 17.5-39 39-39 39 17.5 39 39z"
/>
<path
id="Shape"
fill="#F46767"
d="m77.8 14.6c-0.6-3.5-2.6-6.37-5.8-7.23-3.4-0.95-6.6 0.41-9.7 3.53-1.7-4.74-4.3-8.24-8.4-10-4.3-1.89-8.4-0.645-11 2.64-2.7 3.42-3.8 8.66-0.9 15.6 2.7 6.5 14.9 19.5 15.2 19.9 0.5-0.3 14-8.7 17.3-12.9 3.2-4 3.9-8.1 3.3-11.5zm-42.7-11.1c-2.6-3.28-6.7-4.53-11-2.68-4.1 1.8-6.7 5.3-8.4 10-3.1-3.12-6.25-4.48-9.7-3.53-3.16 0.86-5.2 3.73-5.8 7.23-0.599 3.4 0.072 7.5 3.31 11.5 3.31 4.2 16.8 12.6 17.3 12.9 0.3-0.4 12.5-13.4 15.2-19.9 2.9-6.9 1.8-12.1-0.9-15.6v0.04z"
/>
<path
id="Shape"
fill="#664E27"
d="m61.1 46.9c0-1.1-0.6-2.4-2.4-2.7-4.5-0.9-11.1-1.8-19.7-1.8s-15.2 0.9-19.7 1.8c-1.8 0.3-2.4 1.6-2.4 2.7 0 9.4 7.3 18.9 22.1 18.9s22.1-9.5 22.1-18.9z"
/>
<path
id="Shape"
fill="#fff"
d="m55.5 47.2c-2.9-0.5-8.9-1.3-16.5-1.3s-13.6 0.8-16.5 1.3c-1.7 0.3-1.8 0.9-1.7 1.9 0.1 0.6 0.2 1.3 0.4 2 0.2 0.9 0.3 1.2 1.6 1.1 2.5-0.3 29.9-0.3 32.4 0 1.3 0.1 1.4-0.2 1.6-1.1 0.2-0.7 0.3-1.4 0.4-2 0.1-1 0-1.6-1.7-1.9z"
/>
</g>
</g>
)
export default Loved
================================================
FILE: packages/website/src/components/Layout/Header/Logo/Status/Sexy/index.tsx
================================================
export const Sexy = () => (
<g id="sexy" transform="translate(304 32)">
<g id="Group">
<ellipse id="Oval" rx={39} ry={39} cy={39} cx={39} fill="#FFDD67" />
<ellipse
id="Oval"
rx="10.4"
ry="10.4"
cy="45.7"
cx="66.1"
fill="#FF717F"
/>
<ellipse
id="Oval"
rx="10.4"
ry="10.4"
cy="45.7"
cx="11.9"
fill="#FF717F"
/>
<path
id="Shape"
fill="#917524"
d="m68.4 24.2c-3.5-4.2-8.7-6.7-14.2-6.6-0.8 0-1-2.9 0-2.9 6.3 0 12.4 2.8 16.4 7.7 0.6 0.7-1.7 2.4-2.2 1.8zm-44.6-6.8c-5.5 0-10.7 2.4-14.2 6.6-0.52 0.6-2.81-1.1-2.23-1.8 4.03-4.9 10.1-7.7 16.4-7.7 1 0 0.8 2.9 0 2.9z"
/>
<ellipse id="Oval" rx="5.85" ry="5.85" cy="61.7" cx={39} fill="#664E27" />
<path
id="Shape"
fill="#fff"
d="m35.8 35.3c0 6.4-5.3 11.7-11.8 11.7-6.4 0-11.6-5.3-11.6-11.7 0-6.5 5.2-11.7 11.6-11.7 6.5 0 11.8 5.2 11.8 11.7z"
/>
<ellipse id="Oval" rx="5.85" ry="5.85" cy="35.3" cx={24} fill="#664E27" />
<g transform="translate(41.6 23.4)">
<path
id="Shape"
fill="#fff"
d="m24 11.9c0 6.4-5.2 11.7-11.6 11.7-6.51 0-11.8-5.3-11.8-11.7 0.05-6.48 5.29-11.7 11.8-11.7 6.4-0.022 11.6 5.22 11.6 11.7z"
/>
<ellipse
id="Oval"
rx="5.85"
ry="5.85"
cy="11.9"
cx="12.4"
fill="#664E27"
/>
</g>
</g>
</g>
)
export default Sexy
================================================
FILE: packages/website/src/components/Layout/Header/Logo/Status/Smiling/index.tsx
================================================
export const Smiling = () => (
<g id="haha" transform="translate(304 32)">
<g id="Group">
<path
id="Oval"
fill="#FFDD67"
d="m39 78c21.5 0 39-17.5 39-39s-17.5-39-39-39-39 17.5-39 39 17.5 39 39 39z"
/>
<g id="Shape" fill="#664E27" transform="translate(11.7 20.5)">
<path d="m52.9 2.09c0.3 0.15 0.4 0.47 0.4 0.79-0.1 0.32-0.3 0.57-0.6 0.63-3.5 0.52-7.3 1.12-10.8 3.07 5.2 0.87 9.4 3.52 11.7 6.32 0.5 0.6-0.1 1.4-0.7 1.2-6.2-2.3-12.6-3.5-20.6-2.6-0.6 0-1.2-0.3-1-0.9 2.1-9.39 14.2-12.9 21.6-8.51zm-51.2 0c-0.29 0.15-0.45 0.47-0.4 0.79s0.3 0.57 0.62 0.63c3.5 0.52 7.21 1.12 10.8 3.07-5.35 0.87-9.55 3.52-11.8 6.32-0.538 0.6 0.11 1.4 0.68 1.2 6.2-2.3 12.6-3.5 20.6-2.6 0.6 0 1.2-0.3 1-0.9-2.1-9.39-14.2-12.9-21.6-8.51h0.03z" />
</g>
<path
id="Shape"
fill="#664E27"
d="m62 42.2c-0.5-0.7-1.5-0.6-2.5-0.6h-41c-1 0-2-0.1-2.5 0.6-5.1 6.4 0.9 25.4 23 25.4s28.1-19 23-25.4z"
/>
<path
id="Shape"
fill="#4C3526"
d="m41.4 51.7c-0.8-0.1-1.9 0.6-1.5 2.5 0.2 0.9 1.6 2.1 1.6 3.6 0 3.1-5 3.1-5 0 0-1.5 1.4-2.7 1.6-3.6 0.4-1.9-0.7-2.6-1.5-2.5-2 0-5.4 2.2-5.4 5.9 0 4.2 3.5 7.6 7.8 7.6s7.8-3.4 7.8-7.6c0-3.7-3.4-5.9-5.4-5.9z"
/>
<path
id="Shape"
fill="#FF717F"
d="m29 63.3c2.9 1.2 6.2 1.9 10 1.9s7.1-0.7 10-1.9c-2.8-1.4-6.1-2.2-10-2.2s-7.2 0.8-10 2.2z"
/>
<path
id="Shape"
fill="#fff"
d="m58.4 44.2h-38.8c-2.7 0-2.7 5.2-0.1 5.2h39c2.6 0 2.6-5.2-0.1-5.2z"
/>
</g>
</g>
)
export default Smiling
================================================
FILE: packages/website/src/components/Layout/Header/Logo/Status/Sunglasses/index.tsx
================================================
export const Sunglasses = () => (
<g id="sunglasses" transform="translate(304 32)">
<g id="Group">
<path
id="Shape"
fill="#FFDD67"
d="m39 0c21.5 0 39 17.5 39 39s-17.5 39-39 39-39-17.5-39-39 17.5-39 39-39"
/>
<path
id="Shape"
fill="#494949"
d="m44 24c-2.9 1.4-7.1 1.4-10 0-3.1-1.6-6.8-2.6-11.3-2.9-4.3-0.4-13.6-0.4-18.2 1.2-0.52 0.2-1.04 0.4-1.55 0.7-0.28 0.1-0.34 0.2-0.34 0.8v0.7c0 1.3-0.16 0.8 0.76 1.3 1.8 1 2.82 3.8 3.36 7.5 0.78 5.5 3.47 8.9 7.87 10.6 4 1.5 8.5 1.5 12.6-0.2 2.2-0.8 4.1-2.2 5.6-4.5 2.7-3.9 1.9-6.4 3.3-9.8 1.2-2.9 4.6-2.9 5.8 0 1.4 3.4 0.6 5.9 3.3 9.8 1.5 2.3 3.4 3.7 5.6 4.5 4.1 1.7 8.6 1.7 12.6 0.2 4.4-1.7 7.1-5.1 7.9-10.6 0.5-3.7 1.5-6.5 3.3-7.5 0.9-0.5 0.8 0 0.8-1.3v-0.7c0-0.6-0.1-0.7-0.4-0.8-0.5-0.3-1-0.5-1.5-0.7-4.6-1.6-13.9-1.6-18.2-1.2-4.5 0.3-8.2 1.3-11.3 2.9"
/>
<path
id="Shape"
fill="#664E27"
d="m55.4 52.4c-10.6 7.3-22.3 7.3-32.8 0-1.2-0.9-2.4 0.6-1.5 2 3.2 5.3 9.6 10 17.9 10s14.7-4.7 17.9-10c0.9-1.4-0.3-2.9-1.5-2z"
/>
</g>
</g>
)
export default Sunglasses
================================================
FILE: packages/website/src/components/Layout/Header/Logo/Status/Tongue/index.tsx
================================================
export const Tongue = () => (
<g id="tongue" transform="translate(304 32)">
<g id="Group">
<ellipse id="Oval" rx={39} ry={39} cy={39} cx={39} fill="#FFDD67" />
<path
id="Shape"
fill="#fff"
d="m38 29.4c0 7.1-5.8 13-13 13s-13-5.9-13-13c0-7.2 5.8-13 13-13s13 5.8 13 13z"
/>
<ellipse id="Oval" rx="5.85" ry="5.85" cy="29.4" cx={25} fill="#664E27" />
<path
id="Shape"
fill="#664E27"
d="m63.7 35.3c-2.5-5.3-6.1-8-9.7-8-3.7 0-7.3 2.7-9.8 8-0.2 0.5 1 1.5 1.7 0.9 2.3-1.9 5.1-2.7 8.1-2.7 2.9 0 5.7 0.8 8 2.7 0.7 0.6 1.9-0.4 1.7-0.9z"
/>
<g id="Shape" transform="translate(16.9 46.8)">
<path
d="m42.7 0h-41.2c-0.989 0-1.5 0.659-1.5 1.3 0.0013 9.5 7.75 19.5 22.1 19.5s22.1-10 22.1-19.5c0-0.641-0.5-1.3-1.5-1.3z"
fill="#664E27"
/>
<path
d="m34 7.8h-11.9-11.9c-0.95 0-1.1 0.41-1.1 1.1v5.2c0 11.4 5.8 17.1 13 17.1s13-5.7 13-17.1v-5.2c0-0.69-0.1-1.1-1.1-1.1z"
fill="#FF717F"
/>
<polygon points="24 7.8 22.1 25.7 20.2 7.8" fill="#E2596C" />
</g>
</g>
</g>
)
export default Tongue
================================================
FILE: packages/website/src/components/Layout/Header/Logo/Status/index.tsx
================================================
import Joy from './Joy'
import Loved from './Loved'
import Sexy from './Sexy'
import Smiling from './Smiling'
import Sunglasses from './Sunglasses'
import Tongue from './Tongue'
export const LOGO_STATUSES = {
JOY: 'JOY',
LOVED: 'LOVED',
SEXY: 'SEXY',
SMILING: 'SMILING',
SUNGLASSES: 'SUNGLASSES',
TONGUE: 'TONGUE',
} as const
export type EmojiLogoStatus = keyof typeof LOGO_STATUSES | null
type Props = { status: EmojiLogoStatus }
const Status = (props: Props) => {
switch (props.status) {
case LOGO_STATUSES.JOY:
return <Joy />
case LOGO_STATUSES.LOVED:
return <Loved />
case LOGO_STATUSES.SEXY:
return <Sexy />
case LOGO_STATUSES.SMILING:
return <Smiling />
case LOGO_STATUSES.SUNGLASSES:
return <Sunglasses />
case LOGO_STATUSES.TONGUE:
return <Tongue />
default:
return null
}
}
export default Status
================================================
FILE: packages/website/src/components/Layout/Header/Logo/Status/styles.module.css
================================================
================================================
FILE: packages/website/src/components/Layout/Header/Logo/index.tsx
================================================
'use client'
import { useState, useEffect } from 'react'
import Status, { LOGO_STATUSES, type EmojiLogoStatus } from './Status'
import styles from './styles.module.css'
const Logo = () => {
const statuses = Object.values(LOGO_STATUSES)
const [status, setStatus] = useState<EmojiLogoStatus>(null)
useEffect(() => {
setStatus(statuses[Math.floor(Math.random() * statuses.length)])
}, [])
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={styles.logo}
height="137px"
width="457px"
version="1.1"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox="0 0 457 137"
>
<title>gitmoji</title>
<g id="gitmoji" fillRule="evenodd" fill="none">
<g id="Logo" transform="translate(-270 -430)">
<g id="gitmoji" transform="translate(270 430)">
<path
d="m17.5 106c0.6 4 2.4 7 5.3 10 2.9 2 6.6 4 11.2 4 6.1 0 10.7-2 14-5s4.9-8 4.9-15.1v-5.1c-1.5 2.3-3.8 4.4-7.1 6.2-3.2 2-7.5 3-12.6 3-4.7 0-9.1-1-13.1-3-4.1-1.5-7.6-3.9-10.5-7-2.91-3-5.22-6.7-6.9-10.8-1.68-4.2-2.52-8.8-2.52-13.8 0.004-4.7 0.82-9.1 2.45-13.3s3.89-7.9 6.77-11c2.9-3.2 6.3-5.7 10.4-7.5 4-1.8 8.5-2.7 13.4-2.7 5.6 0 10.1 1 13.4 3 3.2 2 5.5 4.2 6.7 6.6v-8.3h18.5v63.2c0 4.6-0.7 9.6-2.1 13.6-1.4 5-3.6 9-6.6 12-3 4-6.8 6-11.5 8s-10.4 4-17 4c-4.9 0-9.3-1-13.3-3-4-1-7.5-3-10.5-5-2.94-3-5.34-5-7.16-8-1.82-4-2.98-7-3.46-10l17.3-5zm19-18.6c4.9 0 8.9-1.6 12-4.9 3.1-3.2 4.7-7.6 4.7-13.1s-1.7-9.8-4.9-13c-3.3-3.2-7.2-4.8-11.8-4.8-2.4 0-4.7 0.4-6.7 1.2-2.1 0.8-3.9 2-5.4 3.6-1.6 1.6-2.8 3.5-3.6 5.7-0.9 2.2-1.3 4.6-1.3 7.3 0 5.6 1.6 10 4.7 13.2 3.2 3.2 7.3 4.8 12.3 4.8zm54.7 19.6v-70.8h18.8v70.8h-18.8zm-2.3-94c0-3.39 1.1-6.22 3.4-8.53 2.3-2.3 5.1-3.45 8.7-3.45 3 0 6 1.15 8 3.45 2 2.31 4 5.14 4 8.53 0 3.1-2 5.9-4 8.2s-5 3.4-8 3.4c-3.6 0-6.4-1.1-8.7-3.4s-3.4-5.1-3.4-8.2zm65.1 23.2h15v16.9h-15v29.7c0 3.1 1 5.2 2 6.4 2 1.2 4 1.8 7 1.8 1 0 2 0 3-0.1s2-0.3 3-0.5v15.6c-1 1-3 1-4 1-2 1-4 1-7 1-7 0-13-2-17-6-4-3.6-6-9-6-15.9v-33h-12v-16.9h3c4 0 7-1.1 8-3.3 2-2.1 3-4.8 3-8v-9.9h17v21.2zm30 70.8v-70.8h18v8.6c1-1.7 2-3.3 4-4.6 1-1.4 3-2.5 5-3.3 2-0.9 4-1.6 6-2 2-0.5 4-0.8 6-0.8 5 0 9 1 13 3.1 3 2 6 4.9 8 8.7 3-4.3 6-7.3 10-9.1s8-2.7 12-2.7 7 0.5 10 1.5c3 1.1 6 2.7 8 4.9 3 2.2 4 5 6 8.4 1 3.4 2 7.5 2 12.2v45.9h-19v-42c0-3.9 0-7.1-2-9.6-2-2.6-6-3.8-10-3.8s-7 1.3-10 4.1c-2 2.7-3 6-3 9.8v41.5h-19v-42c0-3.9-1-7.1-3-9.6-2-2.6-5-3.8-10-3.8-4 0-7 1.3-9 4-3 2.7-4 6-4 9.9v41.5h-19zm159-15.3c3 0 5-0.4 7-1.3 2-0.8 4-2.1 6-3.8 1-1.7 3-3.7 4-6.2s1-5.5 1-8.8c0-3.4 0-6.3-1-8.8s-3-4.6-4-6.3c-2-1.7-4-2.9-6-3.8s-4-1.3-7-1.3c-2 0-4 0.4-6 1.3-3 0.9-5 2.1-6 3.8-2 1.7-3 3.8-4 6.3s-2 5.4-2 8.8c0 3.3 1 6.3 2 8.8s2 4.5 4 6.2c1 1.7 3 3 6 3.8 2 0.9 4 1.3 6 1.3zm0-57.7c6 0 11 0.9 15 2.8 5 1.9 9 4.5 12 7.8s6 7.2 8 11.9c2 4.6 2 9.6 2 15.1 0 5.4 0 10.5-2 15-2 4.6-5 8.5-8 11.9-3 3.5-7 5.5-12 7.5-4 2-9 3-15 3-5 0-10-1-14-3-5-2-9-4-12-7.5-4-3.4-6-7.3-8-11.9-2-4.5-3-9.6-3-15 0-5.5 1-10.5 3-15.1 2-4.7 4-8.6 8-11.9 3-3.3 7-5.9 12-7.8 4-1.9 9-2.8 14-2.8zm53 2.2h19v76.8c0 4-1 7-2 10-1 2-2 5-4 7s-4 4-7 5-6 2-9 2-6-1-8-1c-2-1-3-1-3-1v-16h2c1 1 2 1 4 1 3 0 5-1 6-3 1-1 2-3 2-6v-74.8zm-3-23.4c0-3.34 1-6.17 4-8.47 2-2.31 5-3.46 8-3.46 3 0.002 6 1.15 9 3.46 2 2.3 3 5.13 3 8.47 0 3.3-1 6-3 8.3-3 2.3-6 3.4-9 3.4s-6-1.1-8-3.4c-3-2.3-4-5-4-8.3zm42 94.2v-70.8h19v70.8h-19zm-2-94c0-3.39 1-6.22 3-8.53 3-2.3 5-3.45 9-3.45 3 0 6 1.15 8 3.45 2 2.31 4 5.14 4 8.53 0 3.1-2 5.9-4 8.2s-5 3.4-8 3.4c-4 0-6-1.1-9-3.4-2-2.3-3-5.1-3-8.2z"
fill="#000"
/>
<Status status={status} />
</g>
</g>
</g>
</svg>
)
}
export default Logo
================================================
FILE: packages/website/src/components/Layout/Header/Logo/styles.module.css
================================================
.logo {
width: 100%;
height: 115px;
}
================================================
FILE: packages/website/src/components/Layout/Header/index.tsx
================================================
import Button from 'src/components/Button'
import Logo from './Logo'
import styles from './styles.module.css'
type Props = { withHeadline: boolean }
const Header = (props: Props) => (
<header className={styles.header}>
<Logo />
{props.withHeadline && (
<h2 className={styles.title}>An emoji guide for your commit messages</h2>
)}
<div className={styles.buttons}>
<Button
icon="star"
link="https://github.com/carloscuesta/gitmoji"
text="GitHub"
/>
<Button
icon="twitter-x"
link={
'https://twitter.com/intent/tweet?text=gitmoji' +
'%20%E2%80%93%20An%20%23emoji%20guide%20for%20your%20commit' +
'%20messages%20by%20%40crloscuesta%20%F0%9F%98%8D%F0%9F%98%9C' +
'&url=https://gitmoji.dev'
}
target="_blank"
text="Share"
/>
</div>
</header>
)
export default Header
================================================
FILE: packages/website/src/components/Layout/Header/styles.module.css
================================================
.header {
background-color: var(--primary);
padding: 4.5em 2em;
text-align: center;
}
.title {
padding: 0.5em 0;
margin: 0;
font-size: 2em;
color: var(--textInPrimary);
}
.buttons {
padding: 1em 0;
text-align: center;
}
.buttons a:first-child {
margin-right: 1em;
}
================================================
FILE: packages/website/src/components/Layout/__tests__/layout.spec.tsx
================================================
import { render, screen } from '@testing-library/react'
import Layout from '../index'
import Status, { LOGO_STATUSES } from '../Header/Logo/Status'
import * as stubs from './stubs'
jest.mock('next/navigation', () => ({
usePathname: jest.fn(() => '/'),
}))
describe('Layout', () => {
beforeAll(() => {
Math.random = jest.fn().mockReturnValue(1)
})
beforeEach(() => {
jest.clearAllMocks()
})
it('should render the component with children', () => {
render(
<Layout {...stubs.props}>
<p>Some children</p>
</Layout>,
)
expect(screen.getByText('Some children')).toBeInTheDocument()
})
describe('Logo', () => {
Object.values(LOGO_STATUSES)
.map((status) => status)
.forEach((status) => {
it('should render Logo with status ' + status, () => {
const { container } = render(<Status status={status} />)
expect(container.firstChild).toBeInTheDocument()
})
})
})
})
================================================
FILE: packages/website/src/components/Layout/__tests__/stubs.ts
================================================
export const props = {
headerWithSocialButtons: true,
}
================================================
FILE: packages/website/src/components/Layout/index.tsx
================================================
import { IconDefinitions } from 'src/components/Icon'
import Header from './Header'
import Hamburger from './Hamburger'
import Footer from './Footer'
type Props = { children: React.ReactNode }
const Layout = (props: Props) => (
<>
<IconDefinitions />
<Hamburger />
<Header withHeadline />
<main className="wrap">{props.children}</main>
<Footer />
</>
)
export default Layout
================================================
FILE: packages/website/src/utils/theme/theme.css
================================================
:root {
--background: #ffffff;
--primary: #ffdd67;
--primaryShadow: #ffcc1b;
--secondary: #ff5a79;
--secondaryShadow: #f3002e;
--textInPrimary: #000000;
--textInSecondary: #ffffff;
--footerBackground: #00e5ff;
--cardBackground: #ffffff;
--cardText: #999999;
--emojiCodeText: #000000;
--cardShadow: rgba(168, 182, 191, 0.6);
--notificationText: #ffffff;
--notificationShadow: rgba(0, 0, 0, 0.05);
--notificationEmojiCodeColor: rgba(0, 0, 0, 0.85);
--menuBackground: #ff5a79;
--carbonAdBadgeBackground: #f1f1f1;
}
[data-theme='dark'] {
--background: #121212;
--primary: #ffdd67;
--primaryShadow: #ffcc1b;
--secondary: #ff5a79;
--secondaryShadow: #f3002e;
--textInPrimary: #000000;
--textInSecondary: #ffffff;
--footerBackground: #00e5ff;
--cardBackground: #2b2b2b;
--cardText: #ffffff;
--emojiCodeText: #ffffff;
--cardShadow: none;
--notificationText: #ffffff;
--notificationShadow: rgba(0, 0, 0, 0.05);
--notificationEmojiCodeColor: rgba(0, 0, 0, 0.85);
--menuBackground: #ff5a79;
--carbonAdBadgeBackground: #2b2b2b;
}
[data-theme='dark'] body {
color: var(--cardText);
}
html,
body {
background-color: var(--background);
margin: 0;
padding: 0;
font-size: 16.5px;
font-family:
Avenir,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
'Roboto',
'Oxygen',
'Ubuntu',
'Cantarell',
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
}
h1 {
font-size: 2em;
}
a {
text-decoration: none;
color: var(--secondary);
}
a:hover {
animation: zomg 0.5s infinite;
}
@media (prefers-reduced-motion: reduce) {
a:hover {
animation: none;
text-decoration: underline;
}
}
code {
font-family:
Avenir,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
'Roboto',
'Oxygen',
'Ubuntu',
'Cantarell',
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-weight: 700;
font-size: 1.25em;
word-break: break-all;
}
section {
padding: 0.5em;
}
pre {
background-color: var(--primary);
border-radius: 4px;
box-shadow: 0 4px var(--primaryShadow);
color: var(--textInPrimary);
padding: 1em;
}
.overflow-hidden {
overflow: hidden;
}
.overflow-x-adjust {
overflow-x: auto;
}
.wrap {
max-width: 1100px;
margin: 0 auto;
}
main.wrap {
padding: 2em;
}
@keyframes zomg {
0%,
100% {
color: #7ccdea;
}
16% {
color: #0074d9;
}
32% {
color: #2ecc40;
}
48% {
color: #ffdc00;
}
64% {
color: #b10dc9;
}
80% {
color: #ff4136;
}
}
@media (min-width: 2048px) {
html,
body {
font-size: 19px;
}
}
/* Flexboxgrid critical */
.col-sm-2,
.col-xs-12,
.col-xs-3,
.row {
box-sizing: border-box;
}
.container {
margin-right: auto;
margin-left: auto;
}
.row {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-flex: 0;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.col-xs-12,
.col-xs-3 {
-webkit-box-flex: 0;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
padding-right: 1.25rem;
padding-left: 1.25rem;
}
.col-xs-12 {
-webkit-flex-basis: 100%;
-ms-flex-preferred-size: 100%;
flex-basis: 100%;
max-width: 100%;
}
.col-xs-3 {
-ms-flex-preferred-size: 25%;
flex-basis: 25%;
max-width: 25%;
}
.center-xs {
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
text-align: center;
}
.middle-xs {
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
}
@media only screen and (min-width: 48em) {
.container {
width: 49rem;
}
.col-sm-2,
.col-sm-6 {
box-sizing: border-box;
-webkit-box-flex: 0;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
padding-right: 1.25rem;
padding-left: 1.25rem;
}
.col-sm-6 {
-webkit-flex-basis: 50%;
-ms-flex-preferred-size: 50%;
flex-basis: 50%;
max-width: 50%;
}
.col-sm-2 {
-ms-flex-preferred-size: 16.66666667%;
flex-basis: 16.66666667%;
max-width: 16.66666667%;
}
}
@media only screen and (min-width: 64em) {
.container {
width: 65rem;
}
.col-md-3 {
box-sizing: border-box;
-webkit-box-flex: 0;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
padding-right: 1.25rem;
padding-left: 1.25rem;
-ms-flex-preferred-size: 25%;
flex-basis: 25%;
max-width: 25%;
}
.col-md-4 {
-ms-flex-preferred-size: 33.33333333%;
flex-basis: 33.33333333%;
max-width: 33.33333333%;
}
}
================================================
FILE: packages/website/tsconfig.json
================================================
{
"compilerOptions": {
"baseUrl": ".",
"target": "es6",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
================================================
FILE: pnpm-workspace.yaml
================================================
packages:
- 'packages/*'
================================================
FILE: turbo.json
================================================
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"dev": {
"dependsOn": ["^build"],
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^build"],
"outputs": []
},
"tscheck": {
"dependsOn": ["^build"],
"outputs": []
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"]
},
"build": {
"outputs": [".next/**", "public/**", "dist/**"],
"dependsOn": ["^build"]
},
"publishPackage": {
"dependsOn": ["^lint"],
"outputs": []
}
}
}
gitextract_os7qfgki/ ├── .editorconfig ├── .github/ │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ ├── config.yml │ │ ├── discussion.yml │ │ ├── feature-request.yml │ │ └── gitmoji-proposal.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yml │ ├── lock.yml │ └── npm-publish.yml ├── .gitignore ├── .husky/ │ ├── .gitignore │ ├── pre-commit │ └── pre-push ├── .lintstagedrc.json ├── .node-version ├── AGENTS.md ├── LICENSE ├── README.md ├── package.json ├── packages/ │ ├── gitmojis/ │ │ ├── .lintstagedrc.json │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ ├── gitmojis.json │ │ ├── index.d.ts │ │ ├── index.js │ │ └── schema.json │ └── website/ │ ├── .lintstagedrc.json │ ├── __mocks__/ │ │ └── svg.js │ ├── eslint.config.mjs │ ├── jest.config.js │ ├── jest.d.ts │ ├── jest.setup.js │ ├── jsconfig.json │ ├── next-env.d.ts │ ├── next-sitemap.config.js │ ├── next-sitemap.js │ ├── next.config.js │ ├── package.json │ ├── public/ │ │ ├── _redirects │ │ └── static/ │ │ ├── browserconfig.xml │ │ ├── manifest.json │ │ └── opensearchdescription.xml │ ├── scripts/ │ │ └── generate-api.js │ ├── src/ │ │ ├── __tests__/ │ │ │ ├── pages.spec.tsx │ │ │ └── stubs.tsx │ │ ├── app/ │ │ │ ├── about/ │ │ │ │ └── page.tsx │ │ │ ├── contributors/ │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ ├── related-tools/ │ │ │ │ └── page.tsx │ │ │ └── specification/ │ │ │ └── page.tsx │ │ ├── components/ │ │ │ ├── Button/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── button.spec.tsx │ │ │ │ │ └── stubs.ts │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── CarbonAd/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── carbonAd.spec.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── ContributorsList/ │ │ │ │ ├── Contributor/ │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.module.css │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── contributorsList.spec.tsx │ │ │ │ │ └── stubs.ts │ │ │ │ └── index.tsx │ │ │ ├── GitmojiList/ │ │ │ │ ├── Gitmoji/ │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.module.css │ │ │ │ ├── SearchParamsSync.tsx │ │ │ │ ├── Toolbar/ │ │ │ │ │ ├── Kbd/ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── styles.module.css │ │ │ │ │ ├── ListModeSelector/ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── styles.module.css │ │ │ │ │ ├── ThemeSelector/ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── styles.module.css │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.module.css │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── gitmojiList.spec.tsx │ │ │ │ │ └── stubs.ts │ │ │ │ ├── emojiColorsMap.ts │ │ │ │ ├── hooks/ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ ├── stubs.ts │ │ │ │ │ │ └── useLocalStorage.spec.tsx │ │ │ │ │ └── useLocalStorage.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── Icon/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── icon.spec.tsx │ │ │ │ │ └── stubs.ts │ │ │ │ ├── definitions.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ └── Layout/ │ │ │ ├── Footer/ │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── Hamburger/ │ │ │ │ ├── CloseIcon/ │ │ │ │ │ └── index.tsx │ │ │ │ ├── MenuLink/ │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.module.css │ │ │ │ ├── OpenIcon/ │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── Header/ │ │ │ │ ├── Logo/ │ │ │ │ │ ├── Status/ │ │ │ │ │ │ ├── Joy/ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── Loved/ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── Sexy/ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── Smiling/ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── Sunglasses/ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── Tongue/ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── styles.module.css │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.module.css │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── __tests__/ │ │ │ │ ├── layout.spec.tsx │ │ │ │ └── stubs.ts │ │ │ └── index.tsx │ │ └── utils/ │ │ └── theme/ │ │ └── theme.css │ └── tsconfig.json ├── pnpm-workspace.yaml └── turbo.json
SYMBOL INDEX (28 symbols across 22 files)
FILE: packages/gitmojis/src/index.d.ts
type Gitmoji (line 2) | type Gitmoji = {
FILE: packages/website/jest.config.js
function jestConfig (line 5) | async function jestConfig() {
FILE: packages/website/src/app/about/page.tsx
function About (line 13) | function About() {
FILE: packages/website/src/app/contributors/page.tsx
type Contributor (line 13) | type Contributor = {
type GitHubContributor (line 19) | type GitHubContributor = {
function getContributors (line 26) | async function getContributors(): Promise<Contributor[]> {
function Contributors (line 42) | async function Contributors() {
FILE: packages/website/src/app/layout.tsx
function RootLayout (line 66) | function RootLayout({
FILE: packages/website/src/app/page.tsx
function Home (line 7) | function Home() {
FILE: packages/website/src/app/related-tools/page.tsx
function RelatedTools (line 108) | function RelatedTools() {
FILE: packages/website/src/app/specification/page.tsx
function Specification (line 13) | function Specification() {
FILE: packages/website/src/components/Button/index.tsx
type Props (line 4) | type Props = { target?: string; icon?: string; text: string; link: string }
FILE: packages/website/src/components/ContributorsList/Contributor/index.tsx
type Props (line 3) | type Props = { avatar: string; url: string }
FILE: packages/website/src/components/ContributorsList/index.tsx
type Props (line 3) | type Props = {
FILE: packages/website/src/components/GitmojiList/Gitmoji/index.tsx
type Props (line 4) | type Props = {
FILE: packages/website/src/components/GitmojiList/SearchParamsSync.tsx
type Props (line 6) | type Props = {
function SearchParamsSync (line 15) | function SearchParamsSync({
FILE: packages/website/src/components/GitmojiList/Toolbar/ListModeSelector/index.tsx
type Props (line 4) | type Props = {
FILE: packages/website/src/components/GitmojiList/Toolbar/index.tsx
type Props (line 11) | type Props = {
FILE: packages/website/src/components/GitmojiList/hooks/useLocalStorage.tsx
function useLocalStorage (line 3) | function useLocalStorage<T>(key: string, defaultValue: T) {
FILE: packages/website/src/components/GitmojiList/index.tsx
type Props (line 14) | type Props = {
FILE: packages/website/src/components/Icon/index.tsx
type Props (line 4) | type Props = { name: string }
FILE: packages/website/src/components/Layout/Hamburger/MenuLink/index.tsx
type Props (line 8) | type Props = { href: string; text: string }
FILE: packages/website/src/components/Layout/Header/Logo/Status/index.tsx
constant LOGO_STATUSES (line 8) | const LOGO_STATUSES = {
type EmojiLogoStatus (line 17) | type EmojiLogoStatus = keyof typeof LOGO_STATUSES | null
type Props (line 19) | type Props = { status: EmojiLogoStatus }
FILE: packages/website/src/components/Layout/Header/index.tsx
type Props (line 5) | type Props = { withHeadline: boolean }
FILE: packages/website/src/components/Layout/index.tsx
type Props (line 6) | type Props = { children: React.ReactNode }
Condensed preview — 118 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (153K chars).
[
{
"path": ".editorconfig",
"chars": 316,
"preview": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = spaces\ntab_width = 2\ncharset = utf-8\ntrim_trailing_whitespace "
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 2405,
"preview": "# Contributing to gitmoji\n\nHello!\n\nThanks for contributing on [gitmoji](https://github.com/carloscuesta/gitmoji). Before"
},
{
"path": ".github/FUNDING.yml",
"chars": 115,
"preview": "# These are supported funding model platforms\n\ngithub: ['carloscuesta']\ncustom: ['https://paypal.me/carloscuesta']\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug-report.yml",
"chars": 1973,
"preview": "name: 🐛 Bug report\ndescription: Report an issue\nbody:\n - type: markdown\n attributes:\n value: |\n Thanks f"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 245,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: 😍 Contribution Guide\n url: https://github.com/carloscuesta/gitmo"
},
{
"path": ".github/ISSUE_TEMPLATE/discussion.yml",
"chars": 1027,
"preview": "name: ⁉️ Discussion\ndescription: Want to discuss something? Use this template\nlabels: [discussion]\nbody:\n - type: markd"
},
{
"path": ".github/ISSUE_TEMPLATE/feature-request.yml",
"chars": 1640,
"preview": "name: 🚀 Feature proposal\ndescription: Propose a new feature\nlabels: [feature]\nbody:\n - type: markdown\n attributes:\n "
},
{
"path": ".github/ISSUE_TEMPLATE/gitmoji-proposal.yml",
"chars": 3371,
"preview": "name: 😜 Gitmoji proposal\ndescription: Suggest a new gitmoji!\nlabels: [emoji]\nbody:\n - type: markdown\n attributes:\n "
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 377,
"preview": "<!--\nPlease, before opening a PR, first open an issue as stated in the [contributing guidelines][1],\nso we can talk abou"
},
{
"path": ".github/dependabot.yml",
"chars": 351,
"preview": "version: 2\nupdates:\n- package-ecosystem: npm\n directory: \"/\"\n schedule:\n interval: monthly\n time: \"04:00\"\n open"
},
{
"path": ".github/workflows/ci.yml",
"chars": 679,
"preview": "name: CI\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\njobs:\n ci:\n runs-on: ubuntu-lates"
},
{
"path": ".github/workflows/lock.yml",
"chars": 281,
"preview": "name: Lock Issues and PRs\non:\n schedule:\n - cron: '0 0 * * *'\njobs:\n lock:\n runs-on: ubuntu-latest\n steps:\n "
},
{
"path": ".github/workflows/npm-publish.yml",
"chars": 749,
"preview": "name: NPM Publish\non:\n push:\n tags:\n - \"v*\"\npermissions:\n id-token: write\n contents: write\njobs:\n npm-publis"
},
{
"path": ".gitignore",
"chars": 465,
"preview": ".DS_Store\ndist/\nnode_modules/\n.publish/\n.next\nout/\ncoverage/\n.eslintcache\n*.log\n.pnp.*\n\n# next-pwa\npackages/website/publ"
},
{
"path": ".husky/.gitignore",
"chars": 2,
"preview": "_\n"
},
{
"path": ".husky/pre-commit",
"chars": 22,
"preview": "pnpm exec lint-staged\n"
},
{
"path": ".husky/pre-push",
"chars": 38,
"preview": "pnpm turbo tscheck && pnpm turbo test\n"
},
{
"path": ".lintstagedrc.json",
"chars": 102,
"preview": "{\n \"*.json\": [\"prettier --write\"],\n \"*.md\": [\"prettier --write\"],\n \"*.yml\": [\"prettier --write\"]\n}\n"
},
{
"path": ".node-version",
"chars": 3,
"preview": "24\n"
},
{
"path": "AGENTS.md",
"chars": 2431,
"preview": "# Gitmoji Guide for AI Assistants\n\n## Purpose\n\nThis guide helps AI assistants understand and use gitmoji convention when"
},
{
"path": "LICENSE",
"chars": 1075,
"preview": "MIT License\n\nCopyright (c) 2016-2022 Carlos Cuesta\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "README.md",
"chars": 2708,
"preview": "<p align=\"center\">\n\t<a href=\"https://gitmoji.dev\">\n\t\t<img src=\"https://cloud.githubusercontent.com/assets/7629661/200731"
},
{
"path": "package.json",
"chars": 352,
"preview": "{\n \"name\": \"gitmoji\",\n \"private\": true,\n \"engines\": {\n \"node\": \"22\",\n \"pnpm\": \">=8\"\n },\n \"scripts\": {\n \"pr"
},
{
"path": "packages/gitmojis/.lintstagedrc.json",
"chars": 154,
"preview": "{\n \"./src/*.json\": [\"prettier --write ./src/*.json\"],\n \"./src/*.ts\": [\"prettier --write ./src/*.ts\"],\n \"./src/*.js\": "
},
{
"path": "packages/gitmojis/README.md",
"chars": 1596,
"preview": "<p align=\"center\">\n\t<a href=\"https://gitmoji.dev\">\n\t\t<img src=\"https://cloud.githubusercontent.com/assets/7629661/200731"
},
{
"path": "packages/gitmojis/package.json",
"chars": 1330,
"preview": "{\n \"name\": \"gitmojis\",\n \"type\": \"module\",\n \"version\": \"3.15.0\",\n \"description\": \"An emoji guide for your commit mess"
},
{
"path": "packages/gitmojis/src/gitmojis.json",
"chars": 14662,
"preview": "{\n \"$schema\": \"https://gitmoji.dev/api/gitmojis/schema\",\n \"gitmojis\": [\n {\n \"emoji\": \"🎨\",\n \"entity\": \"&#x"
},
{
"path": "packages/gitmojis/src/index.d.ts",
"chars": 878,
"preview": "declare module 'gitmojis' {\n type Gitmoji = {\n /**\n * Gitmoji unicode character\n * @example '🎨', '⚡️', '🔥', "
},
{
"path": "packages/gitmojis/src/index.js",
"chars": 189,
"preview": "import gitmojisJson from './gitmojis.json' assert { type: 'json' }\n\nexport { default as schema } from './schema.json' as"
},
{
"path": "packages/gitmojis/src/schema.json",
"chars": 875,
"preview": "{\n \"type\": \"object\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"required\": [\"gitmojis\"],\n \"proper"
},
{
"path": "packages/website/.lintstagedrc.json",
"chars": 114,
"preview": "{\n \"./src/**/*.{ts,tsx,css}\": [\n \"eslint --cache --fix\",\n \"prettier --write ./src/**/*.{ts,tsx,css}\"\n ]\n}\n"
},
{
"path": "packages/website/__mocks__/svg.js",
"chars": 28,
"preview": "module.exports = 'svg-mock'\n"
},
{
"path": "packages/website/eslint.config.mjs",
"chars": 2427,
"preview": "import { FlatCompat } from '@eslint/eslintrc'\nimport js from '@eslint/js'\nimport typescriptParser from '@typescript-esli"
},
{
"path": "packages/website/jest.config.js",
"chars": 860,
"preview": "const nextJest = require('next/jest')\n\nconst createJestConfig = nextJest({ dir: './' })\n\nasync function jestConfig() {\n "
},
{
"path": "packages/website/jest.d.ts",
"chars": 52,
"preview": "/// <reference types=\"@testing-library/jest-dom\" />\n"
},
{
"path": "packages/website/jest.setup.js",
"chars": 2642,
"preview": "import '@testing-library/jest-dom'\nimport React from 'react'\n\n// Mock next/dynamic to load components synchronously in t"
},
{
"path": "packages/website/jsconfig.json",
"chars": 50,
"preview": "{\n \"compilerOptions\": {\n \"baseUrl\": \".\"\n }\n}\n"
},
{
"path": "packages/website/next-env.d.ts",
"chars": 247,
"preview": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\nimport \"./.next/types/routes.d.ts\";\n\n//"
},
{
"path": "packages/website/next-sitemap.config.js",
"chars": 128,
"preview": "/** @type {import('next-sitemap').IConfig} */\nmodule.exports = {\n siteUrl: 'https://gitmoji.dev',\n generateRobotsTxt: "
},
{
"path": "packages/website/next-sitemap.js",
"chars": 82,
"preview": "module.exports = {\n siteUrl: 'https://gitmoji.dev',\n generateRobotsTxt: true,\n}\n"
},
{
"path": "packages/website/next.config.js",
"chars": 66,
"preview": "module.exports = {\n reactStrictMode: true,\n output: 'export',\n}\n"
},
{
"path": "packages/website/package.json",
"chars": 2204,
"preview": "{\n \"name\": \"website\",\n \"private\": true,\n \"version\": \"1.0.0\",\n \"engines\": {\n \"node\": \"24\"\n },\n \"scripts\": {\n "
},
{
"path": "packages/website/public/_redirects",
"chars": 45,
"preview": "/api/gitmojis /api/gitmojis/index.json 200\n"
},
{
"path": "packages/website/public/static/browserconfig.xml",
"chars": 303,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig><msapplication><tile><square70x70logo src=\"/static/ms-icon-70x70.p"
},
{
"path": "packages/website/public/static/manifest.json",
"chars": 1233,
"preview": "{\n \"name\": \"Gitmoji\",\n \"display\": \"minimal-ui\",\n \"start_url\": \"/\",\n \"theme_color\": \"#FFDD67\",\n \"background_color\": "
},
{
"path": "packages/website/public/static/opensearchdescription.xml",
"chars": 654,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\">\n <ShortName"
},
{
"path": "packages/website/scripts/generate-api.js",
"chars": 375,
"preview": "const { gitmojis } = require('gitmojis')\nconst fs = require('fs')\nconst path = require('path')\n\nconst outputDir = path.j"
},
{
"path": "packages/website/src/__tests__/pages.spec.tsx",
"chars": 1903,
"preview": "import { render, screen } from '@testing-library/react'\n\nimport RootLayout from '../app/layout'\nimport Home from '../app"
},
{
"path": "packages/website/src/__tests__/stubs.tsx",
"chars": 464,
"preview": "export const appProps = {\n Component: (props: object) => <div {...props}>Component</div>,\n pageProps: { test: '' },\n}\n"
},
{
"path": "packages/website/src/app/about/page.tsx",
"chars": 3707,
"preview": "import type { Metadata } from 'next'\nimport Link from 'next/link'\n\nimport CarbonAd from 'src/components/CarbonAd'\n\nexpor"
},
{
"path": "packages/website/src/app/contributors/page.tsx",
"chars": 1256,
"preview": "import type { Metadata } from 'next'\n\nimport ContributorsList from 'src/components/ContributorsList'\nimport CarbonAd fro"
},
{
"path": "packages/website/src/app/layout.tsx",
"chars": 3382,
"preview": "import type { Metadata } from 'next'\nimport { ThemeProvider } from 'next-themes'\n\nimport Layout from 'src/components/Lay"
},
{
"path": "packages/website/src/app/page.tsx",
"chars": 352,
"preview": "import { Toaster } from 'react-hot-toast'\nimport { gitmojis } from 'gitmojis'\n\nimport GitmojiList from 'src/components/G"
},
{
"path": "packages/website/src/app/related-tools/page.tsx",
"chars": 3788,
"preview": "import type { Metadata } from 'next'\n\nimport CarbonAd from 'src/components/CarbonAd'\n\nexport const metadata: Metadata = "
},
{
"path": "packages/website/src/app/specification/page.tsx",
"chars": 3770,
"preview": "import type { Metadata } from 'next'\nimport Link from 'next/link'\n\nimport CarbonAd from 'src/components/CarbonAd'\n\nexpor"
},
{
"path": "packages/website/src/components/Button/__tests__/button.spec.tsx",
"chars": 899,
"preview": "import { render, screen } from '@testing-library/react'\n\nimport Button from '../index'\nimport * as stubs from './stubs'\n"
},
{
"path": "packages/website/src/components/Button/__tests__/stubs.ts",
"chars": 92,
"preview": "export const props = {\n target: '_blank',\n icon: 'star',\n text: 'GitHub',\n link: '/',\n}\n"
},
{
"path": "packages/website/src/components/Button/index.tsx",
"chars": 392,
"preview": "import Icon from 'src/components/Icon'\nimport styles from './styles.module.css'\n\ntype Props = { target?: string; icon?: "
},
{
"path": "packages/website/src/components/Button/styles.module.css",
"chars": 460,
"preview": ".button {\n border-radius: 4px;\n cursor: pointer;\n display: inline-block;\n font-weight: 600;\n margin: 0.25em 0;\n pa"
},
{
"path": "packages/website/src/components/CarbonAd/__tests__/carbonAd.spec.tsx",
"chars": 489,
"preview": "import { render, waitFor } from '@testing-library/react'\n\nimport CarbonAd from '../index'\nimport styles from '../styles."
},
{
"path": "packages/website/src/components/CarbonAd/index.tsx",
"chars": 1091,
"preview": "'use client'\n\nimport { useRef, useEffect } from 'react'\n\nimport styles from './styles.module.css'\n\nconst CarbonAd = () ="
},
{
"path": "packages/website/src/components/CarbonAd/styles.module.css",
"chars": 1088,
"preview": ".carbonContainer {\n height: 100px;\n}\n\n:global(#carbonads) {\n display: block;\n overflow: hidden;\n max-width: 728px;\n "
},
{
"path": "packages/website/src/components/ContributorsList/Contributor/index.tsx",
"chars": 340,
"preview": "import styles from './styles.module.css'\n\ntype Props = { avatar: string; url: string }\n\nconst Contributor = (props: Prop"
},
{
"path": "packages/website/src/components/ContributorsList/Contributor/styles.module.css",
"chars": 72,
"preview": ".picture {\n max-width: 100%;\n border-radius: 50%;\n padding: 0.5em;\n}\n"
},
{
"path": "packages/website/src/components/ContributorsList/__tests__/contributorsList.spec.tsx",
"chars": 1130,
"preview": "import { render } from '@testing-library/react'\n\nimport ContributorsList from '../index'\nimport Contributor from '../Con"
},
{
"path": "packages/website/src/components/ContributorsList/__tests__/stubs.ts",
"chars": 192,
"preview": "export const contributor = {\n url: 'https://github.com/profile',\n avatar: 'https://github.com/avatar',\n id: 'contribu"
},
{
"path": "packages/website/src/components/ContributorsList/index.tsx",
"chars": 439,
"preview": "import Contributor from './Contributor'\n\ntype Props = {\n contributors: Array<{\n avatar: string\n id: string\n ur"
},
{
"path": "packages/website/src/components/GitmojiList/Gitmoji/index.tsx",
"chars": 1810,
"preview": "import emojiColorsMap from '../emojiColorsMap'\nimport styles from './styles.module.css'\n\ntype Props = {\n code: string\n "
},
{
"path": "packages/website/src/components/GitmojiList/Gitmoji/styles.module.css",
"chars": 2809,
"preview": ".emoji {\n display: flex;\n box-sizing: border-box;\n}\n\n.card {\n background-color: var(--cardBackground);\n border-radiu"
},
{
"path": "packages/website/src/components/GitmojiList/SearchParamsSync.tsx",
"chars": 868,
"preview": "'use client'\n\nimport { useEffect } from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\n\ntype Props"
},
{
"path": "packages/website/src/components/GitmojiList/Toolbar/Kbd/index.tsx",
"chars": 295,
"preview": "import styles from './styles.module.css'\n\nconst isMacOs = () => {\n return (\n typeof window !== 'undefined' &&\n wi"
},
{
"path": "packages/website/src/components/GitmojiList/Toolbar/Kbd/styles.module.css",
"chars": 315,
"preview": ".kbd {\n right: 0;\n align-items: center;\n border-radius: 3px;\n border: solid 1px #999;\n color: #595959;\n display: f"
},
{
"path": "packages/website/src/components/GitmojiList/Toolbar/ListModeSelector/index.tsx",
"chars": 780,
"preview": "import Icon from 'src/components/Icon'\nimport styles from './styles.module.css'\n\ntype Props = {\n isListMode: boolean\n "
},
{
"path": "packages/website/src/components/GitmojiList/Toolbar/ListModeSelector/styles.module.css",
"chars": 420,
"preview": ".container {\n display: flex;\n}\n\n.button {\n align-items: center;\n border-radius: 4px;\n border: none;\n color: var(--t"
},
{
"path": "packages/website/src/components/GitmojiList/Toolbar/ThemeSelector/index.tsx",
"chars": 749,
"preview": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport { useTheme } from 'next-themes'\n\nimport Icon from 'src/"
},
{
"path": "packages/website/src/components/GitmojiList/Toolbar/ThemeSelector/styles.module.css",
"chars": 408,
"preview": ".button {\n display: flex;\n align-items: center;\n border-radius: 4px;\n border: none;\n color: var(--text);\n cursor: "
},
{
"path": "packages/website/src/components/GitmojiList/Toolbar/index.tsx",
"chars": 1774,
"preview": "'use client'\n\nimport { useEffect, useRef } from 'react'\nimport dynamic from 'next/dynamic'\n\nimport ListModeSelector from"
},
{
"path": "packages/website/src/components/GitmojiList/Toolbar/styles.module.css",
"chars": 900,
"preview": ".container {\n align-items: center;\n display: flex;\n flex-direction: row;\n margin-bottom: 0.5rem;\n margin-top: 1.5re"
},
{
"path": "packages/website/src/components/GitmojiList/__tests__/gitmojiList.spec.tsx",
"chars": 3706,
"preview": "import { useRouter, useSearchParams } from 'next/navigation'\nimport { render, screen, fireEvent } from '@testing-library"
},
{
"path": "packages/website/src/components/GitmojiList/__tests__/stubs.ts",
"chars": 523,
"preview": "import { gitmojis } from 'gitmojis'\n\nexport const props = {\n gitmojis: gitmojis.slice(0, 6),\n}\n\nexport const routerMock"
},
{
"path": "packages/website/src/components/GitmojiList/emojiColorsMap.ts",
"chars": 1987,
"preview": "export default {\n 'adhesive-bandage': '#fbcfb7',\n alembic: '#7f39fb',\n alien: '#c5e763',\n ambulance: '#fb584a',\n di"
},
{
"path": "packages/website/src/components/GitmojiList/hooks/__tests__/stubs.ts",
"chars": 90,
"preview": "export const localStorageMock = {\n key: 'gitmojiTestKey',\n value: 'gitmojiTestValue',\n}\n"
},
{
"path": "packages/website/src/components/GitmojiList/hooks/__tests__/useLocalStorage.spec.tsx",
"chars": 2207,
"preview": "import { render } from '@testing-library/react'\n\nimport useLocalStorage from '../useLocalStorage'\nimport * as stubs from"
},
{
"path": "packages/website/src/components/GitmojiList/hooks/useLocalStorage.tsx",
"chars": 577,
"preview": "import { useState, useEffect } from 'react'\n\nexport default function useLocalStorage<T>(key: string, defaultValue: T) {\n"
},
{
"path": "packages/website/src/components/GitmojiList/index.tsx",
"chars": 2869,
"preview": "'use client'\n\nimport { Suspense, useEffect, useState } from 'react'\nimport Clipboard from 'clipboard'\nimport type { Gitm"
},
{
"path": "packages/website/src/components/GitmojiList/styles.module.css",
"chars": 724,
"preview": ".gitmojiCode {\n padding: 0 4px;\n border-radius: 4px;\n background-color: var(--notificationEmojiCodeColor);\n color: v"
},
{
"path": "packages/website/src/components/Icon/__tests__/icon.spec.tsx",
"chars": 890,
"preview": "import { render } from '@testing-library/react'\n\nimport Icon, { IconDefinitions } from '../index'\nimport * as stubs from"
},
{
"path": "packages/website/src/components/Icon/__tests__/stubs.ts",
"chars": 41,
"preview": "export const props = {\n name: 'star',\n}\n"
},
{
"path": "packages/website/src/components/Icon/definitions.tsx",
"chars": 5884,
"preview": "export const IconDefinitions = () => (\n <svg\n style={{ position: 'absolute', width: 0, height: 0 }}\n width={0}\n "
},
{
"path": "packages/website/src/components/Icon/index.tsx",
"chars": 288,
"preview": "export { IconDefinitions } from './definitions'\nimport styles from './styles.module.css'\n\ntype Props = { name: string }\n"
},
{
"path": "packages/website/src/components/Icon/styles.module.css",
"chars": 107,
"preview": ".icon {\n width: 1em;\n height: 1em;\n margin-right: 0.25em;\n}\n\n.icon:global(.icon-heart) {\n margin: 0;\n}\n"
},
{
"path": "packages/website/src/components/Layout/Footer/index.tsx",
"chars": 801,
"preview": "import Link from 'next/link'\n\nimport Icon from 'src/components/Icon'\nimport styles from './styles.module.css'\n\nconst Foo"
},
{
"path": "packages/website/src/components/Layout/Footer/styles.module.css",
"chars": 569,
"preview": ".footer {\n padding: 1.5em;\n background-color: var(--footerBackground);\n color: var(--textInSecondary);\n}\n\n.footerNav "
},
{
"path": "packages/website/src/components/Layout/Hamburger/CloseIcon/index.tsx",
"chars": 353,
"preview": "const CloseIcon = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"48\"\n height=\"48\"\n fill=\"current"
},
{
"path": "packages/website/src/components/Layout/Hamburger/MenuLink/index.tsx",
"chars": 728,
"preview": "'use client'\n\nimport Link from 'next/link'\nimport { usePathname } from 'next/navigation'\n\nimport styles from './styles.m"
},
{
"path": "packages/website/src/components/Layout/Hamburger/MenuLink/styles.module.css",
"chars": 172,
"preview": ".link {\n color: var(--text);\n text-decoration: none;\n font-weight: bold;\n}\n\n.link:hover {\n text-decoration: underlin"
},
{
"path": "packages/website/src/components/Layout/Hamburger/OpenIcon/index.tsx",
"chars": 254,
"preview": "const OpenIcon = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"48\"\n height=\"48\"\n fill=\"currentC"
},
{
"path": "packages/website/src/components/Layout/Hamburger/index.tsx",
"chars": 1931,
"preview": "'use client'\n\nimport { useState, useEffect } from 'react'\nimport { usePathname } from 'next/navigation'\nimport FocusTrap"
},
{
"path": "packages/website/src/components/Layout/Hamburger/styles.module.css",
"chars": 752,
"preview": ".hamburger {\n position: fixed;\n right: 0;\n z-index: 1;\n}\n\n.menu {\n background: var(--menuBackground);\n bottom: 0;\n "
},
{
"path": "packages/website/src/components/Layout/Header/Logo/Status/Joy/index.tsx",
"chars": 1658,
"preview": "export const Joy = () => (\n <g id=\"joy\" transform=\"translate(304 32)\">\n <g id=\"Group\">\n <circle id=\"Oval\" cy={3"
},
{
"path": "packages/website/src/components/Layout/Header/Logo/Status/Loved/index.tsx",
"chars": 1327,
"preview": "export const Loved = () => (\n <g id=\"loved\" transform=\"translate(304 32)\">\n <g id=\"Group\">\n <path\n id=\"S"
},
{
"path": "packages/website/src/components/Layout/Header/Logo/Status/Sexy/index.tsx",
"chars": 1524,
"preview": "export const Sexy = () => (\n <g id=\"sexy\" transform=\"translate(304 32)\">\n <g id=\"Group\">\n <ellipse id=\"Oval\" rx"
},
{
"path": "packages/website/src/components/Layout/Header/Logo/Status/Smiling/index.tsx",
"chars": 1584,
"preview": "export const Smiling = () => (\n <g id=\"haha\" transform=\"translate(304 32)\">\n <g id=\"Group\">\n <path\n id=\""
},
{
"path": "packages/website/src/components/Layout/Header/Logo/Status/Sunglasses/index.tsx",
"chars": 1118,
"preview": "export const Sunglasses = () => (\n <g id=\"sunglasses\" transform=\"translate(304 32)\">\n <g id=\"Group\">\n <path\n "
},
{
"path": "packages/website/src/components/Layout/Header/Logo/Status/Tongue/index.tsx",
"chars": 1153,
"preview": "export const Tongue = () => (\n <g id=\"tongue\" transform=\"translate(304 32)\">\n <g id=\"Group\">\n <ellipse id=\"Oval"
},
{
"path": "packages/website/src/components/Layout/Header/Logo/Status/index.tsx",
"chars": 896,
"preview": "import Joy from './Joy'\nimport Loved from './Loved'\nimport Sexy from './Sexy'\nimport Smiling from './Smiling'\nimport Sun"
},
{
"path": "packages/website/src/components/Layout/Header/Logo/Status/styles.module.css",
"chars": 0,
"preview": ""
},
{
"path": "packages/website/src/components/Layout/Header/Logo/index.tsx",
"chars": 3623,
"preview": "'use client'\n\nimport { useState, useEffect } from 'react'\n\nimport Status, { LOGO_STATUSES, type EmojiLogoStatus } from '"
},
{
"path": "packages/website/src/components/Layout/Header/Logo/styles.module.css",
"chars": 42,
"preview": ".logo {\n width: 100%;\n height: 115px;\n}\n"
},
{
"path": "packages/website/src/components/Layout/Header/index.tsx",
"chars": 920,
"preview": "import Button from 'src/components/Button'\nimport Logo from './Logo'\nimport styles from './styles.module.css'\n\ntype Prop"
},
{
"path": "packages/website/src/components/Layout/Header/styles.module.css",
"chars": 289,
"preview": ".header {\n background-color: var(--primary);\n padding: 4.5em 2em;\n text-align: center;\n}\n\n.title {\n padding: 0.5em 0"
},
{
"path": "packages/website/src/components/Layout/__tests__/layout.spec.tsx",
"chars": 978,
"preview": "import { render, screen } from '@testing-library/react'\n\nimport Layout from '../index'\nimport Status, { LOGO_STATUSES } "
},
{
"path": "packages/website/src/components/Layout/__tests__/stubs.ts",
"chars": 58,
"preview": "export const props = {\n headerWithSocialButtons: true,\n}\n"
},
{
"path": "packages/website/src/components/Layout/index.tsx",
"chars": 402,
"preview": "import { IconDefinitions } from 'src/components/Icon'\nimport Header from './Header'\nimport Hamburger from './Hamburger'\n"
},
{
"path": "packages/website/src/utils/theme/theme.css",
"chars": 4916,
"preview": ":root {\n --background: #ffffff;\n --primary: #ffdd67;\n --primaryShadow: #ffcc1b;\n --secondary: #ff5a79;\n --secondary"
},
{
"path": "packages/website/tsconfig.json",
"chars": 699,
"preview": "{\n \"compilerOptions\": {\n \"baseUrl\": \".\",\n \"target\": \"es6\",\n \"lib\": [\n \"dom\",\n \"dom.iterable\",\n "
},
{
"path": "pnpm-workspace.yaml",
"chars": 27,
"preview": "packages:\n - 'packages/*'\n"
},
{
"path": "turbo.json",
"chars": 586,
"preview": "{\n \"$schema\": \"https://turbo.build/schema.json\",\n \"tasks\": {\n \"dev\": {\n \"dependsOn\": [\"^build\"],\n \"cache\""
}
]
About this extraction
This page contains the full source code of the carloscuesta/gitmoji GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 118 files (132.4 KB), approximately 48.3k tokens, and a symbol index with 28 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.