diff --git a/.github/ISSUE_TEMPLATE/good_first_issue.yml b/.github/ISSUE_TEMPLATE/good_first_issue.yml index 6f1ae36..2508f21 100644 --- a/.github/ISSUE_TEMPLATE/good_first_issue.yml +++ b/.github/ISSUE_TEMPLATE/good_first_issue.yml @@ -36,8 +36,8 @@ body: label: Resources description: Helpful resources for completing this task value: | - - [Contribution Guide - Start Here!](https://github.com/AOSSIE-Org/TODO/blob/main/CONTRIBUTING.md) - - [Discord Channel](https://discord.gg/hjUhu33uAn) + - [Contribution Guide - Start Here!](https://github.com/AOSSIE-Org/SupportUsButton/blob/main/CONTRIBUTING.md) + - [Discord Channel #support-us-button](https://discord.gg/vK2NfgG) validations: required: false @@ -54,9 +54,9 @@ body: label: Getting Started description: Before you begin, please confirm the following options: - - label: I have joined the [Discord server](https://discord.gg/hjUhu33uAn) and will post updates there + - label: I have joined the [Discord server](https://discord.gg/vK2NfgG) and will post updates there required: true - - label: I have read the [Contribution Guide](https://github.com/AOSSIE-Org/Template-Repo/blob/main/CONTRIBUTING.md) + - label: I have read the [Contribution Guide](https://github.com/AOSSIE-Org/SupportUsButton/blob/main/CONTRIBUTING.md) required: true - label: I understand this issue is assigned on a first-come, first-served basis required: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..410a511 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI & Quality Checks + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-test: + name: Type-Check, Test & Build + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout Code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + with: + node-version: 20 + cache: "npm" + + - name: Install Dependencies + run: npm install + + - name: Type Check + run: npm run type-check + + - name: Run Tests + run: npm test + + - name: Build Package + run: npm run build diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..cfece1b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,62 @@ +# SupportUsButton — AI Agent Guidelines & Architecture Directives + +Welcome, AI Agent! This document contains essential instructions, code style standards, and architectural directives for operating on the **SupportUsButton** repository. + +--- + +## 🛠️ Stack & Workspace Overview + +- **Package Name:** `support-us-button` +- **Framework:** React 19 / React 18 (`react`, `react-dom`) +- **Language:** TypeScript 5.9 (`tsconfig.json`) +- **Styling:** Tailwind CSS v4 (`@tailwindcss/cli` & `@tailwindcss/vite`) +- **Bundler:** Rollup 4 (`rollup.config.js`) +- **Dev Preview:** Vite 8 (`demo/App.tsx`) + +### Key Build & Test Commands: + +- **Type-Check:** `npx tsc --noEmit` +- **Build Package:** `npm run build` +- **Develop Demo:** `npm run dev:demo` + +--- + +## 🎨 Theme Inheritance Architecture + +1. **Default Theme (`Theme="auto"`)**: + - The component MUST inherit the host website's font family (`font-inherit`) and background natively via CSS transparency (`bg-transparent text-inherit`). + - Do **NOT** hardcode fixed background colors or font stacks on the outer component wrapper when `Theme="auto"`. + +2. **Parent Style Detection (`useParentStyles` hook)**: + - Uses `window.getComputedStyle` with upward DOM traversal inside `useIsomorphicLayoutEffect`. + - Used primarily to evaluate relative luminance (`isDarkColor`) to invert logo (`brightness-0 invert`) and button contrast dynamically. + +--- + +## 📁 Repository Structure + +```text +SupportUsButton/ +├── brand/ # Brand assets (logo.svg, favicon.svg, Brand.md) +├── demo/ # Interactive Vite Dev Preview app (App.tsx) +├── dist/ # Bundled package outputs (ESM, CJS, UMD, style.css) +├── src/ +│ ├── components/ # SupportUsButton.tsx +│ ├── hooks/ # useParentStyles.ts +│ ├── styles/ # style.css (Tailwind v4 input) +│ ├── types/ # index.ts (TypeScript definitions) +│ └── index.ts # Main package entrypoint +├── AGENTS.md # AI agent instructions (this file) +├── BestPracticesChecklist.md # Audit & best practices status +├── MAINTAINERS.md # Project maintainers and mentors +├── README.md # User-facing package documentation +└── rollup.config.js # Rollup bundler configuration +``` + +--- + +## ⚠️ Important Guidelines for Agents + +1. **Preserve Compatibility**: Maintain support for both React 18 and React 19. +2. **Always Run Type-Check & Build**: Before declaring a task completed, execute `npx tsc --noEmit` and `npm run build`. +3. **No Unneeded Dependencies**: Avoid adding external runtime dependencies unless explicitly approved. diff --git a/BestPracticesChecklist.md b/BestPracticesChecklist.md new file mode 100644 index 0000000..62774dc --- /dev/null +++ b/BestPracticesChecklist.md @@ -0,0 +1,43 @@ +# AOSSIE Best Practices Checklist — SupportUsButton + +This document tracks compliance with the **AOSSIE Best Practices Guidelines** for the **SupportUsButton** project. + +--- + +## 🔴 Must (Mandatory Practices) + +- [x] **Licensing & Copyright**: Includes GNU General Public License v3.0 in [`LICENSE`](LICENSE). +- [x] **Project Branding**: Complete SVG logo, favicon, color palette, and typography documented in [`brand/Brand.md`](brand/Brand.md). +- [x] **Maintainers List**: Listed in [`MAINTAINERS.md`](MAINTAINERS.md). +- [x] **Clean Documentation**: `README.md` and `CONTRIBUTING.md` contain no broken links or TODOs left. +- [x] **Build & Run Instructions**: `README.md` contains clear installation, development, testing, and build instructions. +- [x] **Type Safety**: Written in 100% strict TypeScript with type definitions exported. +- [x] **No Magic Constants**: Styling tokens and default properties are configured cleanly in types and CSS variables. +- [x] **Zero Build Warnings**: `npm run build` compiles cleanly with zero warnings or errors. +- [x] **AI Agent Directives**: Context and operational guidelines provided in [`AGENTS.md`](AGENTS.md). +- [x] **CodeRabbit Configuration**: Customized in [`.coderabbit.yaml`](.coderabbit.yaml). + +--- + +## 🟡 Should (Recommended Practices) + +- [x] **Automatic Host Style Adaptation**: Package inherits host page `font-family`, `background-color`, and `color` natively. +- [x] **Contrast Fail-Safes**: Dynamic relative luminance contrast calculation (`isDarkColor`) for logo and button visibility on light/dark host themes. +- [x] **Automated Testing & CI**: Unit testing and GitHub Actions workflow configured for PR validation. +- [x] **Micro-Animations**: GPU-accelerated CSS keyframe transitions (`animate-sub-fade-in`, `animate-sub-scale-in`). +- [x] **Zero Third-Party UI Dependencies**: Ultra-lightweight package architecture (only standard `tslib` helper; zero external UI framework dependencies) producing ~12 KB minified JS output (`dist/index.esm.js`) and ~4 KB minified CSS (`dist/style.css`). +- [x] **Multi-Bundle Formats**: Exports ESM (`dist/index.esm.js`), CommonJS (`dist/index.cjs.js`), and UMD (`dist/index.umd.js`). + +--- + +## 🟢 Could (Optional / Future Enhancements) + +- [ ] Interactive online playground demo deployed to GitHub Pages. +- [ ] Automated npm release workflow via GitHub Actions on tagged releases. + +--- + +## 📑 Verification Log + +* **TypeScript Validation (`npx tsc --noEmit`)**: ✅ Passed (0 errors) +* **Bundle Build (`npm run build`)**: ✅ Passed (0 warnings) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b5300b..8e84000 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,21 +1,21 @@ -# Contributing to TODO: Project Name +# Contributing to SupportUsButton ⭐ First off, thank you for considering contributing to this project! ⭐ We welcome contributions from everyone. By participating in this project, you agree to abide by our Code of Conduct. -## � IMPORTANT: Discord Communication is Mandatory +## 💬 IMPORTANT: Discord Communication is Mandatory -**All project communication MUST happen on Discord. We do not pay attention to GitHub notifications.** +**All project communication MUST happen on Discord in the [#support-us-button](https://discord.gg/vK2NfgG) channel. We do not pay attention to GitHub notifications.** -- Join our [Discord server](https://discord.gg/hjUhu33uAn) before starting any work -- Post your PR/issue updates in the relevant Discord channel (**MANDATORY**) +- Join our [Discord server & #support-us-button channel](https://discord.gg/vK2NfgG) before starting any work +- Post your PR/issue updates in the `#support-us-button` Discord channel (**MANDATORY**) - All discussions, questions, and updates should be on Discord - GitHub is for code only - Discord is for communication **PRs without Discord updates will not be reviewed or may face delays.** -## �📋 Table of Contents +## 📋 Table of Contents - [How Can I Contribute?](#how-can-i-contribute) - [Coding with AI](#coding-with-ai) @@ -68,7 +68,8 @@ What we expect: ### Prerequisites -TODO: List prerequisites specific to your project +- Node.js (>= 18.0.0) +- npm (>= 9.0.0) ### Setup @@ -79,13 +80,13 @@ TODO: List prerequisites specific to your project 2. **Clone Your Fork** ```bash - git clone https://github.com/YOUR_USERNAME/TODO.git - cd TODO + git clone https://github.com/YOUR_USERNAME/SupportUsButton.git + cd SupportUsButton ``` 3. **Add Upstream Remote** ```bash - git remote add upstream https://github.com/AOSSIE-Org/TODO.git + git remote add upstream https://github.com/AOSSIE-Org/SupportUsButton.git ``` 4. **Install Dependencies** @@ -119,14 +120,19 @@ git checkout -b fix/your-bug-fix - Add comments where necessary - Update documentation if needed -### 3. Test Your Changes +### 3. Test & Build Your Changes -TODO: Add project-specific testing instructions +Run type checks, build, and test verification before opening your PR: ```bash +# Type check TypeScript definitions +npx tsc --noEmit + +# Run unit tests npm test -# or -npm run lint + +# Build production library bundle & minified CSS +npm run build ``` ### 4. Commit Your Changes @@ -223,7 +229,7 @@ Steps to test the changes ## 📝 Code Style Guidelines -TODO: Add project-specific code style guidelines +Maintain 100% strict TypeScript types and export clean React component contracts. Ensure automatic theme inheritance (`Theme="auto"`) and CSS fallbacks remain unbroken. ### General Guidelines @@ -538,4 +544,4 @@ If you encounter issues not covered here: - Check for existing PRs before starting to avoid duplication, as there might PRs that didn't mention the related issue -Thank you for contributing to TODO! Your efforts help make this project better for everyone. 🚀 +Thank you for contributing to SupportUsButton! Your efforts help make this project better for everyone. 🚀 diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000..69caede --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,18 @@ +# Maintainers + +This document lists the maintainers of the **SupportUsButton** project. + +| Name | GitHub Username | Discord Username | Email Address | +| -------------------------------- | ------------------ | ------------------ | ---------------------------- | +| Bruno Woltzenlogel Paleo | @Zahnentferner | @b.wp | zahnentferner@gmail.com | +| Rahul Vyas | @rahul-vyas-dev | @rahulvyas_dev | rahulvyas.5100@gmail.com | +| Saksham Jain | @reach2saksham | @theonlypro | reach2saksham2004@gmail.com | + +--- + +## 📩 Contacting Maintainers + +* **Security Vulnerabilities**: For private security reports, please email [reach2saksham2004@gmail.com](mailto:reach2saksham2004@gmail.com) directly before public disclosure. +* **Architectural Questions & Governance**: + 1. **AOSSIE Discord Channel**: [#support-us-button](https://discord.gg/hjUhu33uAn) + 2. **GitHub Issues**: [github.com/AOSSIE-Org/SupportUsButton/issues](https://github.com/AOSSIE-Org/SupportUsButton/issues) diff --git a/brand/Brand.md b/brand/Brand.md new file mode 100644 index 0000000..169ef7b --- /dev/null +++ b/brand/Brand.md @@ -0,0 +1,68 @@ +# SupportUsButton — Brand & Design Specifications + +This document defines the official branding, logo assets, color palette, typography guidelines, and design tokens for the **SupportUsButton** project by **AOSSIE**. + +--- + +## 🎨 Brand Identity Overview + +**SupportUsButton** is an open-source, customizable, tier-based React component package designed to display donation and sponsorship options cleanly on web applications while seamlessly adapting to any host site's visual theme. + +--- + +## 🖼️ Logo & Assets + +All official brand assets are located inside the [`brand/`](./) directory. + +* **Primary Logo (SVG)**: [`logo.svg`](./logo.svg) +* **Favicon / Logomark (SVG)**: [`favicon.svg`](./favicon.svg) + +### Usage Rules: + +* Always maintain aspect ratios when displaying the SVG logo. +* On dark backgrounds, use the primary logo or inverted contrast version (`brightness-0 invert`). +* On light host backgrounds, use dark contrast text and borders (`brightness-0`). + +--- + +## 🎨 Color Palette + +The SupportUsButton brand palette balances modern dark UI elements with high-contrast accent highlights. + +| Token | Hex Value | RGB / HSL | Usage | +| :--- | :--- | :--- | :--- | +| **Accent Primary** | `#ffd700` | `rgb(255, 215, 0)` | Hover highlights, primary CTA accents, gold tier badges | +| **Dark Background** | `#191919` | `rgb(25, 25, 25)` | Predefined dark theme container background | +| **Dark Surface** | `#111111` | `rgb(17, 17, 17)` | Surface containers and card backgrounds | +| **Light Background** | `#F4F4F4` | `rgb(244, 244, 244)` | Predefined light theme container background | +| **Text Dark** | `#191919` | `rgb(25, 25, 25)` | Text on light backgrounds | +| **Text Light** | `#F4F4F4` | `rgb(244, 244, 244)` | Text on dark backgrounds | + +--- + +## 🔤 Typography + +SupportUsButton is designed with **automatic font-family inheritance** so that it matches the host website typography natively out-of-the-box. + +### Font Family Specs: + +* **Primary (Inherited)**: `font-family: inherit` (Default for `Theme="auto"`) +* **Fallback Stack**: `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif` +* **Heading Sizes**: + * Hero Title: `text-3xl sm:text-3xl md:text-5xl` (`font-medium`) + * Section Headers: `text-xl sm:text-2xl lg:text-3xl` (`font-medium`) + * CTA Button Text: `text-[18px]` (`font-semibold`) + +--- + +## 📦 CSS Custom Variables + +Host applications can override custom CSS variables if explicit theme control is needed: + +```css +:root { + --color-primary: #ffd700; + --color-background-light: #f4f4f4; + --color-background-dark: #191919; +} +``` diff --git a/brand/logo.svg b/brand/logo.svg new file mode 100644 index 0000000..86660e8 --- /dev/null +++ b/brand/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/demo/App.tsx b/demo/App.tsx index 453caa8..e2fc147 100644 --- a/demo/App.tsx +++ b/demo/App.tsx @@ -1,11 +1,70 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import SupportUsButton from "../src/index"; import type { Theme, supportUsButtonProps } from "../src/types/index"; import "../src/styles/style.css"; +const DEMO_TEXT = { + headerTitle: "SupportUsButton — Dev Preview", + themeLabel: "Theme Prop:", + hostBgLabel: "Host BG:", + hostFontLabel: "Host Font:", + bgLogoLabel: "BG Logo:", + toggleLogoAction: "Toggle background logo visibility", + options: { + auto: "✨ Auto (Host Adapted)", + inherit: "🏛️ Inherit (Host Theme)", + light: "☀️ Light", + dark: "🌙 Dark", + }, +}; + +const HOST_BG_MAP: Record = { + zinc: { bg: "#09090b", text: "#ffffff", label: "Dark Zinc (#09090b)" }, + slate: { bg: "#0f172a", text: "#ffffff", label: "Slate Dark (#0f172a)" }, + amber: { bg: "#451a03", text: "#fef3c7", label: "Amber Dark (#451a03)" }, + ocean: { bg: "#082f49", text: "#e0f2fe", label: "Deep Ocean (#082f49)" }, + purple: { bg: "#3b0764", text: "#f3e8ff", label: "Royal Purple (#3b0764)" }, + light: { bg: "#f3f4f6", text: "#111827", label: "Light Gray (#f3f4f6)" }, +}; + +const HOST_FONT_MAP: Record = { + sans: { family: "ui-sans-serif, system-ui, -apple-system, sans-serif", label: "Sans-Serif" }, + serif: { family: "ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif", label: "Serif (Georgia)" }, + mono: { family: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace", label: "Monospace (SFMono)" }, +}; + export function App() { - const [theme, setTheme] = useState("dark"); - const [showLogo, setShowLogo] = useState(true); + const [theme, setTheme] = useState(() => { + return (localStorage.getItem("demo_theme_prop") as Theme) || "auto"; + }); + const [showLogo, setShowLogo] = useState(() => { + return localStorage.getItem("demo_show_logo") !== "false"; + }); + const [customHostBgKey, setCustomHostBgKey] = useState(() => { + return localStorage.getItem("demo_host_bg_key") || "zinc"; + }); + const [customHostFontKey, setCustomHostFontKey] = useState(() => { + return localStorage.getItem("demo_host_font_key") || "sans"; + }); + + useEffect(() => { + localStorage.setItem("demo_theme_prop", theme); + }, [theme]); + + useEffect(() => { + localStorage.setItem("demo_show_logo", String(showLogo)); + }, [showLogo]); + + useEffect(() => { + localStorage.setItem("demo_host_bg_key", customHostBgKey); + }, [customHostBgKey]); + + useEffect(() => { + localStorage.setItem("demo_host_font_key", customHostFontKey); + }, [customHostFontKey]); + + const activeBg = HOST_BG_MAP[customHostBgKey] || HOST_BG_MAP.zinc; + const activeFont = HOST_FONT_MAP[customHostFontKey] || HOST_FONT_MAP.sans; const sampleProps: supportUsButtonProps = { Theme: theme, @@ -34,30 +93,86 @@ export function App() { }; return ( - + {/* Top Controls Bar */} - + - - SupportUsButton — Dev Preview + + {DEMO_TEXT.headerTitle} - + - Theme: - setTheme(theme === "dark" ? "light" : "dark")} - className="px-4 py-2 rounded-lg text-sm font-semibold transition-all duration-200 border border-amber-500/40 bg-amber-500/10 hover:bg-amber-500 hover:text-black text-amber-400 cursor-pointer active:scale-95 shadow-sm hover:shadow-amber-500/25" + + {DEMO_TEXT.themeLabel} + + setTheme(e.target.value as Theme)} + className="px-3 py-1.5 rounded-lg text-sm font-semibold bg-zinc-800 text-amber-400 border border-zinc-700 cursor-pointer focus:outline-none focus:ring-2 focus:ring-amber-500" > - {theme === "dark" ? "☀️ Light Mode" : "🌙 Dark Mode"} - + {DEMO_TEXT.options.auto} + {DEMO_TEXT.options.inherit} + {DEMO_TEXT.options.light} + {DEMO_TEXT.options.dark} + + + + + + {DEMO_TEXT.hostBgLabel} + + setCustomHostBgKey(e.target.value)} + className="px-3 py-1.5 rounded-lg text-sm font-semibold bg-zinc-800 text-blue-400 border border-zinc-700 cursor-pointer focus:outline-none focus:ring-2 focus:ring-blue-500" + > + {Object.entries(HOST_BG_MAP).map(([key, item]) => ( + + {item.label} + + ))} + + + + + + {DEMO_TEXT.hostFontLabel} + + setCustomHostFontKey(e.target.value)} + className="px-3 py-1.5 rounded-lg text-sm font-semibold bg-zinc-800 text-emerald-400 border border-zinc-700 cursor-pointer focus:outline-none focus:ring-2 focus:ring-emerald-500" + > + {Object.entries(HOST_FONT_MAP).map(([key, item]) => ( + + {item.label} + + ))} + - BG Logo: + + {DEMO_TEXT.bgLogoLabel} + setShowLogo(!showLogo)} - className={`px-4 py-2 rounded-lg text-sm font-semibold transition-all duration-200 border cursor-pointer active:scale-95 shadow-sm ${ + className={`px-4 py-1.5 rounded-lg text-sm font-semibold transition-all duration-200 border cursor-pointer active:scale-95 shadow-sm ${ showLogo ? "bg-emerald-500/20 border-emerald-500/50 text-emerald-400 hover:bg-emerald-500 hover:text-white hover:shadow-emerald-500/25" : "bg-zinc-800 border-zinc-700 text-gray-400 hover:bg-zinc-700 hover:text-white" diff --git a/package-lock.json b/package-lock.json index 5f207d9..e1b4af4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,8 @@ "rollup-plugin-postcss": "^4.0.2", "tailwindcss": "^4.2.1", "typescript": "^5.9.3", - "vite": "^8.1.5" + "vite": "^8.1.5", + "vitest": "^3.0.5" }, "engines": { "node": ">=14" @@ -115,6 +116,448 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -2674,6 +3117,24 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2734,6 +3195,94 @@ } } }, + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -2763,6 +3312,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/autoprefixer": { "version": "10.4.27", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", @@ -2874,6 +3433,16 @@ "dev": true, "license": "MIT" }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -2908,6 +3477,23 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2925,6 +3511,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3158,6 +3754,34 @@ "dev": true, "license": "MIT" }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -3268,6 +3892,55 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3292,6 +3965,16 @@ "dev": true, "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -3834,6 +4517,13 @@ "dev": true, "license": "MIT" }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -3888,6 +4578,13 @@ "node": ">=4" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", @@ -3994,6 +4691,23 @@ "dev": true, "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4915,6 +5629,13 @@ "node": ">=20.0.0" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/smob": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.1.tgz", @@ -4964,6 +5685,20 @@ "dev": true, "license": "MIT" }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/string-hash": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", @@ -4971,6 +5706,26 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/style-inject": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", @@ -5090,6 +5845,20 @@ "dev": true, "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -5107,6 +5876,36 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5256,6 +6055,104 @@ } } }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/vite/node_modules/lightningcss": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", @@ -5528,6 +6425,208 @@ "type": "opencollective", "url": "https://opencollective.com/parcel" } + }, + "node_modules/vitest": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } } } } diff --git a/package.json b/package.json index 5aab804..746f9a1 100644 --- a/package.json +++ b/package.json @@ -46,9 +46,9 @@ ], "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/index.esm.js", - "require": "./dist/index.cjs.js", - "types": "./dist/index.d.ts" + "require": "./dist/index.cjs.js" }, "./style.css": "./dist/style.css" }, @@ -59,6 +59,8 @@ "build": "npm run build:css && npm run build:js", "dev": "rollup -c -w", "dev:demo": "vite", + "type-check": "tsc --noEmit", + "test": "npm run type-check && vitest run --passWithNoTests", "prepublishOnly": "npm run build" }, "engines": { @@ -84,7 +86,8 @@ "rollup-plugin-postcss": "^4.0.2", "tailwindcss": "^4.2.1", "typescript": "^5.9.3", - "vite": "^8.1.5" + "vite": "^8.1.5", + "vitest": "^3.0.5" }, "peerDependencies": { "react": ">=18", diff --git a/src/__tests__/SupportUsButton.test.ts b/src/__tests__/SupportUsButton.test.ts new file mode 100644 index 0000000..9331883 --- /dev/null +++ b/src/__tests__/SupportUsButton.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from "vitest"; +import SupportUsButton from "../index"; +import type { Theme, supportUsButtonProps } from "../types/index"; + +describe("SupportUsButton Unit & Contract Tests", () => { + it("should export default SupportUsButton React component", () => { + expect(SupportUsButton).toBeDefined(); + expect(typeof SupportUsButton).toBe("function"); + }); + + it("should support auto, inherit, light, and dark Theme modes", () => { + const validThemes: Theme[] = ["auto", "inherit", "light", "dark"]; + expect(validThemes).toContain("auto"); + expect(validThemes).toContain("inherit"); + expect(validThemes).toContain("light"); + expect(validThemes).toContain("dark"); + }); + + it("should structure supportUsButtonProps cleanly", () => { + const sampleProps: supportUsButtonProps = { + Theme: "auto", + Logo: true, + organizationInformation: { + name: "AOSSIE", + desc: "Australian Open Source Software Innovation and Education organization", + image: "/aossie_logomark.svg", + link: "https://aossie.org", + }, + ctaSection: { + sponsorLink: [ + { name: "Sponsor Us", url: "https://github.com/sponsors/AOSSIE-Org" }, + ], + }, + }; + + expect(sampleProps.organizationInformation.name).toBe("AOSSIE"); + expect(sampleProps.Theme).toBe("auto"); + }); +}); diff --git a/src/components/SupportUsButton.tsx b/src/components/SupportUsButton.tsx index fe18b50..7dbe0c9 100644 --- a/src/components/SupportUsButton.tsx +++ b/src/components/SupportUsButton.tsx @@ -1,16 +1,53 @@ -import React from "react"; +import React, { useRef } from "react"; import type { supportUsButtonProps } from "../types/index"; import type { Theme } from "../types/index"; +import { useParentStyles } from "../hooks/useParentStyles"; -// Function to get the appropriate classes based on the selected theme, used for styling different sections of the component according to the chosen theme +function sRgbLuminance(c: number): number { + const norm = c / 255; + return norm <= 0.04045 ? norm / 12.92 : Math.pow((norm + 0.055) / 1.055, 2.4); +} + +function isDarkColor(colorStr?: string): boolean { + if (!colorStr) return true; + let r = 0, g = 0, b = 0; + const rgbMatch = colorStr.match(/\d+/g); + if (rgbMatch && rgbMatch.length >= 3) { + r = parseInt(rgbMatch[0], 10); + g = parseInt(rgbMatch[1], 10); + b = parseInt(rgbMatch[2], 10); + } else if (colorStr.startsWith("#")) { + const hex = colorStr.replace("#", ""); + if (hex.length === 3) { + r = parseInt(hex[0] + hex[0], 16); + g = parseInt(hex[1] + hex[1], 16); + b = parseInt(hex[2] + hex[2], 16); + } else if (hex.length === 6) { + r = parseInt(hex.substring(0, 2), 16); + g = parseInt(hex.substring(2, 4), 16); + b = parseInt(hex.substring(4, 6), 16); + } else { + return true; + } + } else { + return true; + } + // WCAG relative luminance formula: L = 0.2126 * R + 0.7152 * G + 0.0722 * B + const lum = 0.2126 * sRgbLuminance(r) + 0.7152 * sRgbLuminance(g) + 0.0722 * sRgbLuminance(b); + return lum < 0.179; +} + +// Function to get the appropriate classes based on the selected theme function classAccordingToTheme(Theme: Theme): string { switch (Theme) { case "light": return "bg-[#F4F4F4] text-[#191919]"; case "dark": return "bg-[#191919] text-[#F4F4F4]"; + case "inherit": + case "auto": default: - return "bg-black text-white"; + return "bg-transparent text-inherit"; } } @@ -26,7 +63,7 @@ function validateUrl(url?: string): string | undefined { // Main component function that renders the support us button, taking in various props for customization and rendering different sections such as hero, organization information, sponsors, and call-to-action based on the provided data and selected theme and button variant function SupportUsButton({ - Theme = "dark", + Theme = "auto", organizationInformation, sponsors, ctaSection, @@ -44,33 +81,50 @@ function SupportUsButton({ RightY2: "1000", }, }: supportUsButtonProps): React.JSX.Element { + const containerRef = useRef(null); + const isAuto = Theme === "auto" || Theme === "inherit"; + const parentStyles = useParentStyles(containerRef, isAuto); + const darkThemeActive = + Theme === "dark" || (isAuto && isDarkColor(parentStyles.backgroundColor)); + const validatedUrl = validateUrl(organizationInformation?.link); return ( {Logo && ( - - - + + + + + )} - + {/* Border around page - wrapped around content */} @@ -195,7 +249,7 @@ function SupportUsButton({ /> - + - + {ctaSection.sponsorLink.map((link, index) => ( { if (validateUrl(link.url)) { - window.open(link.url, "_blank"); + window.open(link.url, "_blank", "noopener,noreferrer"); } }} > @@ -277,13 +331,14 @@ function SupportUsButton({ {projectInformation?.name && ( - - - + + + Supported By Global - Powerhouses + Powerhouses - + {sponsors?.map((sponsor, index) => ( , + enabled: boolean = true, + fallbackBg: string = "#191919", + fallbackFont: string = "inherit" +): ParentStyles { + const [styles, setStyles] = useState({ + backgroundColor: fallbackBg, + fontFamily: fallbackFont, + color: "inherit", + }); + + useIsomorphicLayoutEffect(() => { + if (!enabled || typeof window === "undefined" || !containerRef.current) { + return; + } + + const element = containerRef.current; + + const detectStyles = () => { + let parent: HTMLElement | null = element.parentElement; + let detectedBg: string | undefined = undefined; + let detectedFont: string | undefined = undefined; + let detectedColor: string | undefined = undefined; + + while (parent && parent !== document.documentElement) { + try { + const computed = window.getComputedStyle(parent); + + if (!detectedFont && computed.fontFamily && computed.fontFamily !== "") { + detectedFont = computed.fontFamily; + } + + if (!detectedColor && computed.color && isNonTransparent(computed.color)) { + detectedColor = computed.color; + } + + if (!detectedBg && isNonTransparent(computed.backgroundColor)) { + detectedBg = computed.backgroundColor; + break; // Stop at nearest non-transparent parent node + } + } catch { + break; + } + + parent = parent.parentElement; + } + + // Fallback to body / html background if parent chain was fully transparent + if (!detectedBg && typeof document !== "undefined") { + try { + const bodyBg = window.getComputedStyle(document.body).backgroundColor; + if (isNonTransparent(bodyBg)) { + detectedBg = bodyBg; + } else { + const htmlBg = window.getComputedStyle(document.documentElement).backgroundColor; + if (isNonTransparent(htmlBg)) { + detectedBg = htmlBg; + } + } + } catch { + // Fallback default + } + } + + const nextBg = detectedBg || fallbackBg; + const nextFont = detectedFont || fallbackFont; + const nextColor = detectedColor || "inherit"; + + setStyles((prev) => { + if ( + prev.backgroundColor === nextBg && + prev.fontFamily === nextFont && + prev.color === nextColor + ) { + return prev; + } + return { + backgroundColor: nextBg, + fontFamily: nextFont, + color: nextColor, + }; + }); + }; + + // Synchronous layout detection + detectStyles(); + + window.addEventListener("resize", detectStyles); + + // Observe parent element & body for dynamic class/style mutations (e.g. host theme toggles) + const observer = new MutationObserver(() => { + detectStyles(); + }); + + if (element.parentElement) { + observer.observe(element.parentElement, { + attributes: true, + attributeFilter: ["class", "style"], + childList: true, + subtree: true, + }); + } + observer.observe(document.body, { + attributes: true, + attributeFilter: ["class", "style"], + }); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["class", "style"], + }); + + return () => { + window.removeEventListener("resize", detectStyles); + observer.disconnect(); + }; + }, [enabled]); + + return styles; +} diff --git a/src/styles/style.css b/src/styles/style.css index c9aeb63..64835a8 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -20,6 +20,43 @@ .md-justify-between { @media (width >= 900px) { flex-direction: row; - } } + +@keyframes sub-fade-in-up { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes sub-scale-in { + from { + opacity: 0; + transform: scale(0.94); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.animate-sub-fade-in { + animation: sub-fade-in-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +.animate-sub-scale-in { + animation: sub-scale-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +@media (prefers-reduced-motion: reduce) { + .animate-sub-fade-in, + .animate-sub-scale-in { + animation: none !important; + } +} + diff --git a/src/types/index.ts b/src/types/index.ts index 7263183..88be9a9 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -2,7 +2,7 @@ Theme ========================= */ -export type Theme = "light" | "dark"; +export type Theme = "auto" | "inherit" | "light" | "dark"; /* ========================= PROJECT INFORMATION