diff --git a/.commitlintrc.json b/.commitlintrc.json new file mode 100644 index 0000000..0df1d25 --- /dev/null +++ b/.commitlintrc.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "@commitlint/config-conventional" + ] +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..53ef2f5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Enforce LF for all text files +* text=auto eol=lf + +# Keep Windows command scripts with CRLF line endings +*.bat text eol=crlf +*.cmd text eol=crlf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..958f06c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,80 @@ +name: CI + +on: + push: + branches: + - develop + - 'feature/**' + pull_request: + branches: + - develop + - 'feature/**' + workflow_call: + inputs: + full: + description: 'Run full OS x Node matrix' + required: false + type: boolean + default: false + +jobs: + compute-matrix: + name: Compute matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set.outputs.matrix }} + name_suffix: ${{ steps.set.outputs.name_suffix }} + steps: + - id: set + run: | + if [[ "${{ inputs.full }}" == "true" ]]; then + echo 'matrix={"os":["ubuntu-latest"],"node":[22]}' >> $GITHUB_OUTPUT + echo 'name_suffix=(full matrix)' >> $GITHUB_OUTPUT + else + echo 'matrix={"os":["ubuntu-latest"],"node":[22]}' >> $GITHUB_OUTPUT + echo 'name_suffix=' >> $GITHUB_OUTPUT + fi + + testing: + name: Testing ${{ needs.compute-matrix.outputs.name_suffix }} + needs: compute-matrix + runs-on: ${{ matrix.os }} + permissions: + contents: read + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.compute-matrix.outputs.matrix) }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure git EOL (Windows) + if: runner.os == 'Windows' + run: | + git config core.autocrlf false + git config core.eol lf + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Typecheck + run: npm run typecheck + + - name: Test + run: npm run test:ci + + - name: Upload coverage artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: coverage-${{ matrix.os }}-node${{ matrix.node }} + path: coverage diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ae5436e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,97 @@ +name: Release + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + version: + description: 'Exact version to release (e.g. 1.2.3). Leave empty to auto-bump.' + required: false + type: string + preid: + description: 'Pre-release identifier (e.g. beta, rc). Optional' + required: false + type: string + npm_tag: + description: 'npm dist-tag (e.g. latest, beta)' + required: false + default: 'latest' + type: string + +permissions: + contents: write + id-token: write + +jobs: + ci: + name: CI + uses: ./.github/workflows/ci.yml + with: + full: true + + release: + name: Release & Publish + runs-on: ubuntu-latest + needs: ci + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + always-auth: true + + - name: Install dependencies + run: npm ci + + - name: Configure Git user + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Run release-it + env: + DEBUG: release-it:*,@release-it/* + HUSKY: 0 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + VERSION_ARG="" + if [ -n "${{ inputs.version }}" ]; then + VERSION_ARG="${{ inputs.version }}" + fi + + PREID_ARG="" + if [ -n "${{ inputs.preid }}" ]; then + PREID_ARG="--preRelease=${{ inputs.preid }}" + fi + + if [ -n "${{ inputs.npm_tag }}" ]; then + NPM_TAG="${{ inputs.npm_tag }}" + else + NPM_TAG="latest" + fi + NPM_TAG_ARG="--npm.tag=${NPM_TAG}" + + npm run release -- --ci $PREID_ARG $NPM_TAG_ARG $VERSION_ARG + + - name: Sync main β†’ develop + uses: devmasx/merge-branch@v1.4.0 + with: + type: now + from_branch: main + target_branch: develop + github_token: ${{ secrets.GITHUB_TOKEN }} + env: + HUSKY: 0 + diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..1cfb642 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +# Husky commit-msg hook: validate commit message with commitlint + +npx --no -- commitlint --edit "$1" diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..ae97c4f --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,6 @@ +#!/usr/bin/env sh + +# Husky pre-commit hook: format and run related tests on staged files + +npm run test:related +npm run format diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 0000000..edf3235 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +# Husky pre-push hook: typecheck, run full tests, and build + +npm run typecheck || exit 1 +npm run lint || exit 1 +#npm run test:ci || exit 1 +#npm run build || exit 1 diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..2f327b1 --- /dev/null +++ b/.mailmap @@ -0,0 +1,2 @@ +Addon Stack <191148085+addon-stack@users.noreply.github.com> +Addon Stack \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index a7db0d7..272aca5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ node_modules/ dist/ -build/ \ No newline at end of file +build/ +.github/workflows/*.yml \ No newline at end of file diff --git a/.release-it.cjs b/.release-it.cjs new file mode 100644 index 0000000..572c7c3 --- /dev/null +++ b/.release-it.cjs @@ -0,0 +1,256 @@ +const {execSync} = require("node:child_process"); +const pkg = require("./package.json"); + +function deriveGithubFromEmail(email) { + if (!email) { + return {}; + } + + const m = email.match(/^([^@]+)@users\.noreply\.github\.com$/i); + + if (!m) { + return {}; + } + + const local = m[1]; + const login = local.includes("+") ? local.split("+").pop() : local; + + if (!login) { + return {}; + } + + return {login, url: `https://github.com/${login}`}; +} + +function getContributors() { + try { + let fromTag; + + try { + const tag = execSync("git describe --tags --abbrev=0", {encoding: "utf8"}).trim(); + fromTag = tag || null; + } catch { + fromTag = null; + } + + const range = fromTag ? `${fromTag}..HEAD` : ""; + const cmd = `git shortlog -sne ${range}`.trim(); + const out = execSync(cmd, {encoding: "utf8"}).trim(); + + if (!out) { + return []; + } + + const lines = out.split("\n").filter(Boolean); + + const map = new Map(); + + for (const line of lines) { + const m = line.match(/^\s*(\d+)\s+(.*?)(?:\s+<([^>]+)>)?\s*$/); + + const count = Number(m?.[1] || 0); + + const displayName = (m?.[2] || "").trim() || undefined; + const displayEmail = (m?.[3] || "").trim() || undefined; + + const lcName = (displayName || "").toLowerCase(); + const lcEmail = (displayEmail || "").toLowerCase(); + + // Filter bots using lowercase keys only + if (lcName.includes("bot") || lcEmail.includes("bot")) { + continue; + } + + const gh = deriveGithubFromEmail(displayEmail); + const loginKey = gh.login ? gh.login.toLowerCase() : null; + + const key = loginKey ? `gh:${loginKey}` : lcEmail ? `em:${lcEmail}` : `nm:${lcName}`; + + const existing = map.get(key); + + if (existing) { + existing.count += count; + if (!existing.login && gh.login) { + existing.login = gh.login; + existing.url = gh.url; + } + if (!existing.name && displayName) { + existing.name = displayName; + } + if (!existing.email && displayEmail) { + existing.email = displayEmail; + } + } else { + map.set(key, {count, name: displayName, email: displayEmail, ...gh}); + } + } + + return Array.from(map.values()); + } catch { + return []; + } +} + +const types = new Map([ + ["feat", "✨ Features"], + ["fix", "πŸ› Bug Fixed"], + ["perf", "⚑️ Performance Improvements"], + ["refactor", "πŸ› οΈ Refactoring"], + ["docs", "πŸ“ Documentation"], + ["test", "πŸ§ͺ Tests"], + ["build", "πŸ—οΈ Build System"], + ["ci", "πŸ€– CI"], + ["chore", "🧹 Chores"], + ["revert", "βͺ Reverts"], +]); + +const normalizeRepoUrl = url => url.replace(/^git\+/, "").replace(/\.git$/, ""); +const repoUrl = pkg?.repository?.url ? normalizeRepoUrl(pkg.repository.url) : null; + +module.exports = () => { + const contributors = getContributors(); + + return { + ci: true, + + git: { + requireCleanWorkingDir: true, + requireUpstream: false, + requireBranch: false, + commit: true, + // biome-ignore lint/suspicious/noTemplateCurlyInString: release-it placeholder + commitMessage: "chore(release): v${version}", + tag: true, + // biome-ignore lint/suspicious/noTemplateCurlyInString: release-it placeholder + tagName: "v${version}", + // biome-ignore lint/suspicious/noTemplateCurlyInString: release-it placeholder + tagAnnotation: "v${version}", + push: true, + }, + + github: { + release: true, + // biome-ignore lint/suspicious/noTemplateCurlyInString: release-it placeholder + releaseName: "v${version}", + autoGenerate: false, + // Ensure GitHub receives exactly the generated changelog body + releaseNotes: ({changelog}) => changelog, + }, + + npm: { + publish: true, + versionArgs: ["--no-git-tag-version"], + publishArgs: ["--provenance", "--access", "public"], + }, + + plugins: { + "@release-it/conventional-changelog": { + infile: "CHANGELOG.md", + preset: "conventionalcommits", + + parserOpts: { + headerPattern: /^(\w+)(?:\(([^)]+)\))?(!)?:\s(.+?)(?:\s\(#\d+\))?$/, + headerCorrespondence: ["type", "scope", "breaking", "subject"], + noteKeywords: ["BREAKING CHANGE", "BREAKING-CHANGE"], + }, + + presetConfig: { + types: [...types.entries()].map(([type, section]) => ({type, section, hidden: false})), + }, + + context: { + name: pkg.name, + pkg: {name: pkg.name}, + repoUrl, + contributors, + }, + + recommendedBumpOpts: { + preset: "conventionalcommits", + whatBump: commits => { + let isMajor = false; + let isMinor = false; + let isPatch = false; + + for (const commit of commits) { + if (commit.notes?.some(n => /BREAKING CHANGE/i.test(n.title || n.text || ""))) { + isMajor = true; + break; + } + + const type = (commit.type || "").toLowerCase(); + + if (type === "feat") { + isMinor = true; + } + + if (["fix", "perf", "refactor", "ci"].includes(type)) { + isPatch = true; + } + } + + if (isMajor) return {level: 0}; + if (isMinor) return {level: 1}; + if (isPatch) return {level: 2}; + + return null; + }, + }, + writerOpts: { + headerPartial: + "## πŸš€ Release {{#if name}}`{{name}}` {{else}}{{#if @root.pkg}}`{{@root.pkg.name}}` {{/if}}{{/if}}v{{version}} ({{date}})\n\n", + footerPartial: `{{#if @root.contributors.length}}\n### πŸ™Œ Contributors\n\n{{#each @root.contributors}}- {{#if url}}{{#if name}}[{{name}}]({{url}}){{#if login}} (@{{login}}){{/if}}{{else}}[@{{login}}]({{url}}){{/if}}{{else}}{{#if email}}{{#if name}}[{{name}}](mailto:{{email}}){{else}}{{email}}{{/if}}{{else}}{{name}}{{/if}}{{/if}} β€” commits: {{count}}\n{{/each}}{{/if}}`, + mainTemplate: + "{{> header}}\n" + + "{{#if noteGroups}}\n### πŸ’₯ Breaking Changes\n\n{{#each noteGroups}}{{#each notes}}* {{{text}}}\n\n{{/each}}{{/each}}{{/if}}" + + "{{#each commitGroups}}\n### {{title}}\n\n{{#each commits}}{{> commit root=@root}}\n{{/each}}\n\n{{/each}}" + + "{{#unless commitGroups}}\n{{#each commits}}{{> commit root=@root}}\n{{/each}}{{/unless}}\n\n" + + "{{> footer}}", + commitPartial: + "{{#if type}}* {{#if scope}}**{{scope}}:** {{/if}}{{#if subject}}{{subject}}{{else}}{{header}}{{/if}}{{#if href}} ([{{shorthash}}]({{href}})){{/if}}\n\n{{#if body}}{{{body}}}\n{{/if}}{{/if}}", + groupBy: "type", + commitGroupsSort: "title", + commitsSort: ["scope", "subject"], + transform: commit => { + const nextCommit = {...commit}; + + // If header had a '!' (captured into `breaking` by parser), ensure we surface a BREAKING note + if (nextCommit.breaking && (!nextCommit.notes || nextCommit.notes.length === 0)) { + const text = nextCommit.subject || nextCommit.header; + nextCommit.notes = [{title: "BREAKING CHANGE", text}]; + } + + // Normalize type: lowercase and drop trailing '!' so 'feat!' maps to 'feat' + const type = (nextCommit.type || "").toLowerCase().replace(/!+$/, ""); + const section = types.get(type); + + if (section) { + nextCommit.type = section; + } else { + nextCommit.type = "🧩 Other"; + } + + if (nextCommit.body) { + const body = nextCommit.body.replace(/\r\n/g, "\n").trim(); + + nextCommit.body = body + .split("\n") + .map(line => (line ? ` ${line}` : "")) + .join("\n"); + } + + if (!nextCommit.href && nextCommit.hash && repoUrl) { + nextCommit.href = `${repoUrl}/commit/${nextCommit.hash}`; + } + + if (!nextCommit.shorthash && nextCommit.hash) { + nextCommit.shorthash = nextCommit.hash.slice(0, 7); + } + + return nextCommit; + }, + }, + }, + }, + }; +}; diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..de853b6 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2025 Addon Stack; Anjey Tsibylskij + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 532a561..2f32648 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Addon UI (addon-ui) +# addon-ui [![npm version](https://img.shields.io/npm/v/addon-ui.svg)](https://www.npmjs.com/package/addon-ui) [![npm downloads](https://img.shields.io/npm/dm/addon-ui.svg)](https://www.npmjs.com/package/addon-ui) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) -A comprehensive UI component library designed for the Addon Bone framework. +Addon UI - A comprehensive UI component library designed for the Addon Bone framework. This library provides a set of customizable React components with theming capabilities to build modern, responsive user interfaces. @@ -19,55 +19,72 @@ responsive user interfaces. ## Table of Contents - [Installation](#installation) +- [Components](#components) - [Basic Usage](#basic-usage) - [Integration](#integration) - [Customization](#customization) - [Using Extra Props](#using-extra-props) -- [Component Examples](#component-examples) -- [Supported Components](#supported-components) - - [Avatar](#avatar) - - [BaseButton](#basebutton) - - [Button](#button) - - [Checkbox](#checkbox) - - [Dialog](#dialog) - - [Drawer](#drawer) - - [Footer](#footer) - - [Header](#header) - - [Highlight](#highlight) - - [Icon](#icon) - - [IconButton](#iconbutton) - - [Layout](#layout) - - [List](#list) - - [ListItem](#listitem) - - [Modal](#modal) - - [Odometer](#odometer) - - [ScrollArea](#scrollarea) - - [SvgSprite](#svgsprite) - - [Switch](#switch) - - [Tag](#tag) - - [TextArea](#textarea) - - [TextField](#textfield) - - [Toast](#toast) - - [Tooltip](#tooltip) - - [View](#view) - - [ViewDrawer](#viewdrawer) - - [ViewModal](#viewmodal) -- [License](#license) +- [Theming and style reuse](#theming-and-style-reuse) +- [Radix UI and third-party integrations](#radix-ui-and-third-party-integrations) +- [Icons and sprite](#icons-and-sprite) +- [Extra props](#extra-props-cross-cutting-configuration) +- [Contributing](#contributing) ## Installation -Using npm: +### npm: ```bash npm install addon-ui ``` -Using Yarn: +### pnpm: + +```bash +pnpm add addon-ui +``` + +### yarn: ```bash yarn add addon-ui ``` +## Components + +This library now ships with dedicated documentation files for each component in the docs/ directory. Start here: + +- [Avatar](docs/Avatar.md) +- [Button](docs/Button.md) +- [Checkbox](docs/Checkbox.md) +- [Dialog](docs/Dialog.md) +- [Drawer](docs/Drawer.md) +- [Footer](docs/Footer.md) +- [Header](docs/Header.md) +- [Highlight](docs/Highlight.md) +- [Icon](docs/Icon.md) +- [IconButton](docs/IconButton.md) +- [List](docs/List.md) (covers List and ListItem) +- [Modal](docs/Modal.md) +- [Odometer](docs/Odometer.md) (component + useOdometer hook) +- [ScrollArea](docs/ScrollArea.md) +- [SvgSprite](docs/SvgSprite.md) +- [Switch](docs/Switch.md) +- [Tag](docs/Tag.md) +- [TextArea](docs/TextArea.md) +- [TextField](docs/TextField.md) +- [Toast](docs/Toast.md) +- [View](docs/View.md) +- [ViewDrawer](docs/ViewDrawer.md) +- [ViewModal](docs/ViewModal.md) +- [Viewport](docs/Viewport.md) (ViewportProvider + useViewport) + +Notes: + +- Each CSS variables table lists only component-scoped variables with exact fallback chains from the corresponding \* + .module.scss file. +- Where a component wraps a Radix UI primitive, the doc links to the official Radix docs and lists common props. + ## Basic Usage ```jsx @@ -92,9 +109,13 @@ export default App; ## Integration -**Addon UI** is designed exclusively for the Addon Bone framework and does not have a standalone build as it's connected as a plugin. This library is an integral part of the Addon Bone ecosystem for developing browser extensions with a shared codebase. +**Addon UI** is designed exclusively for the [Addon Bone](https://addonbone.com) framework and does not have a standalone build as it's connected +as a plugin. This library is an integral part of the Addon Bone ecosystem for developing browser extensions with a +shared codebase. -Addon Bone is a framework for developing browser extensions with a common codebase. This means you can create multiple extensions with the same functionality but with different designs, localizations, and feature sets depending on the needs of each extension while maintaining access to a shared codebase. +[Addon Bone](https://addonbone.com) is a framework for developing browser extensions with a common codebase. This means you can create multiple +extensions with the same functionality but with different designs, localizations, and feature sets depending on the +needs of each extension while maintaining access to a shared codebase. ### Plugin Setup @@ -118,16 +139,22 @@ export default defineConfig({ ### Configuration Files -The `addon-ui` configuration is designed to retrieve configuration from each extension separately, allowing for different designs for different extensions without changing any code. You only need to modify the configuration, style variables, and icons. +The `addon-ui` configuration is designed to retrieve configuration from each extension separately, allowing for +different designs for different extensions without changing any code. You only need to modify the configuration, style +variables, and icons. -The plugin looks for configuration files in specific directories within your project. By default, it searches in the following locations (in order of priority): +The plugin looks for configuration files in specific directories within your project. By default, it searches in the +following locations (in order of priority): 1. **App-specific directory**: `src/apps/[app-name]/[app-src-dir]/[theme-dir]` 2. **Shared directory**: `src/shared/[theme-dir]` Where `[theme-dir]` is the directory specified in the `themeDir` option (defaults to the current directory). -The `mergeConfig` option (default: `true`) determines whether configurations from multiple directories should be merged. When enabled, configurations from both app-specific and shared directories will be combined, with app-specific values taking precedence in case of conflicts. If disabled, only the first configuration found will be used (with app-specific having priority). +The `mergeConfig` option (default: `true`) determines whether configurations from multiple directories should be merged. +When enabled, configurations from both app-specific and shared directories will be combined, with app-specific values +taking precedence in case of conflicts. If disabled, only the first configuration found will be used (with app-specific +having priority). You can create these files to customize the UI components: @@ -169,7 +196,9 @@ The configuration can also include SVG icons imported directly from your project #### ui.style.scss -Similar to configuration files, style files are also searched for in the same directories with the same priority order. The `mergeStyles` option (default: `true`) works the same way as `mergeConfig`, allowing styles from multiple directories to be combined when enabled. +Similar to configuration files, style files are also searched for in the same directories with the same priority order. +The `mergeStyles` option (default: `true`) works the same way as `mergeConfig`, allowing styles from multiple +directories to be combined when enabled. ```scss // src/shared/theme/ui.style.scss @@ -230,7 +259,9 @@ Similar to configuration files, style files are also searched for in the same di ## Customization -The `addon-ui` library allows for extensive customization to create different designs for different extensions without changing code. This is particularly useful in the Addon Bone framework where you might need to maintain multiple browser extensions with the same functionality but different visual appearances. +The `addon-ui` library allows for extensive customization to create different designs for different extensions without +changing code. This is particularly useful in the Addon Bone framework where you might need to maintain multiple browser +extensions with the same functionality but different visual appearances. ### Global Theme Customization @@ -261,11 +292,14 @@ function App() { ### Using Extra Props -Extra Props is a powerful feature that allows you to extend component props with custom properties. This is particularly useful when you need to add custom functionality or data to components across your application without modifying the original component code. +Extra Props is a powerful feature that allows you to extend component props with custom properties. This is particularly +useful when you need to add custom functionality or data to components across your application without modifying the +original component code. #### What are Extra Props? -Extra Props provide a way to pass additional properties to components throughout your application using React Context. This allows you to: +Extra Props provide a way to pass additional properties to components throughout your application using React Context. +This allows you to: - Add application-specific properties to UI components - Share common data across multiple components @@ -317,7 +351,8 @@ function AppHeader() { #### Example Use Case -A common use case for Extra Props is to add application-specific configuration to UI components. For example, you might want to add custom analytics tracking to buttons: +A common use case for Extra Props is to add application-specific configuration to UI components. For example, you might +want to add custom analytics tracking to buttons: ```jsx import {Button, useExtra} from "addon-ui"; @@ -360,7 +395,8 @@ declare module "addon-ui" { } ``` -With this type definition, TypeScript will provide proper type checking and autocompletion when using the `useExtra` hook: +With this type definition, TypeScript will provide proper type checking and autocompletion when using the `useExtra` +hook: ```tsx import React from "react"; @@ -392,806 +428,42 @@ function App() { } ``` -## Component Examples - -### Buttons - -```jsx -import {Button, ButtonColor, ButtonSize, ButtonVariant} from 'addon-ui'; - -// Basic button - - -// Variants - - - - -// Colors - - - - -// Sizes - - - - -// Disabled state - -``` - -### Form Components - -```jsx -import {TextField, TextArea, Checkbox, Switch} from 'addon-ui'; - -// Text input - - -// Text area -