diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..2eeb8cb --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,15 @@ +{ + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "@taci-tech"], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "rules": { + "react-hooks/exhaustive-deps": "off", + "operator-linebreak": ["error", "before"], + "max-len": "off", + "quote-props": "off", + // For TypeScript lint: + "no-unused-vars": "off", + "react/display-name": "off", + "valid-jsdoc": "off" + } +} diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..72c8945 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,14 @@ +# Description + + + +# Related Issues + + +* #ISSUE_NUMBER_OR_PR_NUMBER + +# Contribution Checklist + +- [ ] My changes generate no new warnings. +- [ ] My code builds and runs properly on my machine. +- [ ] I have documented my code. diff --git a/.github/workflows/eslint-check.yml b/.github/workflows/eslint-check.yml new file mode 100644 index 0000000..82eb367 --- /dev/null +++ b/.github/workflows/eslint-check.yml @@ -0,0 +1,24 @@ +name: Eslint Check + +on: + push: + branches: + - '*' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + eslint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: oven-sh/setup-bun@v1 + with: + bun-version: 1.0.6 + - name: Install Dependencies + run: bun install --frozen-lockfile + - name: Lint Check + run: bun lint + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d161ef5 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +hey@lingxi.li. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4f98c36 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Lingxi Li + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1b0a8d3 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Radix Colors for Tailwind CSS + +> [!WARNING] +> This project is still in early development. It is not yet ready for production use. + +Radix Colors is a great color palette for software design and development. I love its simplicity in defining one color palette for both light and dark mode. I really enjoy the time when I don't have to specify `dark:` colors anymore. The original `@radix-ui/colors` library is fully usable with Tailwind CSS (e.g. `bg-mauve-2`), but it is incompatible with Tailwind CSS alpha value injection (e.g. `bg-mauve-4/50`) because its library is originally implemented and built with Hex color format. So at the same time of enjoying the simplicity of Radix Colors, I have to give up the ability to use Tailwind CSS alpha values for getting a bunch of modified semi-transparent colors. + +This library is intended to provide a simple way to integrate all benefits of Radix Colors in Tailwind CSS, including alpha values, P3 display, and composing ability. It generally parses the original library and generates a plug-able Tailwind CSS plugin. + +## Usage + +TBA. + +## Reference + +- Original palette library: https://github.com/radix-ui/colors +- Use it in Swift and SwiftUI: https://github.com/lilingxi01/radix-colors-swift diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..d144b25 Binary files /dev/null and b/bun.lockb differ diff --git a/generator/header-comment.ts b/generator/header-comment.ts new file mode 100644 index 0000000..871018d --- /dev/null +++ b/generator/header-comment.ts @@ -0,0 +1,11 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const packageJson = require('../package.json'); + +export const headerComment = `/** + Radix Colors for Tailwind CSS + + Version: ${packageJson.version} + Created by Lingxi Li on 12/19/23. + + To know more about this file, visit: https://github.com/lilingxi01/radix-colors-tailwind + */`; diff --git a/generator/index.ts b/generator/index.ts new file mode 100644 index 0000000..50d721a --- /dev/null +++ b/generator/index.ts @@ -0,0 +1,119 @@ +import fs from 'fs'; +import path from 'path'; +import { headerComment } from './header-comment.ts'; + +const outputDir = 'dist'; + +/** + * This function is used to parse a color string from Radix Hex Colors to RGB colors that can be combined with transparency. + * @param hexCode - The value of a Radix Color (in hex code, e.g. `#FF0000`). + * @return - The RGB color string (e.g. `255 0 0`). + */ +function hexToRgb(hexCode: string): string { + // Validate hex code format. + if (!hexCode.startsWith('#') || (hexCode.length !== 7 && hexCode.length !== 9)) { + throw new Error('Invalid hex code format'); + } + + // Remove the leading "#". + const hexValue = hexCode.slice(1); + // Convert hex pairs to decimal values. + const matches = hexValue.match(/.{2}/g); + if (!matches) { + throw new Error('Invalid hex code format'); + } + const [r, g, b, a] = matches.map((hexPair) => parseInt(hexPair, 16)); + if (a) { + const roundedAlphaValue = Math.round(a / 255 * 1000) / 1000; + return [r, g, b].join(' ') + ' / ' + roundedAlphaValue; + } + return [r, g, b].join(' '); +} + +async function main() { + // Get a list of files in package `@radix-ui/colors`. + const __dirname = path.resolve(); + const radixPackagePath = require.resolve('@radix-ui/colors'); + // Get relative path to the current execution path instead of using pure absolute path, and remove ending `index.js` from the path. + let radixRelativePackagePath = path.relative(__dirname, radixPackagePath); + radixRelativePackagePath = radixRelativePackagePath.replace(/index\.js$/, ''); + + // Only get `.css` files from the package files. + const files = fs.readdirSync(radixRelativePackagePath).filter((file) => file.endsWith('.css')); + + // Create the output directory if it does not exist. + const outputDirPath = path.join(__dirname, outputDir); + if (!fs.existsSync(outputDirPath)) { + fs.mkdirSync(outputDirPath); + } + + if (!files.length) { + throw new Error('No CSS files found in package `@radix-ui/colors`. It is probably broken after updating the package.'); + } + + const radixCssMatchingRegex = /^--(\w+)-(\w+):\s*(#\w+);$/; + const radixP3MatchingRegex = /^--(\w+)-(\w+):\s*color\(display-p3 (\d+\.?\d*) (\d+\.?\d*) (\d+\.?\d*)(?: \/ (\d+\.?\d*))?\);$/; + + // Process all available files. + for (const currentFilename of files) { + const currentFilePath = path.join(radixRelativePackagePath, currentFilename); + const currentFile = Bun.file(currentFilePath); + const currentText = await currentFile.text(); + if (!currentText) { + console.error('No text found in file: ' + currentFilePath); + continue; + } + const lines: string[] = currentText.split('\n'); + const newLines: string[] = []; + + for (const line of lines) { + const leadingSpaces = line.match(/^\s+/)?.[0] ?? ''; + const lineWithoutLeadingSpaces = line.replace(/^\s+/, ''); + if (!lineWithoutLeadingSpaces || !lineWithoutLeadingSpaces.startsWith('--')) { + newLines.push(line); + continue; + } + if (leadingSpaces.length <= 2) { + const matches = lineWithoutLeadingSpaces.match(radixCssMatchingRegex); + if (!matches || matches.length < 4) { // Check if the line is a valid color line. + newLines.push(line); + continue; + } + const colorName = matches[1]; + const colorIndex = matches[2]; + const colorValue = matches[3]; + if (!colorName || !colorIndex || !colorValue) { // Check if any of the values are missing. + console.error('Invalid color values: ' + line); + throw new Error('Invalid color values at file: ' + currentFilePath); + } + const convertedColorValue = hexToRgb(colorValue); + const newLine = leadingSpaces + '--radix-rgb-' + colorName + '-' + colorIndex + ': ' + convertedColorValue + ';'; + newLines.push(newLine); + continue; + } + const matches = lineWithoutLeadingSpaces.match(radixP3MatchingRegex); + if (!matches || matches.length < 5) { // Check if the line is a valid color line. + newLines.push(line); + continue; + } + const colorName = matches[1]; + const colorIndex = matches[2]; + const r = matches[3]; + const g = matches[4]; + const b = matches[5]; + const a = matches[6]; + if (!r || !g || !b) { // Check if any of the values are missing. + console.error('Invalid color values: ' + line); + throw new Error('Invalid color values at file: ' + currentFilePath); + } + const convertedColorValue = r + ' ' + g + ' ' + b + (a ? (' / ' + a) : ''); + const newLine = leadingSpaces + '--radix-p3-' + colorName + '-' + colorIndex + ': ' + convertedColorValue + ';'; + newLines.push(newLine); + } + + const newFilePath = path.join(outputDirPath, currentFilename); + await Bun.write(newFilePath, headerComment + '\n\n' + newLines.join('\n')); + } +} + +main(); diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..f67b2c6 --- /dev/null +++ b/index.ts @@ -0,0 +1 @@ +console.log("Hello via Bun!"); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..225257d --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "radix-colors-tailwind", + "version": "0.0.1", + "main": "generator/index.ts", + "type": "module", + "scripts": { + "prod": "bun generator/index.ts", + "lint": "eslint generator/*" + }, + "devDependencies": { + "@taci-tech/eslint-config": "^0.0.3", + "@radix-ui/colors": "^3.0.0", + "@typescript-eslint/eslint-plugin": "^5.48.1", + "@typescript-eslint/parser": "^5.48.1", + "eslint": "^8.32.0", + "bun-types": "latest", + "typescript": "^5.0.0" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7556e1d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": [ + "bun-types" // add Bun global + ] + } +}