From 1efb0a2cd22180e0971b10693ce43689b42fa4de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:11:49 +0000 Subject: [PATCH 1/9] Initial plan From de29ac874cc5407bb710b48372fe6184d9b06337 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:22:05 +0000 Subject: [PATCH 2/9] Create import-custom-colors Figma plugin package with complete structure Co-authored-by: mfranzke <787658+mfranzke@users.noreply.github.com> --- package-lock.json | 317 ++++++++++++++++++ package.json | 4 +- packages/.copilot-instructions.md | 127 +++++++ packages/import-custom-colors/manifest.json | 10 + packages/import-custom-colors/package.json | 21 ++ .../import-custom-colors/plugin/package.json | 44 +++ .../import-custom-colors/plugin/src/index.ts | 123 +++++++ .../import-custom-colors/plugin/tsconfig.json | 14 + packages/import-custom-colors/ui/package.json | 34 ++ packages/import-custom-colors/ui/src/App.tsx | 225 +++++++++++++ .../import-custom-colors/ui/src/index.css | 3 + packages/import-custom-colors/ui/src/main.tsx | 10 + .../ui/tailwind.config.ts | 19 ++ .../import-custom-colors/ui/tsconfig.json | 24 ++ .../ui/tsconfig.tsbuildinfo | 1 + .../import-custom-colors/ui/vite.config.ts | 18 + packages/shared/data.ts | 11 +- 17 files changed, 1003 insertions(+), 2 deletions(-) create mode 100644 packages/.copilot-instructions.md create mode 100644 packages/import-custom-colors/manifest.json create mode 100644 packages/import-custom-colors/package.json create mode 100644 packages/import-custom-colors/plugin/package.json create mode 100644 packages/import-custom-colors/plugin/src/index.ts create mode 100644 packages/import-custom-colors/plugin/tsconfig.json create mode 100644 packages/import-custom-colors/ui/package.json create mode 100644 packages/import-custom-colors/ui/src/App.tsx create mode 100644 packages/import-custom-colors/ui/src/index.css create mode 100644 packages/import-custom-colors/ui/src/main.tsx create mode 100644 packages/import-custom-colors/ui/tailwind.config.ts create mode 100644 packages/import-custom-colors/ui/tsconfig.json create mode 100644 packages/import-custom-colors/ui/tsconfig.tsbuildinfo create mode 100644 packages/import-custom-colors/ui/vite.config.ts diff --git a/package-lock.json b/package-lock.json index 1e9d18ee..722cb638 100644 --- a/package-lock.json +++ b/package-lock.json @@ -420,6 +420,10 @@ "resolved": "packages/handover", "link": true }, + "node_modules/@db-ux/import-custom-colors": { + "resolved": "packages/import-custom-colors", + "link": true + }, "node_modules/@db-ux/inspect": { "resolved": "packages/inspect", "link": true @@ -902,6 +906,16 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", @@ -3503,6 +3517,14 @@ "node": ">= 4" } }, + "node_modules/import-custom-colors-code": { + "resolved": "packages/import-custom-colors/plugin", + "link": true + }, + "node_modules/import-custom-colors-ui": { + "resolved": "packages/import-custom-colors/ui", + "link": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -6281,6 +6303,301 @@ "typescript": "^5.9.2" } }, + "packages/import-custom-colors": { + "name": "@db-ux/import-custom-colors", + "version": "1.0.0", + "license": "MIT", + "workspaces": [ + "plugin/*", + "ui/*" + ] + }, + "packages/import-custom-colors/node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "packages/import-custom-colors/node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "packages/import-custom-colors/node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/import-custom-colors/node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/import-custom-colors/node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "packages/import-custom-colors/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/import-custom-colors/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/import-custom-colors/node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/import-custom-colors/node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "packages/import-custom-colors/node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "packages/import-custom-colors/plugin": { + "name": "import-custom-colors-code", + "version": "1.0.0", + "devDependencies": { + "@figma/eslint-plugin-figma-plugins": "*", + "@figma/plugin-typings": "*", + "@typescript-eslint/eslint-plugin": "^8.23.0", + "@typescript-eslint/parser": "^8.26.1", + "eslint": "^8.54.0", + "typescript": "^5.9.2" + } + }, + "packages/import-custom-colors/ui": { + "name": "import-custom-colors-ui", + "version": "0.0.0", + "dependencies": { + "@db-ux/db-theme": "1.0.2", + "@db-ux/react-core-components": "1.2.1", + "react": "^19.1.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "@types/react": "^19.1.13", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.7.0", + "autoprefixer": "^10.4.21", + "eslint": "^9.13.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^15.15.0", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", + "typescript": "~5.9.2", + "typescript-eslint": "^8.44.0", + "vite": "^6.3.5", + "vite-plugin-singlefile": "^2.3.0" + } + }, + "packages/import-custom-colors/ui/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "packages/import-custom-colors/ui/node_modules/eslint": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "packages/import-custom-colors/ui/node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "packages/inspect": { "name": "@db-ux/inspect", "version": "1.0.0", diff --git a/package.json b/package.json index 8f3d99f9..49b508a7 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,14 @@ "build:handover": "npm run build -w=@db-ux/handover", "build:auto-sync": "npm run build -w=@db-ux/auto-sync", "build:design-migration": "npm run build -w=@db-ux/design-migration", + "build:import-custom-colors": "npm run build -w=@db-ux/import-custom-colors", "dev": "npm-run-all --parallel dev:*", "dev:codegen": "npm run dev -w=@db-ux/codegen", "dev:inspect": "npm run dev -w=@db-ux/inspect", "dev:handover": "npm run dev -w=@db-ux/handover", "dev:auto-sync": "npm run dev -w=@db-ux/auto-sync", - "dev:design-migration": "npm run dev -w=@db-ux/design-migration" + "dev:design-migration": "npm run dev -w=@db-ux/design-migration", + "dev:import-custom-colors": "npm run dev -w=@db-ux/import-custom-colors" }, "author": "", "license": "MIT", diff --git a/packages/.copilot-instructions.md b/packages/.copilot-instructions.md new file mode 100644 index 00000000..43ff29a4 --- /dev/null +++ b/packages/.copilot-instructions.md @@ -0,0 +1,127 @@ +# Copilot Instructions for Packages + +This folder contains multiple Figma plugins and widgets for the DB UX Design System. Each package follows a consistent structure and pattern. + +## Package Structure + +Each package in this folder follows this structure: +``` +package-name/ +├── package.json # Main package configuration with workspaces +├── manifest.json # Figma plugin/widget manifest +├── index.html # Built UI file (generated) +├── index.js # Built plugin code (generated) +├── plugin/ # Plugin/widget logic +│ ├── package.json # Plugin-specific dependencies and build scripts +│ ├── tsconfig.json # TypeScript configuration +│ └── src/ +│ └── index.ts # Main plugin entry point +└── ui/ # React-based UI (if applicable) + ├── package.json # UI-specific dependencies and build scripts + ├── vite.config.ts # Vite configuration for UI build + ├── tsconfig.json # TypeScript configuration + └── src/ + └── index.tsx # Main UI entry point +``` + +## Existing Packages + +- **auto-sync**: Widget for synchronizing design tokens +- **codegen**: Plugin for generating code from designs +- **design-migration**: Plugin for migrating design system components +- **handover**: Widget for creating design handover documentation +- **inspect**: Plugin for inspecting design elements +- **shared**: Common utilities and types used across packages + +## Development Guidelines + +### Creating a New Package + +1. Follow the existing package structure pattern +2. Use the same build tools (esbuild for plugin, vite for UI) +3. Include proper TypeScript configurations +4. Add appropriate scripts to root package.json +5. Use shared utilities from the `shared` package when possible + +### Plugin Development (plugin/ folder) + +- Use TypeScript with `@figma/plugin-typings` +- Main entry point should handle `figma.showUI()` for UI-based plugins +- Use `figma.ui.onmessage` for UI communication +- Use esbuild for building with ES6 target +- Follow ESLint configuration with Figma plugin rules + +### UI Development (ui/ folder) + +- Use React with TypeScript +- Use Vite with singlefile plugin to generate inline HTML +- Use Tailwind CSS for styling with DB design tokens +- Use `@db-ux/react-core-components` for UI components +- Communication with plugin via `parent.postMessage()` + +### Build Process + +- Plugin builds to `../index.js` in package root +- UI builds to `../index.html` in package root +- Use `npm-run-all` for parallel builds +- Include watch modes for development + +### Message Communication Pattern + +Use the shared message pattern: +```typescript +// From UI to Plugin +parent.postMessage({ pluginMessage: { type: "action", data: payload } }, "*"); + +// From Plugin to UI +figma.ui.postMessage({ type: "response", data: result }); +``` + +### Code Quality + +- Use ESLint with TypeScript and Figma plugin rules +- Follow existing code patterns and conventions +- Use proper TypeScript types, especially for Figma API +- Include error handling for user interactions + +### Dependencies + +- Prefer shared dependencies in root package.json when possible +- Keep package-specific dependencies minimal +- Use exact versions for design system components +- Avoid duplicating build tools across packages + +## Common Patterns + +### Plugin Entry Point +```typescript +import { handlePluginLogic } from "./plugin-logic"; + +if (figma.editorType === "figma") { + handlePluginLogic(); +} +``` + +### UI Message Handling +```typescript +useEffect(() => { + onmessage = (event: MessageEvent) => { + const message = event.data.pluginMessage; + // Handle different message types + }; +}, []); +``` + +### Build Scripts +```json +{ + "scripts": { + "build": "npm-run-all --parallel build:*", + "build:code": "npm run build -w=package-name-code -- --minify", + "build:ui": "npm run build -w=package-name-ui", + "dev": "npm-run-all --parallel dev:*", + "dev:code": "npm run dev -w=package-name-code", + "dev:ui": "npm run dev -w=package-name-ui" + } +} +``` \ No newline at end of file diff --git a/packages/import-custom-colors/manifest.json b/packages/import-custom-colors/manifest.json new file mode 100644 index 00000000..8a6bfecf --- /dev/null +++ b/packages/import-custom-colors/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "import-custom-colors", + "id": "import-custom-colors", + "api": "1.0.0", + "main": "index.js", + "ui": "index.html", + "editorType": ["figma"], + "networkAccess": { "allowedDomains": ["none"] }, + "documentAccess": "dynamic-page" +} \ No newline at end of file diff --git a/packages/import-custom-colors/package.json b/packages/import-custom-colors/package.json new file mode 100644 index 00000000..506955ba --- /dev/null +++ b/packages/import-custom-colors/package.json @@ -0,0 +1,21 @@ +{ + "name": "@db-ux/import-custom-colors", + "private": true, + "version": "1.0.0", + "description": "Figma plugin for importing custom colors into Figma", + "main": "index.js", + "workspaces": [ + "plugin/*", + "ui/*" + ], + "scripts": { + "build": "npm-run-all --parallel build:*", + "build:code": "npm run build -w=import-custom-colors-code -- --minify", + "build:ui": "npm run build -w=import-custom-colors-ui", + "dev": "npm-run-all --parallel dev:*", + "dev:code": "npm run dev -w=import-custom-colors-code", + "dev:ui": "npm run dev -w=import-custom-colors-ui" + }, + "author": "", + "license": "MIT" +} \ No newline at end of file diff --git a/packages/import-custom-colors/plugin/package.json b/packages/import-custom-colors/plugin/package.json new file mode 100644 index 00000000..00075273 --- /dev/null +++ b/packages/import-custom-colors/plugin/package.json @@ -0,0 +1,44 @@ +{ + "name": "import-custom-colors-code", + "version": "1.0.0", + "main": "import-custom-colors.js", + "type": "module", + "scripts": { + "build": "esbuild src/index.ts --bundle --outfile=../index.js --target=ES6", + "lint": "eslint --ext .ts,.tsx --ignore-pattern node_modules .", + "lint:fix": "eslint --ext .ts,.tsx --ignore-pattern node_modules --fix .", + "dev": "npm run build -- --watch" + }, + "author": "", + "license": "", + "devDependencies": { + "@figma/eslint-plugin-figma-plugins": "*", + "@figma/plugin-typings": "*", + "@typescript-eslint/eslint-plugin": "^8.23.0", + "@typescript-eslint/parser": "^8.26.1", + "eslint": "^8.54.0", + "typescript": "^5.9.2" + }, + "eslintConfig": { + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@figma/figma-plugins/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json" + }, + "root": true, + "rules": { + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/import-custom-colors/plugin/src/index.ts b/packages/import-custom-colors/plugin/src/index.ts new file mode 100644 index 00000000..ef2ffd22 --- /dev/null +++ b/packages/import-custom-colors/plugin/src/index.ts @@ -0,0 +1,123 @@ +import { UiMessageImportColors } from "shared/data"; +import { sendMessage } from "shared/figma"; + +interface CustomColor { + name: string; + hex: string; +} + +export const handleImportCustomColors = () => { + figma.showUI(__html__, { height: 600, width: 400 }); + + figma.ui.onmessage = async (msg: UiMessageImportColors) => { + try { + if (msg.type === "import-colors") { + sendMessage({ type: "loading", data: "Importing custom colors..." }); + await importColors(msg.data); + sendMessage({ type: "success", data: "Colors imported successfully!" }); + } + } catch (error) { + console.error("Error importing colors:", error); + sendMessage({ + type: "error", + data: error instanceof Error ? error.message : "Unknown error occurred" + }); + } + }; +}; + +const importColors = async (colors: CustomColor[]) => { + // Get the current page + const currentPage = figma.currentPage; + + // Create or find a color palette frame + let colorFrame = currentPage.findOne(node => + node.type === "FRAME" && node.name === "Custom Color Palette" + ) as FrameNode; + + if (!colorFrame) { + colorFrame = figma.createFrame(); + colorFrame.name = "Custom Color Palette"; + colorFrame.resize(800, Math.max(400, Math.ceil(colors.length / 8) * 100)); + colorFrame.x = 0; + colorFrame.y = 0; + colorFrame.fills = [{ type: "SOLID", color: { r: 0.98, g: 0.98, b: 0.98 } }]; + } + + // Clear existing colors in the frame + colorFrame.children.forEach(child => child.remove()); + + // Create color swatches + for (let i = 0; i < colors.length; i++) { + const color = colors[i]; + const col = i % 8; + const row = Math.floor(i / 8); + + // Convert hex to RGB + const rgb = hexToRgb(color.hex); + if (!rgb) { + console.warn(`Invalid hex color: ${color.hex}`); + continue; + } + + // Create color swatch + const swatch = figma.createRectangle(); + swatch.resize(80, 80); + swatch.x = col * 90 + 20; + swatch.y = row * 90 + 60; + swatch.fills = [{ + type: "SOLID", + color: { r: rgb.r / 255, g: rgb.g / 255, b: rgb.b / 255 } + }]; + swatch.name = color.name; + + // Add color name text + const text = figma.createText(); + await figma.loadFontAsync({ family: "Inter", style: "Regular" }); + text.characters = color.name; + text.fontSize = 10; + text.x = col * 90 + 20; + text.y = row * 90 + 145; + text.resize(80, 20); + text.textAlignHorizontal = "CENTER"; + + // Add hex value text + const hexText = figma.createText(); + hexText.characters = color.hex.toUpperCase(); + hexText.fontSize = 8; + hexText.x = col * 90 + 20; + hexText.y = row * 90 + 160; + hexText.resize(80, 16); + hexText.textAlignHorizontal = "CENTER"; + hexText.fills = [{ type: "SOLID", color: { r: 0.5, g: 0.5, b: 0.5 } }]; + + // Add to frame + colorFrame.appendChild(swatch); + colorFrame.appendChild(text); + colorFrame.appendChild(hexText); + + // Create local paint style + const paintStyle = figma.createPaintStyle(); + paintStyle.name = `Custom Colors/${color.name}`; + paintStyle.paints = [{ + type: "SOLID", + color: { r: rgb.r / 255, g: rgb.g / 255, b: rgb.b / 255 } + }]; + } + + // Focus on the color frame + figma.viewport.scrollAndZoomIntoView([colorFrame]); +}; + +const hexToRgb = (hex: string): { r: number; g: number; b: number } | null => { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : null; +}; + +if (figma.editorType === "figma") { + handleImportCustomColors(); +} \ No newline at end of file diff --git a/packages/import-custom-colors/plugin/tsconfig.json b/packages/import-custom-colors/plugin/tsconfig.json new file mode 100644 index 00000000..57bd0b1f --- /dev/null +++ b/packages/import-custom-colors/plugin/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES6", + "lib": ["ES6"], + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/packages/import-custom-colors/ui/package.json b/packages/import-custom-colors/ui/package.json new file mode 100644 index 00000000..4130f0e7 --- /dev/null +++ b/packages/import-custom-colors/ui/package.json @@ -0,0 +1,34 @@ +{ + "name": "import-custom-colors-ui", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "npm run build -- --watch", + "build": "tsc -b && vite build --minify esbuild --emptyOutDir=false --target=ES6", + "lint": "eslint ." + }, + "dependencies": { + "@db-ux/react-core-components": "1.2.1", + "@db-ux/db-theme": "1.0.2", + "react": "^19.1.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "@types/react": "^19.1.13", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.7.0", + "autoprefixer": "^10.4.21", + "eslint": "^9.13.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^15.15.0", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", + "typescript": "~5.9.2", + "typescript-eslint": "^8.44.0", + "vite": "^6.3.5", + "vite-plugin-singlefile": "^2.3.0" + } +} \ No newline at end of file diff --git a/packages/import-custom-colors/ui/src/App.tsx b/packages/import-custom-colors/ui/src/App.tsx new file mode 100644 index 00000000..7a3d3fb6 --- /dev/null +++ b/packages/import-custom-colors/ui/src/App.tsx @@ -0,0 +1,225 @@ +import React, { useState, useEffect } from 'react'; +import { DBButton, DBInfotext } from '@db-ux/react-core-components'; + +interface CustomColor { + name: string; + hex: string; +} + +interface PluginMessage { + type: string; + data?: any; +} + +const App: React.FC = () => { + const [colors, setColors] = useState([]); + const [newColorName, setNewColorName] = useState(''); + const [newColorHex, setNewColorHex] = useState('#000000'); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + const [messageType, setMessageType] = useState<'success' | 'error' | 'info'>('info'); + + useEffect(() => { + window.onmessage = (event: MessageEvent) => { + const pluginMessage: PluginMessage = event.data.pluginMessage; + + if (pluginMessage.type === 'loading') { + setLoading(true); + setMessage(pluginMessage.data || 'Processing...'); + setMessageType('info'); + } else if (pluginMessage.type === 'success') { + setLoading(false); + setMessage(pluginMessage.data || 'Success!'); + setMessageType('success'); + // Clear colors after successful import + setColors([]); + } else if (pluginMessage.type === 'error') { + setLoading(false); + setMessage(pluginMessage.data || 'An error occurred'); + setMessageType('error'); + } + }; + }, []); + + const addColor = () => { + if (newColorName.trim() && isValidHex(newColorHex)) { + const newColor: CustomColor = { + name: newColorName.trim(), + hex: newColorHex + }; + + setColors([...colors, newColor]); + setNewColorName(''); + setNewColorHex('#000000'); + setMessage(''); + } else { + setMessage('Please enter a valid color name and hex value'); + setMessageType('error'); + } + }; + + const removeColor = (index: number) => { + setColors(colors.filter((_, i) => i !== index)); + }; + + const importColors = () => { + if (colors.length === 0) { + setMessage('Please add at least one color to import'); + setMessageType('error'); + return; + } + + parent.postMessage({ + pluginMessage: { + type: 'import-colors', + data: colors + } + }, '*'); + }; + + const isValidHex = (hex: string): boolean => { + return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(hex); + }; + + const parseColorsFromText = (text: string) => { + const lines = text.split('\n').filter(line => line.trim()); + const parsedColors: CustomColor[] = []; + + lines.forEach(line => { + // Try different formats: "name #hex", "name: #hex", "name,#hex", etc. + const patterns = [ + /^(.+?)\s*[:\-,]\s*(#[A-Fa-f0-9]{3,6})$/, + /^(.+?)\s+(#[A-Fa-f0-9]{3,6})$/, + /^(#[A-Fa-f0-9]{3,6})\s+(.+)$/ + ]; + + for (const pattern of patterns) { + const match = line.trim().match(pattern); + if (match) { + const [, part1, part2] = match; + let name, hex; + + if (part1.startsWith('#')) { + hex = part1; + name = part2; + } else { + name = part1; + hex = part2; + } + + if (isValidHex(hex)) { + parsedColors.push({ name: name.trim(), hex: hex.toUpperCase() }); + break; + } + } + } + }); + + if (parsedColors.length > 0) { + setColors([...colors, ...parsedColors]); + setMessage(`Added ${parsedColors.length} colors from text`); + setMessageType('success'); + } else { + setMessage('No valid colors found. Use format: "Color Name #HEX" or "Color Name: #HEX"'); + setMessageType('error'); + } + }; + + return ( +
+

Import Custom Colors

+ + {message && ( + + {message} + + )} + +
+

Add Individual Color

+
+
+ + ) => setNewColorName(e.target.value)} + placeholder="e.g. Primary Blue" + className="w-full p-2 border border-gray-300 rounded text-sm" + /> +
+
+ + ) => setNewColorHex(e.target.value)} + className="w-full h-10 border border-gray-300 rounded cursor-pointer" + /> +
+ + Add + +
+
+ +
+

Or Paste Multiple Colors

+