diff --git a/.github/workflows/deploy-storybook.yml b/.github/workflows/deploy-storybook.yml index bd1366b..cfdf898 100644 --- a/.github/workflows/deploy-storybook.yml +++ b/.github/workflows/deploy-storybook.yml @@ -1,56 +1,56 @@ -# Build and deploy Storybook to GitHub Pages -# https://storybook.js.org/docs/react/sharing/publish-storybook#github-pages -name: Deploy Storybook to GitHub Pages - -on: - push: - branches: - - main - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deploy.outputs.page_url }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: "24" - - - name: Install dependencies - run: npm install - - - name: Build Storybook - env: - # Base path for GitHub Pages: https://.github.io// - STORYBOOK_BASE_PATH: /${{ github.event.repository.name }}/ - run: npm run build-storybook - - - name: Upload Storybook artifact - uses: actions/upload-pages-artifact@v3 - with: - path: storybook-static - - - name: Deploy to GitHub Pages - id: deploy - uses: actions/deploy-pages@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} +# Build and deploy Storybook to GitHub Pages +# https://storybook.js.org/docs/react/sharing/publish-storybook#github-pages +name: Deploy Storybook to GitHub Pages + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deploy.outputs.page_url }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Install dependencies + run: npm install + + - name: Build Storybook + env: + # Base path for GitHub Pages: https://.github.io// + STORYBOOK_BASE_PATH: /${{ github.event.repository.name }}/ + run: npm run build-storybook + + - name: Upload Storybook artifact + uses: actions/upload-pages-artifact@v3 + with: + path: storybook-static + + - name: Deploy to GitHub Pages + id: deploy + uses: actions/deploy-pages@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-ui.yml b/.github/workflows/test-ui.yml new file mode 100644 index 0000000..f648ca9 --- /dev/null +++ b/.github/workflows/test-ui.yml @@ -0,0 +1,24 @@ +name: UI Tests + +on: [push] + +jobs: + test: + runs-on: ubuntu-latest + container: + # Using the Playwright image to ensure browsers are available for the test runner + image: mcr.microsoft.com/playwright:v1.52.0-noble + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22.12.0 + + - name: Install dependencies + run: npm install + + # Using test-storybook:ci to build Storybook and run tests against it + - name: Run tests + run: npm run test-storybook:ci diff --git a/.gitignore b/.gitignore index 61c4446..53d1f71 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -package-lock.json -/node_modules -/dist -storybook-static -debug-storybook.log \ No newline at end of file +/node_modules +/dist +storybook-static +debug-storybook.log +config.bat diff --git a/.storybook/main.ts b/.storybook/main.ts index 1c8215b..fed0ab5 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,125 +1,125 @@ -import type { StorybookConfig } from "@storybook/react-webpack5"; -import path from "path"; -import { fileURLToPath } from "url"; -import { createRequire } from "module"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const require = createRequire(import.meta.url); - -// Base path for deployment to subpath (e.g. GitHub Pages: https://owner.github.io/repo/) -const basePath = process.env.STORYBOOK_BASE_PATH || "/"; - -const config: StorybookConfig = { - stories: ["../src/**/*.stories.@(ts|tsx|js|jsx)"], - - addons: [ - "@storybook/addon-a11y", - "@chromatic-com/storybook", - "@storybook/addon-docs" - ], - - framework: { - name: "@storybook/react-webpack5", - options: {}, - }, - - webpackFinal: async (config) => { - // Use relative publicPath when deployed to a subpath (e.g. GitHub Pages) so chunk URLs - // resolve as ./3285....js under the current path instead of /plugin-ui//plugin-ui/... - if (basePath !== "/" && config.output) { - config.output.publicPath = "./"; - } - // Provide React globally so story/component bundles that reference React (e.g. React.createElement) don't throw - const { ProvidePlugin } = require("webpack"); - config.plugins = config.plugins ?? []; - config.plugins.push( - new ProvidePlugin({ - React: [require.resolve("react"), "default"], - }) - ); - config.resolve = config.resolve ?? {}; - - const projectRoot = path.resolve(__dirname, ".."); - - config.resolve.alias = { - ...config.resolve.alias, - "@": path.resolve(projectRoot, "src"), - - // Ensure a SINGLE React instance (prevents hook + context crashes) - react: path.resolve(projectRoot, "node_modules/react"), - "react-dom": path.resolve(projectRoot, "node_modules/react-dom"), - }; - - // -------------------------------------------------- - // POSTCSS (Tailwind) support - // -------------------------------------------------- - const postcssLoader = { - loader: require.resolve("postcss-loader"), - options: { postcssOptions: require("../postcss.config.js") }, - }; - - const rules = config.module?.rules ?? []; - - for (const rule of rules) { - if ( - rule && - typeof rule === "object" && - rule.test instanceof RegExp && - rule.test.test("x.css") - ) { - const use = Array.isArray(rule.use) ? rule.use : [rule.use].filter(Boolean); - - const hasPostcss = use.some( - (u: unknown) => - typeof u === "object" && - u && - "loader" in (u as object) && - String((u as { loader?: string }).loader).includes("postcss") - ); - - if (!hasPostcss) rule.use = [...use, postcssLoader]; - break; - } - } - - // -------------------------------------------------- - // Transpile JSX/TS in story files so "Show code" shows readable JSX. - // Run Babel as a pre-loader so it runs before csf-plugin/export-order on story files. - // -------------------------------------------------- - const babelLoaderForStories = { - loader: require.resolve("babel-loader"), - options: { - presets: [ - [require.resolve("@babel/preset-react"), { runtime: "automatic" }], - [require.resolve("@babel/preset-typescript"), { allowDeclareFields: true }], - ], - }, - }; - config.module?.rules?.unshift({ - test: /\.stories\.(jsx|tsx)$/, - use: [babelLoaderForStories], - enforce: "pre" as const, - }); - - // Transpile TS/TSX from src (components imported by stories) - config.module?.rules?.push({ - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - use: [ - { - loader: require.resolve("babel-loader"), - options: { - presets: [ - [require.resolve("@babel/preset-react"), { runtime: "automatic" }], - [require.resolve("@babel/preset-typescript"), { allowDeclareFields: true }], - ], - }, - }, - ], - }); - - return config; - }, -}; - +import type { StorybookConfig } from "@storybook/react-webpack5"; +import path from "path"; +import { fileURLToPath } from "url"; +import { createRequire } from "module"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const require = createRequire(import.meta.url); + +// Base path for deployment to subpath (e.g. GitHub Pages: https://owner.github.io/repo/) +const basePath = process.env.STORYBOOK_BASE_PATH || "/"; + +const config: StorybookConfig = { + stories: ["../src/**/*.stories.@(ts|tsx|js|jsx)"], + + addons: [ + "@storybook/addon-a11y", + "@chromatic-com/storybook", + "@storybook/addon-docs" + ], + + framework: { + name: "@storybook/react-webpack5", + options: {}, + }, + + webpackFinal: async (config) => { + // Use relative publicPath when deployed to a subpath (e.g. GitHub Pages) so chunk URLs + // resolve as ./3285....js under the current path instead of /plugin-ui//plugin-ui/... + if (basePath !== "/" && config.output) { + config.output.publicPath = "./"; + } + // Provide React globally so story/component bundles that reference React (e.g. React.createElement) don't throw + const { ProvidePlugin } = require("webpack"); + config.plugins = config.plugins ?? []; + config.plugins.push( + new ProvidePlugin({ + React: [require.resolve("react"), "default"], + }) + ); + config.resolve = config.resolve ?? {}; + + const projectRoot = path.resolve(__dirname, ".."); + + config.resolve.alias = { + ...config.resolve.alias, + "@": path.resolve(projectRoot, "src"), + + // Ensure a SINGLE React instance (prevents hook + context crashes) + react: path.resolve(projectRoot, "node_modules/react"), + "react-dom": path.resolve(projectRoot, "node_modules/react-dom"), + }; + + // -------------------------------------------------- + // POSTCSS (Tailwind) support + // -------------------------------------------------- + const postcssLoader = { + loader: require.resolve("postcss-loader"), + options: { postcssOptions: require("../postcss.config.js") }, + }; + + const rules = config.module?.rules ?? []; + + for (const rule of rules) { + if ( + rule && + typeof rule === "object" && + rule.test instanceof RegExp && + rule.test.test("x.css") + ) { + const use = Array.isArray(rule.use) ? rule.use : [rule.use].filter(Boolean); + + const hasPostcss = use.some( + (u: unknown) => + typeof u === "object" && + u && + "loader" in (u as object) && + String((u as { loader?: string }).loader).includes("postcss") + ); + + if (!hasPostcss) rule.use = [...use, postcssLoader]; + break; + } + } + + // -------------------------------------------------- + // Transpile JSX/TS in story files so "Show code" shows readable JSX. + // Run Babel as a pre-loader so it runs before csf-plugin/export-order on story files. + // -------------------------------------------------- + const babelLoaderForStories = { + loader: require.resolve("babel-loader"), + options: { + presets: [ + [require.resolve("@babel/preset-react"), { runtime: "automatic" }], + [require.resolve("@babel/preset-typescript"), { allowDeclareFields: true }], + ], + }, + }; + config.module?.rules?.unshift({ + test: /\.stories\.(jsx|tsx)$/, + use: [babelLoaderForStories], + enforce: "pre" as const, + }); + + // Transpile TS/TSX from src (components imported by stories) + config.module?.rules?.push({ + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + use: [ + { + loader: require.resolve("babel-loader"), + options: { + presets: [ + [require.resolve("@babel/preset-react"), { runtime: "automatic" }], + [require.resolve("@babel/preset-typescript"), { allowDeclareFields: true }], + ], + }, + }, + ], + }); + + return config; + }, +}; + export default config; \ No newline at end of file diff --git a/.storybook/preview.js b/.storybook/preview.js index 59b3a31..68dfd37 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1,51 +1,51 @@ -import React from "react"; -import { ThemeProvider } from "../src/providers"; -import "../src/styles.css"; - -// Ensure React is available in the story iframe (fixes "React is not defined" when story bundles load) -if (typeof window !== "undefined") { - window.React = React; -} - -export const parameters = { - controls: { - matchers: { color: /(background|color)$/i, date: /Date$/i }, - expanded: true, - }, - layout: "centered", - // Accessibility (axe-core). See https://storybook.js.org/docs/writing-tests/accessibility-testing - a11y: { - config: {}, - options: {}, - // 'error' = fail tests on violations; 'todo' = warn only; 'off' = skip - test: "error", - }, - viewport: { - viewports: { - mobile: { - name: "Mobile", - styles: { width: "375px", height: "667px" }, - type: "mobile", - }, - tablet: { - name: "Tablet", - styles: { width: "768px", height: "1024px" }, - type: "tablet", - }, - desktop: { - name: "Desktop", - styles: { width: "1280px", height: "800px" }, - type: "desktop", - }, - }, - }, -}; - -export const decorators = [ - (Story) => - React.createElement( - ThemeProvider, - { pluginId: "storybook" }, - React.createElement("div", { className: "pui-root min-h-[200px] p-6" }, React.createElement(Story)) - ), -]; +import React from "react"; +import { ThemeProvider } from "../src/providers"; +import "../src/styles.css"; + +// Ensure React is available in the story iframe (fixes "React is not defined" when story bundles load) +if (typeof window !== "undefined") { + window.React = React; +} + +export const parameters = { + controls: { + matchers: { color: /(background|color)$/i, date: /Date$/i }, + expanded: true, + }, + layout: "centered", + // Accessibility (axe-core). See https://storybook.js.org/docs/writing-tests/accessibility-testing + a11y: { + config: {}, + options: {}, + // 'error' = fail tests on violations; 'todo' = warn only; 'off' = skip + test: "error", + }, + viewport: { + viewports: { + mobile: { + name: "Mobile", + styles: { width: "375px", height: "667px" }, + type: "mobile", + }, + tablet: { + name: "Tablet", + styles: { width: "768px", height: "1024px" }, + type: "tablet", + }, + desktop: { + name: "Desktop", + styles: { width: "1280px", height: "800px" }, + type: "desktop", + }, + }, + }, +}; + +export const decorators = [ + (Story) => + React.createElement( + ThemeProvider, + { pluginId: "storybook" }, + React.createElement("div", { className: "pui-root min-h-[200px] p-6" }, React.createElement(Story)) + ), +]; diff --git a/.storybook/test-runner.ts b/.storybook/test-runner.ts new file mode 100644 index 0000000..c7517ff --- /dev/null +++ b/.storybook/test-runner.ts @@ -0,0 +1,27 @@ +import { getStoryContext, TestRunnerConfig } from '@storybook/test-runner'; +import { injectAxe, checkA11y } from 'axe-playwright'; + +const config: TestRunnerConfig = { + async preVisit(page) { + await injectAxe(page); + }, + async postVisit(page, context) { + // Get the entire context of a story, including parameters, args, argTypes, etc. + const storyContext = await getStoryContext(page, context); + + // Skip accessibility checks for specific stories if needed + if (storyContext.parameters?.a11y?.disable) { + return; + } + + // Run accessibility checks + await checkA11y(page, '#storybook-root', { + detailedReport: true, + detailedReportOptions: { + html: true, + }, + }); + }, +}; + +export default config; diff --git a/README.md b/README.md index 6455bd5..55567a0 100644 --- a/README.md +++ b/README.md @@ -1,520 +1,520 @@ -# @wedevs/plugin-ui - -Reusable UI components for WordPress plugins. Built with React and designed to work seamlessly with the WordPress admin environment. - -## Table of Contents - -- [Installation](#installation) -- [Quick Start](#quick-start) -- [Development Setup](#development-setup) -- [Building the Package](#building-the-package) -- [Importing Styles](#importing-styles) -- [Usage Examples](#usage-examples) -- [Components](#components) -- [Theme & Variables Mapping](#theme--variables-mapping) -- [Theme Presets](#theme-presets) -- [Project Structure](#project-structure) -- [Available Scripts](#available-scripts) -- [Peer Dependencies](#peer-dependencies) -- [License](#license) - -## Installation - -### Step 1: Add to Your Project - -Add the package to your `package.json` dependencies: - -**Option A: Via GitHub (Recommended)** -```json -{ - "dependencies": { - "@wedevs/plugin-ui": "git+https://github.com/mrabbani/plugin-ui.git" - } -} -``` - -**Option B: Local Development** -If you are developing locally and want to link the package: -```json -{ - "dependencies": { - "@wedevs/plugin-ui": "file:../path/to/plugin-ui" - } -} -``` - -### Step 2: Install Dependencies - -Run the installation command: -```bash -npm install -``` - -### Step 3: Import Styles - -Import the CSS file in your main entry point: -```tsx -import '@wedevs/plugin-ui/styles.css'; -``` - -## Quick Start - -### Step 1: Import Components - -```tsx -import { TextField, Button, Alert, Badge } from '@wedevs/plugin-ui'; -``` - -### Step 2: Use Components - -```tsx -const MyComponent = () => { - const [text, setText] = useState(''); - - return ( -
- - The implementation is working perfectly! - - - - - - - -
- ); -}; -``` - -## Development Setup - -### Step 1: Clone the Repository - -```bash -git clone https://github.com/mrabbani/plugin-ui.git -cd plugin-ui -``` - -### Step 2: Install Dependencies - -```bash -npm install -``` - -### Step 3: Build the Package - -```bash -npm run build -``` - -This will: -- Compile TypeScript files to JavaScript (`dist/`) -- Process CSS with PostCSS and Tailwind (`dist/styles.css`) - -### Step 4: Link for Local Development (Optional) - -If you want to use this package in another project during development: - -```bash -# In plugin-ui directory -npm link - -# In your consuming project -npm link @wedevs/plugin-ui -``` - -## Building the Package - -### Full Build - -Build both TypeScript and CSS: -```bash -npm run build -``` - -This runs: -1. `npm run build:ts` - Compiles TypeScript to JavaScript -2. `npm run build:css` - Processes CSS with PostCSS/Tailwind - -### Build TypeScript Only - -```bash -npm run build:ts -``` - -### Build CSS Only - -```bash -npm run build:css -``` - -### Build Output - -After building, you'll find: -- **JavaScript**: `dist/**/*.js` (compiled from TypeScript) -- **Type Definitions**: `dist/**/*.d.ts` (TypeScript declarations) -- **CSS**: `dist/styles.css` (processed stylesheet) - -## Importing Styles - -### Step 1: Import in Your Main Entry Point - -In your main application file (e.g., `src/index.tsx` or `src/admin/index.tsx`): - -```tsx -import '@wedevs/plugin-ui/styles.css'; -``` - -### Step 2: Ensure Tailwind CSS 4 Setup - -This package requires **Tailwind CSS 4**. In your main CSS file: - -```css -@import "tailwindcss"; - -/* Optional: Override plugin-ui CSS variables */ -:root { - --plugin-ui-primary: #6366f1; - --plugin-ui-primary-hover: #4f46e5; -} -``` - -### Step 3: Verify Styles are Loaded - -Check your browser's developer tools to ensure `styles.css` is loaded correctly. - -## Usage Examples - -### Form with Validation - -```tsx -import { TextField, Button, Alert, FieldLabel } from '@wedevs/plugin-ui'; - -const LoginForm = () => { - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [error, setError] = useState(''); - - const handleSubmit = () => { - if (!email || !password) { - setError('Please fill in all fields'); - return; - } - // Handle login - }; - - return ( -
- {error && ( - - {error} - - )} - - - - - - - - - - -
- ); -}; -``` - -### Data Display with Filters - -```tsx -import { SearchInput, Badge, Modal, List } from '@wedevs/plugin-ui'; - -const DataView = () => { - const [search, setSearch] = useState(''); - const [isModalOpen, setIsModalOpen] = useState(false); - - return ( -
-
- - -
- - - -
- Item 1 - -
-
-
- - setIsModalOpen(false)} - title="Add New Item" - > - {/* Modal content */} - -
- ); -}; -``` - -## Components - -### Feedback & Status -- **`Alert`** - Notice/Message with variants (success, info, warning, error) -- **`Badge`** - Status indicator (requires `label` prop) -- **`InfoBox`** - Informational block with title and content -- **`Tooltip`** - Hover tooltip for additional information - -### Actions -- **`Button`** - Styled buttons with multiple variants and loading states -- **`Link`** - Styled link component -- **`SocialButton`** - Social media login buttons (Google, etc.) - -### Form Controls -- **`TextField`** - Standard text input -- **`NumberField`** - Numeric input with validation -- **`TextArea`** - Multi-line text input -- **`PasswordField`** - Secure password input with show/hide toggle -- **`Select`** - Dropdown selection with search -- **`Checkbox`** & **`CheckboxGroup`** - Boolean and multiple selection -- **`Radio`** & **`RadioCapsule`** - Single selection from a group -- **`Switch`** - Toggle switch component -- **`FieldLabel`** - Wrapper for input labels with descriptions -- **`ColorPicker`** - Color selection input -- **`TimePicker`** - Time selection input -- **`FileUpload`** - File upload with drag & drop -- **`MediaUploader`** - WordPress Media Library integration - -### Specialized Inputs -- **`SearchInput`** - Input with search icon and clearing logic -- **`DebouncedInput`** - Input that delays `onChange` execution -- **`CustomizeRadio`** - Rich radio options (Cards, Templates, etc.) -- **`AsyncSelect`** - Async data loading select components - - `ProductAsyncSelect` - - `VendorAsyncSelect` - - `OrderAsyncSelect` - - `CouponAsyncSelect` - -### Rich Text & Content -- **`RichText`** - Quill-based rich text editor -- **`CopyField`** - Read-only field with copy functionality -- **`ShowHideField`** - Field with show/hide toggle - -### Lists & Layout -- **`List`** - Versatile list display for activities or items -- **`Modal`** - Accessible dialog windows -- **`Popover`** - Popover component for additional content -- **`Repeater`** - Repeating field groups -- **`Filter`** - Filter component for data views - -### Utilities -- **`VisitStore`** - Store visit link component - -## Theme & Variables Mapping - -Design tokens (ThemeTokens) are converted to CSS variables and wired into Tailwind’s theme. For a full description of how tokens map to base CSS variables and to Tailwind theme variables, see **[docs/VARIABLES-MAPPING.md](docs/VARIABLES-MAPPING.md)**. - -Summary: - -- **ThemeTokens** (camelCase in JS) → **Base CSS variables** (e.g. `--primary-foreground`) via `tokensToCssVariables()` in `ThemeProvider`. -- **Base variables** → **Tailwind theme** via `@theme inline` in `styles.css` (e.g. `--color-primary: var(--primary)`). -- Custom token keys are converted the same way (`camelCase` → `--kebab-case`). - -## Theme Presets - -The package includes **theme presets** — ready-made light/dark token sets you can pass to `ThemeProvider`. For the full list, usage with `ThemeProvider`, and how to extend them with `createTheme` / `createDarkTheme`, see **[docs/THEME-PRESETS.md](docs/THEME-PRESETS.md)**. - -Quick example: - -```tsx -import { ThemeProvider, dokanTheme, dokanDarkTheme } from "@wedevs/plugin-ui"; - - - - -``` - -Available presets: `defaultTheme`, `dokanTheme`, `blueTheme`, `greenTheme`, `amberTheme`, `slateTheme` (and their `*DarkTheme` variants). Use `createTheme(tokens)` or `createDarkTheme(tokens)` to build a custom theme from a base. - -## Project Structure - -``` -plugin-ui/ -├── src/ -│ ├── components/ # React components -│ │ ├── Alert.tsx -│ │ ├── Button.tsx -│ │ ├── TextField.tsx -│ │ └── ... -│ ├── hooks/ # Custom React hooks -│ │ ├── useClickOutside.ts -│ │ ├── useClipboard.ts -│ │ └── useToggle.ts -│ ├── utils/ # Utility functions -│ │ └── classnames.ts -│ ├── types/ # TypeScript type definitions -│ ├── styles.css # Main stylesheet (Tailwind + custom) -│ └── index.ts # Main entry point -├── dist/ # Build output (generated) -│ ├── components/ -│ ├── hooks/ -│ ├── utils/ -│ ├── styles.css # Compiled CSS -│ └── index.js -├── package.json -├── tsconfig.json # TypeScript configuration -├── postcss.config.js # PostCSS configuration -└── README.md -``` - -## Available Scripts - -### Development -```bash -# Build the entire package (TypeScript + CSS) -npm run build - -# Build TypeScript only -npm run build:ts - -# Build CSS only -npm run build:css - -# Run type checking without emitting files -npm run typecheck - -# Lint source files -npm run lint -``` - -### Build Process - -1. **TypeScript Compilation** (`build:ts`) - - Compiles `.ts` and `.tsx` files to JavaScript - - Generates type definitions (`.d.ts` files) - - Output: `dist/**/*.js` and `dist/**/*.d.ts` - -2. **CSS Processing** (`build:css`) - - Processes `src/styles.css` with PostCSS - - Applies Tailwind CSS 4 transformations - - Output: `dist/styles.css` - -3. **Automatic Build** (`prepare`) - - Runs automatically on `npm install` - - Ensures package is built before publishing - -## Peer Dependencies - -This package requires the following peer dependencies. Make sure they are installed in your consuming project: - -### WordPress Packages -- `@wordpress/block-editor` >= 12.0.0 -- `@wordpress/components` >= 19.0.0 -- `@wordpress/element` >= 4.0.0 -- `@wordpress/hooks` >= 3.0.0 -- `@wordpress/i18n` >= 4.0.0 - -### React -- `react` >= 17.0.0 -- `react-dom` >= 17.0.0 - -### Other -- `quill` >= 1.3.0 (for RichText component) - -## Development Workflow - -### Step 1: Make Changes - -Edit files in the `src/` directory: -- Components: `src/components/*.tsx` -- Styles: `src/styles.css` -- Hooks: `src/hooks/*.ts` -- Utils: `src/utils/*.ts` - -### Step 2: Build - -After making changes, rebuild the package: -```bash -npm run build -``` - -### Step 3: Test - -If using `npm link`, changes will be reflected in your consuming project after rebuilding. - -### Step 4: Type Check - -Verify TypeScript types are correct: -```bash -npm run typecheck -``` - -### Step 5: Lint - -Check code quality: -```bash -npm run lint -``` - -## Troubleshooting - -### Styles Not Loading - -1. Ensure you've imported the CSS: - ```tsx - import '@wedevs/plugin-ui/styles.css'; - ``` - -2. Verify Tailwind CSS 4 is set up in your project - -3. Check that `dist/styles.css` exists after building - -### Type Errors - -1. Run `npm run typecheck` to see all type errors -2. Ensure all peer dependencies are installed -3. Check TypeScript version compatibility - -### Build Issues - -1. Clear `dist/` directory and rebuild: - ```bash - rm -rf dist && npm run build - ``` - -2. Reinstall dependencies: - ```bash - rm -rf node_modules package-lock.json - npm install - ``` - -## License - -GPL-2.0-or-later +# @wedevs/plugin-ui + +Reusable UI components for WordPress plugins. Built with React and designed to work seamlessly with the WordPress admin environment. + +## Table of Contents + +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Development Setup](#development-setup) +- [Building the Package](#building-the-package) +- [Importing Styles](#importing-styles) +- [Usage Examples](#usage-examples) +- [Components](#components) +- [Theme & Variables Mapping](#theme--variables-mapping) +- [Theme Presets](#theme-presets) +- [Project Structure](#project-structure) +- [Available Scripts](#available-scripts) +- [Peer Dependencies](#peer-dependencies) +- [License](#license) + +## Installation + +### Step 1: Add to Your Project + +Add the package to your `package.json` dependencies: + +**Option A: Via GitHub (Recommended)** +```json +{ + "dependencies": { + "@wedevs/plugin-ui": "git+https://github.com/mrabbani/plugin-ui.git" + } +} +``` + +**Option B: Local Development** +If you are developing locally and want to link the package: +```json +{ + "dependencies": { + "@wedevs/plugin-ui": "file:../path/to/plugin-ui" + } +} +``` + +### Step 2: Install Dependencies + +Run the installation command: +```bash +npm install +``` + +### Step 3: Import Styles + +Import the CSS file in your main entry point: +```tsx +import '@wedevs/plugin-ui/styles.css'; +``` + +## Quick Start + +### Step 1: Import Components + +```tsx +import { TextField, Button, Alert, Badge } from '@wedevs/plugin-ui'; +``` + +### Step 2: Use Components + +```tsx +const MyComponent = () => { + const [text, setText] = useState(''); + + return ( +
+ + The implementation is working perfectly! + + + + + + + +
+ ); +}; +``` + +## Development Setup + +### Step 1: Clone the Repository + +```bash +git clone https://github.com/mrabbani/plugin-ui.git +cd plugin-ui +``` + +### Step 2: Install Dependencies + +```bash +npm install +``` + +### Step 3: Build the Package + +```bash +npm run build +``` + +This will: +- Compile TypeScript files to JavaScript (`dist/`) +- Process CSS with PostCSS and Tailwind (`dist/styles.css`) + +### Step 4: Link for Local Development (Optional) + +If you want to use this package in another project during development: + +```bash +# In plugin-ui directory +npm link + +# In your consuming project +npm link @wedevs/plugin-ui +``` + +## Building the Package + +### Full Build + +Build both TypeScript and CSS: +```bash +npm run build +``` + +This runs: +1. `npm run build:ts` - Compiles TypeScript to JavaScript +2. `npm run build:css` - Processes CSS with PostCSS/Tailwind + +### Build TypeScript Only + +```bash +npm run build:ts +``` + +### Build CSS Only + +```bash +npm run build:css +``` + +### Build Output + +After building, you'll find: +- **JavaScript**: `dist/**/*.js` (compiled from TypeScript) +- **Type Definitions**: `dist/**/*.d.ts` (TypeScript declarations) +- **CSS**: `dist/styles.css` (processed stylesheet) + +## Importing Styles + +### Step 1: Import in Your Main Entry Point + +In your main application file (e.g., `src/index.tsx` or `src/admin/index.tsx`): + +```tsx +import '@wedevs/plugin-ui/styles.css'; +``` + +### Step 2: Ensure Tailwind CSS 4 Setup + +This package requires **Tailwind CSS 4**. In your main CSS file: + +```css +@import "tailwindcss"; + +/* Optional: Override plugin-ui CSS variables */ +:root { + --plugin-ui-primary: #6366f1; + --plugin-ui-primary-hover: #4f46e5; +} +``` + +### Step 3: Verify Styles are Loaded + +Check your browser's developer tools to ensure `styles.css` is loaded correctly. + +## Usage Examples + +### Form with Validation + +```tsx +import { TextField, Button, Alert, FieldLabel } from '@wedevs/plugin-ui'; + +const LoginForm = () => { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + + const handleSubmit = () => { + if (!email || !password) { + setError('Please fill in all fields'); + return; + } + // Handle login + }; + + return ( +
+ {error && ( + + {error} + + )} + + + + + + + + + + +
+ ); +}; +``` + +### Data Display with Filters + +```tsx +import { SearchInput, Badge, Modal, List } from '@wedevs/plugin-ui'; + +const DataView = () => { + const [search, setSearch] = useState(''); + const [isModalOpen, setIsModalOpen] = useState(false); + + return ( +
+
+ + +
+ + + +
+ Item 1 + +
+
+
+ + setIsModalOpen(false)} + title="Add New Item" + > + {/* Modal content */} + +
+ ); +}; +``` + +## Components + +### Feedback & Status +- **`Alert`** - Notice/Message with variants (success, info, warning, error) +- **`Badge`** - Status indicator (requires `label` prop) +- **`InfoBox`** - Informational block with title and content +- **`Tooltip`** - Hover tooltip for additional information + +### Actions +- **`Button`** - Styled buttons with multiple variants and loading states +- **`Link`** - Styled link component +- **`SocialButton`** - Social media login buttons (Google, etc.) + +### Form Controls +- **`TextField`** - Standard text input +- **`NumberField`** - Numeric input with validation +- **`TextArea`** - Multi-line text input +- **`PasswordField`** - Secure password input with show/hide toggle +- **`Select`** - Dropdown selection with search +- **`Checkbox`** & **`CheckboxGroup`** - Boolean and multiple selection +- **`Radio`** & **`RadioCapsule`** - Single selection from a group +- **`Switch`** - Toggle switch component +- **`FieldLabel`** - Wrapper for input labels with descriptions +- **`ColorPicker`** - Color selection input +- **`TimePicker`** - Time selection input +- **`FileUpload`** - File upload with drag & drop +- **`MediaUploader`** - WordPress Media Library integration + +### Specialized Inputs +- **`SearchInput`** - Input with search icon and clearing logic +- **`DebouncedInput`** - Input that delays `onChange` execution +- **`CustomizeRadio`** - Rich radio options (Cards, Templates, etc.) +- **`AsyncSelect`** - Async data loading select components + - `ProductAsyncSelect` + - `VendorAsyncSelect` + - `OrderAsyncSelect` + - `CouponAsyncSelect` + +### Rich Text & Content +- **`RichText`** - Quill-based rich text editor +- **`CopyField`** - Read-only field with copy functionality +- **`ShowHideField`** - Field with show/hide toggle + +### Lists & Layout +- **`List`** - Versatile list display for activities or items +- **`Modal`** - Accessible dialog windows +- **`Popover`** - Popover component for additional content +- **`Repeater`** - Repeating field groups +- **`Filter`** - Filter component for data views + +### Utilities +- **`VisitStore`** - Store visit link component + +## Theme & Variables Mapping + +Design tokens (ThemeTokens) are converted to CSS variables and wired into Tailwind’s theme. For a full description of how tokens map to base CSS variables and to Tailwind theme variables, see **[docs/VARIABLES-MAPPING.md](docs/VARIABLES-MAPPING.md)**. + +Summary: + +- **ThemeTokens** (camelCase in JS) → **Base CSS variables** (e.g. `--primary-foreground`) via `tokensToCssVariables()` in `ThemeProvider`. +- **Base variables** → **Tailwind theme** via `@theme inline` in `styles.css` (e.g. `--color-primary: var(--primary)`). +- Custom token keys are converted the same way (`camelCase` → `--kebab-case`). + +## Theme Presets + +The package includes **theme presets** — ready-made light/dark token sets you can pass to `ThemeProvider`. For the full list, usage with `ThemeProvider`, and how to extend them with `createTheme` / `createDarkTheme`, see **[docs/THEME-PRESETS.md](docs/THEME-PRESETS.md)**. + +Quick example: + +```tsx +import { ThemeProvider, dokanTheme, dokanDarkTheme } from "@wedevs/plugin-ui"; + + + + +``` + +Available presets: `defaultTheme`, `dokanTheme`, `blueTheme`, `greenTheme`, `amberTheme`, `slateTheme` (and their `*DarkTheme` variants). Use `createTheme(tokens)` or `createDarkTheme(tokens)` to build a custom theme from a base. + +## Project Structure + +``` +plugin-ui/ +├── src/ +│ ├── components/ # React components +│ │ ├── Alert.tsx +│ │ ├── Button.tsx +│ │ ├── TextField.tsx +│ │ └── ... +│ ├── hooks/ # Custom React hooks +│ │ ├── useClickOutside.ts +│ │ ├── useClipboard.ts +│ │ └── useToggle.ts +│ ├── utils/ # Utility functions +│ │ └── classnames.ts +│ ├── types/ # TypeScript type definitions +│ ├── styles.css # Main stylesheet (Tailwind + custom) +│ └── index.ts # Main entry point +├── dist/ # Build output (generated) +│ ├── components/ +│ ├── hooks/ +│ ├── utils/ +│ ├── styles.css # Compiled CSS +│ └── index.js +├── package.json +├── tsconfig.json # TypeScript configuration +├── postcss.config.js # PostCSS configuration +└── README.md +``` + +## Available Scripts + +### Development +```bash +# Build the entire package (TypeScript + CSS) +npm run build + +# Build TypeScript only +npm run build:ts + +# Build CSS only +npm run build:css + +# Run type checking without emitting files +npm run typecheck + +# Lint source files +npm run lint +``` + +### Build Process + +1. **TypeScript Compilation** (`build:ts`) + - Compiles `.ts` and `.tsx` files to JavaScript + - Generates type definitions (`.d.ts` files) + - Output: `dist/**/*.js` and `dist/**/*.d.ts` + +2. **CSS Processing** (`build:css`) + - Processes `src/styles.css` with PostCSS + - Applies Tailwind CSS 4 transformations + - Output: `dist/styles.css` + +3. **Automatic Build** (`prepare`) + - Runs automatically on `npm install` + - Ensures package is built before publishing + +## Peer Dependencies + +This package requires the following peer dependencies. Make sure they are installed in your consuming project: + +### WordPress Packages +- `@wordpress/block-editor` >= 12.0.0 +- `@wordpress/components` >= 19.0.0 +- `@wordpress/element` >= 4.0.0 +- `@wordpress/hooks` >= 3.0.0 +- `@wordpress/i18n` >= 4.0.0 + +### React +- `react` >= 17.0.0 +- `react-dom` >= 17.0.0 + +### Other +- `quill` >= 1.3.0 (for RichText component) + +## Development Workflow + +### Step 1: Make Changes + +Edit files in the `src/` directory: +- Components: `src/components/*.tsx` +- Styles: `src/styles.css` +- Hooks: `src/hooks/*.ts` +- Utils: `src/utils/*.ts` + +### Step 2: Build + +After making changes, rebuild the package: +```bash +npm run build +``` + +### Step 3: Test + +If using `npm link`, changes will be reflected in your consuming project after rebuilding. + +### Step 4: Type Check + +Verify TypeScript types are correct: +```bash +npm run typecheck +``` + +### Step 5: Lint + +Check code quality: +```bash +npm run lint +``` + +## Troubleshooting + +### Styles Not Loading + +1. Ensure you've imported the CSS: + ```tsx + import '@wedevs/plugin-ui/styles.css'; + ``` + +2. Verify Tailwind CSS 4 is set up in your project + +3. Check that `dist/styles.css` exists after building + +### Type Errors + +1. Run `npm run typecheck` to see all type errors +2. Ensure all peer dependencies are installed +3. Check TypeScript version compatibility + +### Build Issues + +1. Clear `dist/` directory and rebuild: + ```bash + rm -rf dist && npm run build + ``` + +2. Reinstall dependencies: + ```bash + rm -rf node_modules package-lock.json + npm install + ``` + +## License + +GPL-2.0-or-later diff --git a/babel.config.cjs b/babel.config.cjs index ee234e4..47bc4e5 100644 --- a/babel.config.cjs +++ b/babel.config.cjs @@ -1,6 +1,6 @@ -module.exports = { - presets: [ - ["@babel/preset-react", { runtime: "automatic" }], - "@babel/preset-typescript", - ], -}; +module.exports = { + presets: [ + ["@babel/preset-react", { runtime: "automatic" }], + "@babel/preset-typescript", + ], +}; diff --git a/components.json b/components.json index 4d0d1c8..4dc73b4 100644 --- a/components.json +++ b/components.json @@ -1,21 +1,21 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "base-vega", - "rsc": false, - "tsx": true, - "rtl": true, - "tailwind": { - "config": "", - "css": "@/styles.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib" - }, - "iconLibrary": "lucide" +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "base-vega", + "rsc": false, + "tsx": true, + "rtl": true, + "tailwind": { + "config": "", + "css": "@/styles.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib" + }, + "iconLibrary": "lucide" } \ No newline at end of file diff --git a/docs/ALERT.md b/docs/ALERT.md index a4266cd..946cbf2 100644 --- a/docs/ALERT.md +++ b/docs/ALERT.md @@ -1,263 +1,263 @@ -# Alert Component - -A flexible alert component for displaying important messages with various visual styles. - -## Import - -```tsx -import { Alert, AlertTitle, AlertDescription, AlertAction } from "@/components/ui/alert" -``` - -## Components - -| Component | Description | -|-----------|-------------| -| `Alert` | Main container component | -| `AlertTitle` | Bold heading text | -| `AlertDescription` | Supporting message content | -| `AlertAction` | Action button/container (top-right absolute) | - ---- - -## Alert Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `variant` | `default \| destructive \| success \| warning \| info` | `"default"` | Visual style of the alert | -| `bgColor` | `string` | - | Custom background color (CSS value) | -| `borderColor` | `string` | - | Custom border color (CSS value) | -| `titleColor` | `string` | - | Custom title text color (CSS value) | -| `descriptionColor` | `string` | - | Custom description text color (CSS value) | -| `className` | `string` | - | Additional CSS classes | -| `style` | `CSSProperties` | - | Additional inline styles | -| `...props` | `ComponentProps<"div">` | - | Standard HTML div attributes | - -### Variant Styles - -| Variant | Background | Border | Title Color | Description Color | -|---------|-----------|--------|-------------|-------------------| -| `default` | `#F8F9F8` | `#E9E9E9` | `#25252D` | `#575757` | -| `destructive` | Red-50 | Transparent | Red-700 | Red-500 | -| `success` | Green-50 | None | Green-700 | Green-700 | -| `warning` | Yellow-50 | None | Yellow-900 | Yellow-900 | -| `info` | Blue-600 | None | White | White/80 | - ---- - -## Usage Examples - -### Title Only - -```tsx - - Default Alert - Title Only - - - - Settings saved successfully! - -``` - -### With Description - -```tsx - - Default Alert - With Description - This is a default alert with both title and description. It uses neutral colors with a light gray background. - -``` - -### All Variants - -```tsx -// Default - - Default - Neutral information message. - - -// Destructive (Error) - - Error occurred - Something went wrong while processing your request. Please try again later. - - -// Success - - Success! - Your changes have been saved successfully. - - -// Warning - - Warning - Your session will expire in 5 minutes. Please save your work. - - -// Info - - Information - Did you know? You can customize the theme colors in the settings panel. - -``` - -### With Inline Buttons - -```tsx - -
- Default Alert - With Buttons -
- - -
-
-
-``` - -### With Action Buttons Below - -```tsx - - New Update Available - A new version of the plugin is available for download. -
- - -
-
-``` - -### With Close Button (AlertAction) - -```tsx -import { X } from "lucide-react" - - - Update Available - A new version of the app is now available. - - - - -``` - -### Dismissible Alert (With State) - -```tsx -import { useState } from "react" -import { X } from "lucide-react" - -function DismissibleAlert() { - const [isVisible, setIsVisible] = useState(true) - - if (!isVisible) return null - - return ( - - Update Available - A new version of the app is now available. - - - - - ) -} -``` - -### With Icon - -```tsx -import { CheckCircle, AlertTriangle, Info } from "lucide-react" - -// Success with icon - - - Payment successful - Your subscription has been activated. - - -// Warning with icon - - - Please review before continuing - -``` - -### Custom Colors (Full Override) - -```tsx - - Custom Colors Alert - This alert uses custom colors defined via props instead of variant presets. - - -// Custom purple theme - - Purple Theme Alert - Custom purple themed alert for special use cases. - -``` - -### Mixing Variant with Custom Colors - -```tsx -// Uses success variant colors but overrides description color - - Custom Description Color - This uses success variant colors but with a darker custom description color. - -``` - -### Description Only (No Title) - -```tsx - - Simple informational message without a title. - -``` - ---- - -## Best Practices - -- Use `default` for general notices -- Use `destructive` for errors or critical issues -- Use `success` for successful operations -- Use `warning` for cautionary messages -- Use `info` for helpful tips or supplementary information -- Include an `AlertAction` with a close button for dismissible alerts -- Keep descriptions concise and actionable -- Use icons to reinforce the message type when appropriate - ---- - -## Accessibility - -The `Alert` component includes `role="alert"` for screen readers, ensuring important messages are announced to users with assistive technologies. +# Alert Component + +A flexible alert component for displaying important messages with various visual styles. + +## Import + +```tsx +import { Alert, AlertTitle, AlertDescription, AlertAction } from "@/components/ui/alert" +``` + +## Components + +| Component | Description | +|-----------|-------------| +| `Alert` | Main container component | +| `AlertTitle` | Bold heading text | +| `AlertDescription` | Supporting message content | +| `AlertAction` | Action button/container (top-right absolute) | + +--- + +## Alert Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `variant` | `default \| destructive \| success \| warning \| info` | `"default"` | Visual style of the alert | +| `bgColor` | `string` | - | Custom background color (CSS value) | +| `borderColor` | `string` | - | Custom border color (CSS value) | +| `titleColor` | `string` | - | Custom title text color (CSS value) | +| `descriptionColor` | `string` | - | Custom description text color (CSS value) | +| `className` | `string` | - | Additional CSS classes | +| `style` | `CSSProperties` | - | Additional inline styles | +| `...props` | `ComponentProps<"div">` | - | Standard HTML div attributes | + +### Variant Styles + +| Variant | Background | Border | Title Color | Description Color | +|---------|-----------|--------|-------------|-------------------| +| `default` | `#F8F9F8` | `#E9E9E9` | `#25252D` | `#575757` | +| `destructive` | Red-50 | Transparent | Red-700 | Red-500 | +| `success` | Green-50 | None | Green-700 | Green-700 | +| `warning` | Yellow-50 | None | Yellow-900 | Yellow-900 | +| `info` | Blue-600 | None | White | White/80 | + +--- + +## Usage Examples + +### Title Only + +```tsx + + Default Alert - Title Only + + + + Settings saved successfully! + +``` + +### With Description + +```tsx + + Default Alert - With Description + This is a default alert with both title and description. It uses neutral colors with a light gray background. + +``` + +### All Variants + +```tsx +// Default + + Default + Neutral information message. + + +// Destructive (Error) + + Error occurred + Something went wrong while processing your request. Please try again later. + + +// Success + + Success! + Your changes have been saved successfully. + + +// Warning + + Warning + Your session will expire in 5 minutes. Please save your work. + + +// Info + + Information + Did you know? You can customize the theme colors in the settings panel. + +``` + +### With Inline Buttons + +```tsx + +
+ Default Alert - With Buttons +
+ + +
+
+
+``` + +### With Action Buttons Below + +```tsx + + New Update Available + A new version of the plugin is available for download. +
+ + +
+
+``` + +### With Close Button (AlertAction) + +```tsx +import { X } from "lucide-react" + + + Update Available + A new version of the app is now available. + + + + +``` + +### Dismissible Alert (With State) + +```tsx +import { useState } from "react" +import { X } from "lucide-react" + +function DismissibleAlert() { + const [isVisible, setIsVisible] = useState(true) + + if (!isVisible) return null + + return ( + + Update Available + A new version of the app is now available. + + + + + ) +} +``` + +### With Icon + +```tsx +import { CheckCircle, AlertTriangle, Info } from "lucide-react" + +// Success with icon + + + Payment successful + Your subscription has been activated. + + +// Warning with icon + + + Please review before continuing + +``` + +### Custom Colors (Full Override) + +```tsx + + Custom Colors Alert + This alert uses custom colors defined via props instead of variant presets. + + +// Custom purple theme + + Purple Theme Alert + Custom purple themed alert for special use cases. + +``` + +### Mixing Variant with Custom Colors + +```tsx +// Uses success variant colors but overrides description color + + Custom Description Color + This uses success variant colors but with a darker custom description color. + +``` + +### Description Only (No Title) + +```tsx + + Simple informational message without a title. + +``` + +--- + +## Best Practices + +- Use `default` for general notices +- Use `destructive` for errors or critical issues +- Use `success` for successful operations +- Use `warning` for cautionary messages +- Use `info` for helpful tips or supplementary information +- Include an `AlertAction` with a close button for dismissible alerts +- Keep descriptions concise and actionable +- Use icons to reinforce the message type when appropriate + +--- + +## Accessibility + +The `Alert` component includes `role="alert"` for screen readers, ensuring important messages are announced to users with assistive technologies. diff --git a/docs/AVATAR_AND_THUMBNAIL.md b/docs/AVATAR_AND_THUMBNAIL.md index 478970a..79625e8 100644 --- a/docs/AVATAR_AND_THUMBNAIL.md +++ b/docs/AVATAR_AND_THUMBNAIL.md @@ -1,280 +1,280 @@ -# Avatar and Thumbnail Components - -## Avatar - -Avatar component for displaying user profile images with fallback support. Built on top of [Base UI Avatar](https://base-ui.com/react/components/avatar). - -### Components - -- `Avatar` - Root container component -- `AvatarImage` - The image element -- `AvatarFallback` - Fallback content when image fails to load -- `AvatarBadge` - Status badge/indicator overlay -- `AvatarGroup` - Container for stacked avatars -- `AvatarGroupCount` - Count indicator for grouped avatars - -### Avatar Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `size` | `"xs" \| "sm" \| "md" \| "lg" \| number` | `"md"` | Size of the avatar. Preset values: `xs` (24px), `sm` (28px), `md` (32px), `lg` (48px). Pass a number for custom pixel size. | -| `shape` | `"circle" \| "square"` | `"circle"` | Shape of the avatar. `circle` is fully rounded, `square` has 5px border-radius. | -| `className` | `string` | - | Additional CSS classes | -| `style` | `CSSProperties` | - | Inline styles (merged with size style) | - -### AvatarImage Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `src` | `string` | - | Image source URL | -| `alt` | `string` | - | Alt text for accessibility | -| `className` | `string` | - | Additional CSS classes | - -### AvatarFallback Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `className` | `string` | - | Additional CSS classes | -| `style` | `CSSProperties` | - | Inline styles | -| `children` | `ReactNode` | - | Fallback content (typically initials or icon) | - -### AvatarBadge Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `className` | `string` | - | Additional CSS classes | -| `style` | `CSSProperties` | - | Inline styles | -| `children` | `ReactNode` | - | Badge content (typically status dot or icon) | - -### AvatarGroup Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `className` | `string` | - | Additional CSS classes | - -### AvatarGroupCount Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `size` | `AvatarSize` | `"md"` | Size of the count badge (inherits Avatar sizing) | -| `className` | `string` | - | Additional CSS classes | -| `style` | `CSSProperties` | - | Inline styles | -| `children` | `ReactNode` | - | Count content (number or text) | - -### Usage Examples - -#### Basic Avatar with image and fallback - -```tsx -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar" - - - - JD - -``` - -#### Different sizes - -```tsx - - - XS - - - - - SM - - - - - MD - - - - - LG - - - - - 64 - -``` - -#### Square shape - -```tsx - - - JD - -``` - -#### With status badge - -```tsx - - - JD - - - - -``` - -#### Avatar group - -```tsx -import { Avatar, AvatarImage, AvatarFallback, AvatarGroup } from "@/components/ui/avatar" - - - - - AB - - - - CD - - - - EF - - +5 - -``` - -#### Colored fallback - -```tsx - - - - JD - - -``` - ---- - -## Thumbnail - -Thumbnail component for displaying images with aspect ratio support and fallback content. - -### Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `src` | `string` | `undefined` | Image source URL. If omitted, shows fallback immediately. | -| `size` | `"xs" \| "sm" \| "md" \| "lg" \| number` | `"md"` | Height of the thumbnail. Preset values: `xs` (24px), `sm` (28px), `md` (32px), `lg` (48px). Pass a number for custom pixel height. Width is calculated based on aspect ratio. | -| `aspect` | `"landscape" \| "portrait" \| "square"` | `"square"` | Aspect ratio of the thumbnail. `landscape` (16:9), `portrait` (3:4), `square` (1:1). | -| `fallback` | `ReactNode` | Default icon | Custom fallback content shown when `src` is empty or image fails to load. Defaults to a landscape image icon. | -| `alt` | `string` | `""` | Alt text for the image | -| `className` | `string` | - | Additional CSS classes | -| `style` | `CSSProperties` | - | Inline styles (merged with size/aspect styles) | - -### Usage Examples - -#### Basic thumbnail - -```tsx -import { Thumbnail } from "@/components/ui/thumbnail" - - -``` - -#### Different sizes - -```tsx - - - - - -``` - -#### Different aspect ratios - -```tsx - - - -``` - -#### With custom fallback - -```tsx -No image} -/> -``` - -#### Loading/fallback state (no src) - -```tsx -Loading...} /> -``` - -#### Combined size and aspect - -```tsx - -``` - -#### Custom styling - -```tsx - -``` - ---- - -## Size Reference - -### Avatar Sizes - -| Size | Pixel Value | -|------|-------------| -| `xs` | 24px | -| `sm` | 28px | -| `md` | 32px | -| `lg` | 48px | - -### Thumbnail Heights (width calculated by aspect) - -| Size | Height | -|------|--------| -| `xs` | 24px | -| `sm` | 28px | -| `md` | 32px | -| `lg` | 48px | - -### Thumbnail Aspect Ratios - -| Aspect | Ratio | -|--------|-------| -| `landscape` | 16:9 | -| `portrait` | 3:4 | -| `square` | 1:1 | - -## Notes - -- **Avatar** always maintains equal width and height (1:1 aspect ratio) -- **AvatarBadge** positions itself at bottom-right and is always circular (on circle avatars) or has 3px radius (on square avatars) -- **AvatarGroup** applies negative horizontal spacing (`-space-x-2`) and ring borders to separate stacked avatars -- **Thumbnail** width is automatically calculated based on height and aspect ratio -- Both components support fully custom sizes via number prop -- Both components use `data-slot` attributes for targeting in CSS +# Avatar and Thumbnail Components + +## Avatar + +Avatar component for displaying user profile images with fallback support. Built on top of [Base UI Avatar](https://base-ui.com/react/components/avatar). + +### Components + +- `Avatar` - Root container component +- `AvatarImage` - The image element +- `AvatarFallback` - Fallback content when image fails to load +- `AvatarBadge` - Status badge/indicator overlay +- `AvatarGroup` - Container for stacked avatars +- `AvatarGroupCount` - Count indicator for grouped avatars + +### Avatar Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `size` | `"xs" \| "sm" \| "md" \| "lg" \| number` | `"md"` | Size of the avatar. Preset values: `xs` (24px), `sm` (28px), `md` (32px), `lg` (48px). Pass a number for custom pixel size. | +| `shape` | `"circle" \| "square"` | `"circle"` | Shape of the avatar. `circle` is fully rounded, `square` has 5px border-radius. | +| `className` | `string` | - | Additional CSS classes | +| `style` | `CSSProperties` | - | Inline styles (merged with size style) | + +### AvatarImage Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `src` | `string` | - | Image source URL | +| `alt` | `string` | - | Alt text for accessibility | +| `className` | `string` | - | Additional CSS classes | + +### AvatarFallback Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `className` | `string` | - | Additional CSS classes | +| `style` | `CSSProperties` | - | Inline styles | +| `children` | `ReactNode` | - | Fallback content (typically initials or icon) | + +### AvatarBadge Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `className` | `string` | - | Additional CSS classes | +| `style` | `CSSProperties` | - | Inline styles | +| `children` | `ReactNode` | - | Badge content (typically status dot or icon) | + +### AvatarGroup Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `className` | `string` | - | Additional CSS classes | + +### AvatarGroupCount Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `size` | `AvatarSize` | `"md"` | Size of the count badge (inherits Avatar sizing) | +| `className` | `string` | - | Additional CSS classes | +| `style` | `CSSProperties` | - | Inline styles | +| `children` | `ReactNode` | - | Count content (number or text) | + +### Usage Examples + +#### Basic Avatar with image and fallback + +```tsx +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar" + + + + JD + +``` + +#### Different sizes + +```tsx + + + XS + + + + + SM + + + + + MD + + + + + LG + + + + + 64 + +``` + +#### Square shape + +```tsx + + + JD + +``` + +#### With status badge + +```tsx + + + JD + + + + +``` + +#### Avatar group + +```tsx +import { Avatar, AvatarImage, AvatarFallback, AvatarGroup } from "@/components/ui/avatar" + + + + + AB + + + + CD + + + + EF + + +5 + +``` + +#### Colored fallback + +```tsx + + + + JD + + +``` + +--- + +## Thumbnail + +Thumbnail component for displaying images with aspect ratio support and fallback content. + +### Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `src` | `string` | `undefined` | Image source URL. If omitted, shows fallback immediately. | +| `size` | `"xs" \| "sm" \| "md" \| "lg" \| number` | `"md"` | Height of the thumbnail. Preset values: `xs` (24px), `sm` (28px), `md` (32px), `lg` (48px). Pass a number for custom pixel height. Width is calculated based on aspect ratio. | +| `aspect` | `"landscape" \| "portrait" \| "square"` | `"square"` | Aspect ratio of the thumbnail. `landscape` (16:9), `portrait` (3:4), `square` (1:1). | +| `fallback` | `ReactNode` | Default icon | Custom fallback content shown when `src` is empty or image fails to load. Defaults to a landscape image icon. | +| `alt` | `string` | `""` | Alt text for the image | +| `className` | `string` | - | Additional CSS classes | +| `style` | `CSSProperties` | - | Inline styles (merged with size/aspect styles) | + +### Usage Examples + +#### Basic thumbnail + +```tsx +import { Thumbnail } from "@/components/ui/thumbnail" + + +``` + +#### Different sizes + +```tsx + + + + + +``` + +#### Different aspect ratios + +```tsx + + + +``` + +#### With custom fallback + +```tsx +No image} +/> +``` + +#### Loading/fallback state (no src) + +```tsx +Loading...} /> +``` + +#### Combined size and aspect + +```tsx + +``` + +#### Custom styling + +```tsx + +``` + +--- + +## Size Reference + +### Avatar Sizes + +| Size | Pixel Value | +|------|-------------| +| `xs` | 24px | +| `sm` | 28px | +| `md` | 32px | +| `lg` | 48px | + +### Thumbnail Heights (width calculated by aspect) + +| Size | Height | +|------|--------| +| `xs` | 24px | +| `sm` | 28px | +| `md` | 32px | +| `lg` | 48px | + +### Thumbnail Aspect Ratios + +| Aspect | Ratio | +|--------|-------| +| `landscape` | 16:9 | +| `portrait` | 3:4 | +| `square` | 1:1 | + +## Notes + +- **Avatar** always maintains equal width and height (1:1 aspect ratio) +- **AvatarBadge** positions itself at bottom-right and is always circular (on circle avatars) or has 3px radius (on square avatars) +- **AvatarGroup** applies negative horizontal spacing (`-space-x-2`) and ring borders to separate stacked avatars +- **Thumbnail** width is automatically calculated based on height and aspect ratio +- Both components support fully custom sizes via number prop +- Both components use `data-slot` attributes for targeting in CSS diff --git a/docs/MODAL.md b/docs/MODAL.md index 93303af..1b3b7e9 100644 --- a/docs/MODAL.md +++ b/docs/MODAL.md @@ -1,173 +1,173 @@ -# Modal - -A dialog component that displays content on top of the main content using React Portal. Automatically manages focus, body scroll lock, and keyboard interactions. - -## Features - -- Portals to `document.body` to avoid z-index conflicts -- Locks body scroll when open -- Restores focus to triggering element on close -- Keyboard accessible (Escape to close) -- Click outside to close (configurable) -- Responsive design - -## Props API - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `open` | `boolean` | *required* | Whether the modal is visible | -| `onClose` | `() => void` | *required* | Callback fired when modal should close | -| `children` | `ReactNode` | - | Modal content (use sub-components for structure) | -| `showCloseButton` | `boolean` | `true` | Show the X button in the top-right corner | -| `closeOnOverlayClick` | `boolean` | `true` | Close when clicking the backdrop overlay | -| `closeOnEscape` | `boolean` | `true` | Close when pressing Escape key | -| `className` | `string` | - | Additional classes for the modal content | -| `size` | `'sm' \| 'default' \| 'lg' \| 'xl' \| 'full'` | `'default'` | Width preset of the modal | - -## Size Options - -| Value | Width | -|-------|-------| -| `sm` | `max-w-sm` (384px) | -| `default` | `max-w-lg` (512px) | -| `lg` | `max-w-2xl` (672px) | -| `xl` | `max-w-4xl` (896px) | -| `full` | Full viewport (minus 2rem margin) | - -## Usage - -### Basic Modal - -```tsx -import { Modal, ModalHeader, ModalTitle, ModalDescription } from "@plugin-ui/ui"; -import { useState } from "react"; - -function Example() { - const [open, setOpen] = useState(false); - - return ( - <> - - - setOpen(false)}> - - Confirm Action - This action cannot be undone. - -
-

Are you sure you want to proceed?

-
-
- - ); -} -``` - -### With Footer Actions - -```tsx -import { Modal, ModalHeader, ModalTitle, ModalDescription, ModalFooter } from "@plugin-ui/ui"; - - setOpen(false)}> - - Delete Account - Permanently delete your account and all data. - -
-

This action cannot be undone.

-
- - - - -
-``` - -### Different Sizes - -```tsx - - Small -
Compact content
-
- - - Large -
More content
-
- - - Full Screen -
Maximum space
-
-``` - -### Custom Close Behavior - -```tsx - - - Read Only - -
-

User must click "Done" to close this modal.

-
- - - -
-``` - -### Custom Styling - -```tsx - - - Custom Styled - -
Content
-
-``` - -## Accessibility - -- **Focus Management**: When opened, the modal traps focus within itself. On close, focus returns to the element that triggered it. -- **Keyboard Support**: - - `Escape` closes the modal (configurable via `closeOnEscape`) - - `Tab` cycles through focusable elements inside the modal -- **ARIA**: The close button includes `sr-only` text for screen readers. - -## Sub-Components - -The Modal uses a composition API. Import sub-components to structure your modal: - -```tsx -import { - Modal, - ModalContent, - ModalHeader, - ModalTitle, - ModalDescription, - ModalFooter, - ModalClose, - ModalOverlay, -} from "@plugin-ui/ui"; -``` - -| Sub-Component | Purpose | -|---------------|---------| -| `ModalHeader` | Header container with border | -| `ModalTitle` | Heading (`

`) with default styling | -| `ModalDescription` | Description text with padding | -| `ModalFooter` | Footer container with border and button layout | -| `ModalContent` | Main content container (rarely used directly) | -| `ModalOverlay` | Backdrop overlay (rarely used directly) | -| `ModalClose` | Close button (rarely used directly) | - -All sub-components accept `className` prop for custom styling. +# Modal + +A dialog component that displays content on top of the main content using React Portal. Automatically manages focus, body scroll lock, and keyboard interactions. + +## Features + +- Portals to `document.body` to avoid z-index conflicts +- Locks body scroll when open +- Restores focus to triggering element on close +- Keyboard accessible (Escape to close) +- Click outside to close (configurable) +- Responsive design + +## Props API + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `open` | `boolean` | *required* | Whether the modal is visible | +| `onClose` | `() => void` | *required* | Callback fired when modal should close | +| `children` | `ReactNode` | - | Modal content (use sub-components for structure) | +| `showCloseButton` | `boolean` | `true` | Show the X button in the top-right corner | +| `closeOnOverlayClick` | `boolean` | `true` | Close when clicking the backdrop overlay | +| `closeOnEscape` | `boolean` | `true` | Close when pressing Escape key | +| `className` | `string` | - | Additional classes for the modal content | +| `size` | `'sm' \| 'default' \| 'lg' \| 'xl' \| 'full'` | `'default'` | Width preset of the modal | + +## Size Options + +| Value | Width | +|-------|-------| +| `sm` | `max-w-sm` (384px) | +| `default` | `max-w-lg` (512px) | +| `lg` | `max-w-2xl` (672px) | +| `xl` | `max-w-4xl` (896px) | +| `full` | Full viewport (minus 2rem margin) | + +## Usage + +### Basic Modal + +```tsx +import { Modal, ModalHeader, ModalTitle, ModalDescription } from "@plugin-ui/ui"; +import { useState } from "react"; + +function Example() { + const [open, setOpen] = useState(false); + + return ( + <> + + + setOpen(false)}> + + Confirm Action + This action cannot be undone. + +
+

Are you sure you want to proceed?

+
+
+ + ); +} +``` + +### With Footer Actions + +```tsx +import { Modal, ModalHeader, ModalTitle, ModalDescription, ModalFooter } from "@plugin-ui/ui"; + + setOpen(false)}> + + Delete Account + Permanently delete your account and all data. + +
+

This action cannot be undone.

+
+ + + + +
+``` + +### Different Sizes + +```tsx + + Small +
Compact content
+
+ + + Large +
More content
+
+ + + Full Screen +
Maximum space
+
+``` + +### Custom Close Behavior + +```tsx + + + Read Only + +
+

User must click "Done" to close this modal.

+
+ + + +
+``` + +### Custom Styling + +```tsx + + + Custom Styled + +
Content
+
+``` + +## Accessibility + +- **Focus Management**: When opened, the modal traps focus within itself. On close, focus returns to the element that triggered it. +- **Keyboard Support**: + - `Escape` closes the modal (configurable via `closeOnEscape`) + - `Tab` cycles through focusable elements inside the modal +- **ARIA**: The close button includes `sr-only` text for screen readers. + +## Sub-Components + +The Modal uses a composition API. Import sub-components to structure your modal: + +```tsx +import { + Modal, + ModalContent, + ModalHeader, + ModalTitle, + ModalDescription, + ModalFooter, + ModalClose, + ModalOverlay, +} from "@plugin-ui/ui"; +``` + +| Sub-Component | Purpose | +|---------------|---------| +| `ModalHeader` | Header container with border | +| `ModalTitle` | Heading (`

`) with default styling | +| `ModalDescription` | Description text with padding | +| `ModalFooter` | Footer container with border and button layout | +| `ModalContent` | Main content container (rarely used directly) | +| `ModalOverlay` | Backdrop overlay (rarely used directly) | +| `ModalClose` | Close button (rarely used directly) | + +All sub-components accept `className` prop for custom styling. diff --git a/docs/NOTICE.md b/docs/NOTICE.md index 7acc351..3b501c3 100644 --- a/docs/NOTICE.md +++ b/docs/NOTICE.md @@ -1,168 +1,168 @@ -# Notice Component - -A callout-style notice component for displaying short, important messages with a left border accent. Similar to Alert but designed for simpler, title-only messages. - -## Import - -```tsx -import { Notice, NoticeTitle, NoticeAction } from "@/components/ui/notice" -``` - -## Components - -| Component | Description | -|-----------|-------------| -| `Notice` | Main container with left border accent | -| `NoticeTitle` | Message text (medium weight) | -| `NoticeAction` | Action button/container (top-right absolute) | - ---- - -## Notice Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `variant` | `default \| destructive \| success \| warning \| info` | `"default"` | Visual style (border + title color) | -| `bgColor` | `string` | `"#EFEAFF"` | Custom background color (CSS value) | -| `borderColor` | `string` | - | Custom left border color (CSS value) | -| `titleColor` | `string` | - | Custom title text color (CSS value) | -| `className` | `string` | - | Additional CSS classes | -| `style` | `CSSProperties` | - | Additional inline styles | -| `...props` | `ComponentProps<"div">` | - | Standard HTML div attributes | - -### Variant Styles - -| Variant | Border Color | Title Color | Background | -|---------|--------------|-------------|------------| -| `default` | `#9CA3AF` (gray) | `#25252D` | `#EFEAFF` (default) | -| `destructive` | Red-500 | Red-700 | `#EFEAFF` | -| `success` | Green-500 | Green-700 | `#EFEAFF` | -| `warning` | Yellow-500 | Yellow-900 | `#EFEAFF` | -| `info` | Blue-500 | Blue-700 | `#EFEAFF` | - ---- - -## Usage Examples - -### Basic Notice - -```tsx - - Default notice message. - -``` - -### All Variants - -```tsx -// Default - - Neutral info displayed here. Carry on. - - -// Success - - A successful message appears to those who did well. - - -// Destructive (Error) - - An error occurred while processing your request. - - -// Warning - - Warning! Please review before you continue. - - -// Info - - Informational message for your reference. - -``` - -### With Close Button - -```tsx - - Success message with close button. - - - - -``` - -### Custom Colors - -```tsx -// Custom purple notice - - Custom purple notice with left border. - -``` - -### Dismissible Notice (With State) - -```tsx -import { useState } from "react" - -function DismissibleNotice() { - const [isVisible, setIsVisible] = useState(true) - - if (!isVisible) return null - - return ( - - Informational notice that can be dismissed. - - - - - ) -} -``` - ---- - -## Notice vs Alert - -| Feature | Notice | Alert | -|---------|--------|-------| -| **Style** | Left border only (callout) | Full border | -| **Content** | Title only | Title + Description | -| **Use Case** | Brief messages | Detailed messages | -| **Border** | 3px left accent | Full rounded border | -| **Background** | Light purple default | Varied by variant | - ---- - -## Best Practices - -- Use `Notice` for brief, single-line messages -- Use `Alert` when you need descriptions or detailed content -- Always include a close button for dismissible notices -- Keep title text short and actionable -- Use `variant` to match the message severity -- Use custom colors to match your brand when needed - ---- - -## Accessibility - -The `Notice` component includes `role="status"` for screen readers, ensuring messages are properly announced without being treated as urgent alerts (unlike `Alert` which uses `role="alert"`). +# Notice Component + +A callout-style notice component for displaying short, important messages with a left border accent. Similar to Alert but designed for simpler, title-only messages. + +## Import + +```tsx +import { Notice, NoticeTitle, NoticeAction } from "@/components/ui/notice" +``` + +## Components + +| Component | Description | +|-----------|-------------| +| `Notice` | Main container with left border accent | +| `NoticeTitle` | Message text (medium weight) | +| `NoticeAction` | Action button/container (top-right absolute) | + +--- + +## Notice Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `variant` | `default \| destructive \| success \| warning \| info` | `"default"` | Visual style (border + title color) | +| `bgColor` | `string` | `"#EFEAFF"` | Custom background color (CSS value) | +| `borderColor` | `string` | - | Custom left border color (CSS value) | +| `titleColor` | `string` | - | Custom title text color (CSS value) | +| `className` | `string` | - | Additional CSS classes | +| `style` | `CSSProperties` | - | Additional inline styles | +| `...props` | `ComponentProps<"div">` | - | Standard HTML div attributes | + +### Variant Styles + +| Variant | Border Color | Title Color | Background | +|---------|--------------|-------------|------------| +| `default` | `#9CA3AF` (gray) | `#25252D` | `#EFEAFF` (default) | +| `destructive` | Red-500 | Red-700 | `#EFEAFF` | +| `success` | Green-500 | Green-700 | `#EFEAFF` | +| `warning` | Yellow-500 | Yellow-900 | `#EFEAFF` | +| `info` | Blue-500 | Blue-700 | `#EFEAFF` | + +--- + +## Usage Examples + +### Basic Notice + +```tsx + + Default notice message. + +``` + +### All Variants + +```tsx +// Default + + Neutral info displayed here. Carry on. + + +// Success + + A successful message appears to those who did well. + + +// Destructive (Error) + + An error occurred while processing your request. + + +// Warning + + Warning! Please review before you continue. + + +// Info + + Informational message for your reference. + +``` + +### With Close Button + +```tsx + + Success message with close button. + + + + +``` + +### Custom Colors + +```tsx +// Custom purple notice + + Custom purple notice with left border. + +``` + +### Dismissible Notice (With State) + +```tsx +import { useState } from "react" + +function DismissibleNotice() { + const [isVisible, setIsVisible] = useState(true) + + if (!isVisible) return null + + return ( + + Informational notice that can be dismissed. + + + + + ) +} +``` + +--- + +## Notice vs Alert + +| Feature | Notice | Alert | +|---------|--------|-------| +| **Style** | Left border only (callout) | Full border | +| **Content** | Title only | Title + Description | +| **Use Case** | Brief messages | Detailed messages | +| **Border** | 3px left accent | Full rounded border | +| **Background** | Light purple default | Varied by variant | + +--- + +## Best Practices + +- Use `Notice` for brief, single-line messages +- Use `Alert` when you need descriptions or detailed content +- Always include a close button for dismissible notices +- Keep title text short and actionable +- Use `variant` to match the message severity +- Use custom colors to match your brand when needed + +--- + +## Accessibility + +The `Notice` component includes `role="status"` for screen readers, ensuring messages are properly announced without being treated as urgent alerts (unlike `Alert` which uses `role="alert"`). diff --git a/docs/STORYBOOK.md b/docs/STORYBOOK.md index 77df325..001d628 100644 --- a/docs/STORYBOOK.md +++ b/docs/STORYBOOK.md @@ -1,395 +1,395 @@ -# Writing Storybook Stories for Plugin UI - -This guide explains how to write Storybook stories for each component in `plugin-ui`. - -## Quick start - -```bash -npm run storybook -``` - -Then open **http://localhost:6006/** in your browser. - -Stories live next to components: `src/components/ui/ComponentName.stories.js` (or `.tsx` if you use TypeScript). - ---- - -## Story file pattern - -One story file per component, colocated with the component: - -``` -src/components/ui/ - button.tsx - Button.stories.tsx - input.tsx - Input.stories.tsx -``` - ---- - -## Basic story template - -```tsx -// ComponentName.stories.tsx -import type { Meta, StoryObj } from "@storybook/react"; -import { ComponentName } from "./component-name"; - -const meta = { - title: "UI/ComponentName", - component: ComponentName, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - // Control props and document behavior - }, -} satisfies Meta; - -export default meta; - -type Story = StoryObj; - -export const Default: Story = { - args: { - // default props - }, -}; -``` - -- **`meta`**: `title` (sidebar path), `component`, `parameters`, `tags`, `argTypes`. -- **`Story`**: Type from your component props. -- **Named exports**: Each export (e.g. `Default`, `Primary`) becomes a story. -- **Controls**: Stories that use `args` get inputs in the **Controls** panel; when you change a value there, the story updates live. Add a **With controls** story (and `argTypes` for the props you want to tweak) to make this obvious—see **UI/Button**, **UI/Label**, and **UI/Input**. - ---- - -## 1. Simple components (Button, Badge, Input, Label) - -**Button example** — variants and sizes as stories: - -```tsx -// Button.stories.tsx -import type { Meta, StoryObj } from "@storybook/react"; -import { Button } from "./button"; - -const meta = { - title: "UI/Button", - component: Button, - parameters: { layout: "centered" }, - tags: ["autodocs"], - argTypes: { - variant: { - control: "select", - options: ["default", "secondary", "outline", "destructive", "ghost", "link"], - }, - size: { - control: "select", - options: ["default", "sm", "lg", "icon"], - }, - disabled: { control: "boolean" }, - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { children: "Button" }, -}; - -export const AllVariants: Story = { - render: () => ( -
- - - - - - -
- ), -}; -``` - -**Input example**: - -```tsx -// Input.stories.tsx -import type { Meta, StoryObj } from "@storybook/react"; -import { Input } from "./input"; -import { Label } from "./label"; - -const meta = { - title: "UI/Input", - component: Input, - parameters: { layout: "centered" }, - tags: ["autodocs"], - argTypes: { - placeholder: { control: "text" }, - disabled: { control: "boolean" }, - type: { control: "select", options: ["text", "email", "password"] }, - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { placeholder: "Placeholder" }, -}; - -export const WithLabel: Story = { - render: (args) => ( -
- - -
- ), - args: { placeholder: "you@example.com" }, -}; -``` - ---- - -## 2. Compound components (Modal, Card, Alert) - -Compose the full component in the story: - -```tsx -// Modal.stories.tsx -import type { Meta, StoryObj } from "@storybook/react"; -import { useState } from "react"; -import { - Modal, - ModalHeader, - ModalTitle, - ModalDescription, - ModalFooter, - Button, -} from "./index"; - -const meta = { - title: "UI/Modal", - parameters: { layout: "centered" }, - tags: ["autodocs"], -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: function Render() { - const [open, setOpen] = useState(false); - return ( - <> - - setOpen(false)}> - - Modal title - Description here. - -
Content
- - - - -
- - ); - }, -}; -``` - ---- - -## 3. Components with Base UI (Select, DropdownMenu, AlertDialog) - -Use the same compound structure; keep **state** inside the story (e.g. `open`, selected value): - -```tsx -// Select.stories.tsx -import type { Meta, StoryObj } from "@storybook/react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "./select"; - -const meta = { - title: "UI/Select", - component: Select, - parameters: { layout: "centered" }, - tags: ["autodocs"], -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: () => ( - - ), -}; -``` - ---- - -## 4. ArgTypes best practices - -- **Control type**: `select` for enums, `boolean` for flags, `text` for strings. -- **Document required props** and sensible defaults in `args`. -- **Actions**: Use `onClick: { action: "clicked" }` to log events in the Actions panel. - -```tsx -argTypes: { - onClick: { action: "clicked" }, - variant: { - control: "select", - options: ["default", "secondary"], - description: "Visual style", - }, -}, -``` - ---- - -## 5. Decorators (global theme / layout) - -All stories run inside the preview decorators (see `.storybook/preview.tsx`), which: - -- Import `src/styles.css` (Tailwind + design tokens). -- Wrap the app in `ThemeProvider` with a `.pui-root` container. - -So every story already has the correct theme and scoping. For a single story you can add a local decorator: - -```tsx -export const DarkBackground: Story = { - decorators: [ - (Story) => ( -
- -
- ), - ], - args: { variant: "outline" }, -}; -``` - ---- - -## 6. Checklist per component - -- [ ] Create `ComponentName.stories.tsx` next to `component-name.tsx`. -- [ ] Set `title: "UI/ComponentName"` (or a subgroup like `"UI/Forms/Input"`). -- [ ] Export `meta` with `component` and `argTypes` for main props. -- [ ] Add at least one **Default** story with `args`. -- [ ] Add variant/size stories (e.g. **AllVariants**) with `render` if helpful. -- [ ] For compound components, use `render` and internal state. -- [ ] Use `tags: ["autodocs"]` so Docs tab is generated. - ---- - -## 7. Running and building - -- **Dev**: `npm run storybook` — opens the Storybook dev server. -- **Build**: `npm run build-storybook` — outputs static files to `storybook-static/` (add to `.gitignore` if desired). - -### Deploy to GitHub Pages (CI/CD) - -A GitHub Actions workflow deploys Storybook to GitHub Pages on push to `main` or `master`. - -1. **Enable GitHub Pages** in the repo: **Settings → Pages → Build and deployment → Source**: choose **GitHub Actions**. -2. Push to `main`/`master` (or run the **Deploy Storybook to GitHub Pages** workflow manually). The workflow: - - Runs `npm ci` and `npm run build-storybook` with `STORYBOOK_BASE_PATH=//` so assets load correctly. - - Uploads `storybook-static` and deploys via `actions/deploy-pages`. -3. The site will be at **`https://.github.io//`** (e.g. `https://mrabbani.github.io/plugin-ui/`). - -Workflow file: `.github/workflows/deploy-storybook.yml`. - ---- - -## Testing in Storybook - -Stories are test cases for your components. Storybook supports several ways to test UIs in the same environment as your stories. See [How to test UIs with Storybook](https://storybook.js.org/docs/writing-tests). - -**Types of tests** - -| Type | What it does | -|------|----------------| -| **Render tests** | Every story is a smoke test: it passes when the story renders without error. | -| **Interaction tests** | Use a `play` function to simulate user actions and assert with `expect`, `fn`, `userEvent`, `within` from `storybook/test`. Example: **Button → Disabled**. | -| **Accessibility tests** | [@storybook/addon-a11y](https://storybook.js.org/docs/writing-tests/accessibility-testing) runs axe-core on each story. Enable via the **Accessibility** checkbox in the test widget. | -| **Visual tests** | [@chromatic-com/storybook](https://storybook.js.org/docs/writing-tests/visual-testing) (Chromatic) compares story screenshots to baselines. | -| **Snapshot tests** | Compare rendered markup to a stored snapshot. | - -**In this project** - -- **Interaction:** `Button.stories.jsx` has a **Disabled** story with a `play` function (see Interactions panel). -- **Accessibility:** Addon installed; `parameters.a11y.test: "error"` in preview so violations fail when you run component tests with the Accessibility checkbox on. -- **Visual:** Chromatic addon installed; use a [Chromatic](https://www.chromatic.com/) account to run visual tests. - -**More** - -- [Interaction testing](https://storybook.js.org/docs/writing-tests/interaction-testing) — play function API, steps, mocks. -- [Accessibility testing](https://storybook.js.org/docs/writing-tests/accessibility-testing) — workflow, CLI/CI, config. -- [Visual testing](https://storybook.js.org/docs/writing-tests/visual-testing) — Chromatic setup. -- [Testing in CI](https://storybook.js.org/docs/writing-tests/test-runner#run-tests-in-ci) — run tests in CI. - ---- - -## 8. Running component tests (with Accessibility) - -To run interaction and accessibility checks from the UI: - -1. **Expand the test widget** (test runner panel in Storybook). -2. **Check the Accessibility checkbox** so a11y checks run with component tests. -3. **Click “Run component tests”** to execute play functions and accessibility checks. - -If any test fails, use the **Accessibility** tab in the bottom panel to inspect violations. The addon highlights violating elements in the preview and explains how to fix them. - -**Take it further** - -- [Accessibility testing docs](https://storybook.js.org/docs/writing-tests/accessibility-testing) — recommended workflow, CLI/CI, and how to configure checks. - -**Accessibility configuration (preview)** - -In `.storybook/preview.js`, `parameters.a11y` is set so that: - -- **`test: "error"`** — accessibility violations fail component tests (when the Accessibility checkbox is on). Use **`"todo"`** on a story or meta to turn failures into warnings while you fix issues; use **`"off"`** to skip a11y for that story. -- **`config`** — [axe.configure()](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure) options (e.g. enable/disable rules). -- **`options`** — [axe.run() options](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter) (e.g. `runOnly` for WCAG 2.2 AAA). -- **Per-story:** set `parameters.a11y` or `globals.a11y.manual: true` in a story to override or disable auto checks. - ---- - -## File reference - -| File / folder | Purpose | -|----------------------|----------------------------------------------| -| `.storybook/main.ts` | Storybook config, story globs, addons | -| `.storybook/preview.tsx` | Global decorators, ThemeProvider, styles | -| `src/components/ui/*.stories.tsx` | One story file per component | - -Use the existing **Button** and **Input** story files (`Button.stories.js`, `Input.stories.js`) as templates for the rest of the components. - ---- - -## 9. Story format in this repo - -The included examples use **plain JavaScript** (`.stories.js`) and `React.createElement` so that Storybook builds without extra Babel/TypeScript configuration. For new components you can: - -- **Option A (recommended for consistency):** Copy `Button.stories.js` or `Input.stories.js`, rename to `YourComponent.stories.js`, and use `React.createElement` for any custom `render` functions. -- **Option B (TypeScript/JSX):** Use the templates in sections 1–3 above in a `.stories.tsx` file with `import type` and JSX. That requires a Storybook setup that transpiles TypeScript/JSX (e.g. running `npx storybook@latest init` and choosing the TypeScript option, or adding the right loaders in `.storybook/main.ts`). +# Writing Storybook Stories for Plugin UI + +This guide explains how to write Storybook stories for each component in `plugin-ui`. + +## Quick start + +```bash +npm run storybook +``` + +Then open **http://localhost:6006/** in your browser. + +Stories live next to components: `src/components/ui/ComponentName.stories.js` (or `.tsx` if you use TypeScript). + +--- + +## Story file pattern + +One story file per component, colocated with the component: + +``` +src/components/ui/ + button.tsx + Button.stories.tsx + input.tsx + Input.stories.tsx +``` + +--- + +## Basic story template + +```tsx +// ComponentName.stories.tsx +import type { Meta, StoryObj } from "@storybook/react"; +import { ComponentName } from "./component-name"; + +const meta = { + title: "UI/ComponentName", + component: ComponentName, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + // Control props and document behavior + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + // default props + }, +}; +``` + +- **`meta`**: `title` (sidebar path), `component`, `parameters`, `tags`, `argTypes`. +- **`Story`**: Type from your component props. +- **Named exports**: Each export (e.g. `Default`, `Primary`) becomes a story. +- **Controls**: Stories that use `args` get inputs in the **Controls** panel; when you change a value there, the story updates live. Add a **With controls** story (and `argTypes` for the props you want to tweak) to make this obvious—see **UI/Button**, **UI/Label**, and **UI/Input**. + +--- + +## 1. Simple components (Button, Badge, Input, Label) + +**Button example** — variants and sizes as stories: + +```tsx +// Button.stories.tsx +import type { Meta, StoryObj } from "@storybook/react"; +import { Button } from "./button"; + +const meta = { + title: "UI/Button", + component: Button, + parameters: { layout: "centered" }, + tags: ["autodocs"], + argTypes: { + variant: { + control: "select", + options: ["default", "secondary", "outline", "destructive", "ghost", "link"], + }, + size: { + control: "select", + options: ["default", "sm", "lg", "icon"], + }, + disabled: { control: "boolean" }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { children: "Button" }, +}; + +export const AllVariants: Story = { + render: () => ( +
+ + + + + + +
+ ), +}; +``` + +**Input example**: + +```tsx +// Input.stories.tsx +import type { Meta, StoryObj } from "@storybook/react"; +import { Input } from "./input"; +import { Label } from "./label"; + +const meta = { + title: "UI/Input", + component: Input, + parameters: { layout: "centered" }, + tags: ["autodocs"], + argTypes: { + placeholder: { control: "text" }, + disabled: { control: "boolean" }, + type: { control: "select", options: ["text", "email", "password"] }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { placeholder: "Placeholder" }, +}; + +export const WithLabel: Story = { + render: (args) => ( +
+ + +
+ ), + args: { placeholder: "you@example.com" }, +}; +``` + +--- + +## 2. Compound components (Modal, Card, Alert) + +Compose the full component in the story: + +```tsx +// Modal.stories.tsx +import type { Meta, StoryObj } from "@storybook/react"; +import { useState } from "react"; +import { + Modal, + ModalHeader, + ModalTitle, + ModalDescription, + ModalFooter, + Button, +} from "./index"; + +const meta = { + title: "UI/Modal", + parameters: { layout: "centered" }, + tags: ["autodocs"], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: function Render() { + const [open, setOpen] = useState(false); + return ( + <> + + setOpen(false)}> + + Modal title + Description here. + +
Content
+ + + + +
+ + ); + }, +}; +``` + +--- + +## 3. Components with Base UI (Select, DropdownMenu, AlertDialog) + +Use the same compound structure; keep **state** inside the story (e.g. `open`, selected value): + +```tsx +// Select.stories.tsx +import type { Meta, StoryObj } from "@storybook/react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "./select"; + +const meta = { + title: "UI/Select", + component: Select, + parameters: { layout: "centered" }, + tags: ["autodocs"], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => ( + + ), +}; +``` + +--- + +## 4. ArgTypes best practices + +- **Control type**: `select` for enums, `boolean` for flags, `text` for strings. +- **Document required props** and sensible defaults in `args`. +- **Actions**: Use `onClick: { action: "clicked" }` to log events in the Actions panel. + +```tsx +argTypes: { + onClick: { action: "clicked" }, + variant: { + control: "select", + options: ["default", "secondary"], + description: "Visual style", + }, +}, +``` + +--- + +## 5. Decorators (global theme / layout) + +All stories run inside the preview decorators (see `.storybook/preview.tsx`), which: + +- Import `src/styles.css` (Tailwind + design tokens). +- Wrap the app in `ThemeProvider` with a `.pui-root` container. + +So every story already has the correct theme and scoping. For a single story you can add a local decorator: + +```tsx +export const DarkBackground: Story = { + decorators: [ + (Story) => ( +
+ +
+ ), + ], + args: { variant: "outline" }, +}; +``` + +--- + +## 6. Checklist per component + +- [ ] Create `ComponentName.stories.tsx` next to `component-name.tsx`. +- [ ] Set `title: "UI/ComponentName"` (or a subgroup like `"UI/Forms/Input"`). +- [ ] Export `meta` with `component` and `argTypes` for main props. +- [ ] Add at least one **Default** story with `args`. +- [ ] Add variant/size stories (e.g. **AllVariants**) with `render` if helpful. +- [ ] For compound components, use `render` and internal state. +- [ ] Use `tags: ["autodocs"]` so Docs tab is generated. + +--- + +## 7. Running and building + +- **Dev**: `npm run storybook` — opens the Storybook dev server. +- **Build**: `npm run build-storybook` — outputs static files to `storybook-static/` (add to `.gitignore` if desired). + +### Deploy to GitHub Pages (CI/CD) + +A GitHub Actions workflow deploys Storybook to GitHub Pages on push to `main` or `master`. + +1. **Enable GitHub Pages** in the repo: **Settings → Pages → Build and deployment → Source**: choose **GitHub Actions**. +2. Push to `main`/`master` (or run the **Deploy Storybook to GitHub Pages** workflow manually). The workflow: + - Runs `npm ci` and `npm run build-storybook` with `STORYBOOK_BASE_PATH=//` so assets load correctly. + - Uploads `storybook-static` and deploys via `actions/deploy-pages`. +3. The site will be at **`https://.github.io//`** (e.g. `https://mrabbani.github.io/plugin-ui/`). + +Workflow file: `.github/workflows/deploy-storybook.yml`. + +--- + +## Testing in Storybook + +Stories are test cases for your components. Storybook supports several ways to test UIs in the same environment as your stories. See [How to test UIs with Storybook](https://storybook.js.org/docs/writing-tests). + +**Types of tests** + +| Type | What it does | +|------|----------------| +| **Render tests** | Every story is a smoke test: it passes when the story renders without error. | +| **Interaction tests** | Use a `play` function to simulate user actions and assert with `expect`, `fn`, `userEvent`, `within` from `storybook/test`. Example: **Button → Disabled**. | +| **Accessibility tests** | [@storybook/addon-a11y](https://storybook.js.org/docs/writing-tests/accessibility-testing) runs axe-core on each story. Enable via the **Accessibility** checkbox in the test widget. | +| **Visual tests** | [@chromatic-com/storybook](https://storybook.js.org/docs/writing-tests/visual-testing) (Chromatic) compares story screenshots to baselines. | +| **Snapshot tests** | Compare rendered markup to a stored snapshot. | + +**In this project** + +- **Interaction:** `Button.stories.jsx` has a **Disabled** story with a `play` function (see Interactions panel). +- **Accessibility:** Addon installed; `parameters.a11y.test: "error"` in preview so violations fail when you run component tests with the Accessibility checkbox on. +- **Visual:** Chromatic addon installed; use a [Chromatic](https://www.chromatic.com/) account to run visual tests. + +**More** + +- [Interaction testing](https://storybook.js.org/docs/writing-tests/interaction-testing) — play function API, steps, mocks. +- [Accessibility testing](https://storybook.js.org/docs/writing-tests/accessibility-testing) — workflow, CLI/CI, config. +- [Visual testing](https://storybook.js.org/docs/writing-tests/visual-testing) — Chromatic setup. +- [Testing in CI](https://storybook.js.org/docs/writing-tests/test-runner#run-tests-in-ci) — run tests in CI. + +--- + +## 8. Running component tests (with Accessibility) + +To run interaction and accessibility checks from the UI: + +1. **Expand the test widget** (test runner panel in Storybook). +2. **Check the Accessibility checkbox** so a11y checks run with component tests. +3. **Click “Run component tests”** to execute play functions and accessibility checks. + +If any test fails, use the **Accessibility** tab in the bottom panel to inspect violations. The addon highlights violating elements in the preview and explains how to fix them. + +**Take it further** + +- [Accessibility testing docs](https://storybook.js.org/docs/writing-tests/accessibility-testing) — recommended workflow, CLI/CI, and how to configure checks. + +**Accessibility configuration (preview)** + +In `.storybook/preview.js`, `parameters.a11y` is set so that: + +- **`test: "error"`** — accessibility violations fail component tests (when the Accessibility checkbox is on). Use **`"todo"`** on a story or meta to turn failures into warnings while you fix issues; use **`"off"`** to skip a11y for that story. +- **`config`** — [axe.configure()](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure) options (e.g. enable/disable rules). +- **`options`** — [axe.run() options](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter) (e.g. `runOnly` for WCAG 2.2 AAA). +- **Per-story:** set `parameters.a11y` or `globals.a11y.manual: true` in a story to override or disable auto checks. + +--- + +## File reference + +| File / folder | Purpose | +|----------------------|----------------------------------------------| +| `.storybook/main.ts` | Storybook config, story globs, addons | +| `.storybook/preview.tsx` | Global decorators, ThemeProvider, styles | +| `src/components/ui/*.stories.tsx` | One story file per component | + +Use the existing **Button** and **Input** story files (`Button.stories.js`, `Input.stories.js`) as templates for the rest of the components. + +--- + +## 9. Story format in this repo + +The included examples use **plain JavaScript** (`.stories.js`) and `React.createElement` so that Storybook builds without extra Babel/TypeScript configuration. For new components you can: + +- **Option A (recommended for consistency):** Copy `Button.stories.js` or `Input.stories.js`, rename to `YourComponent.stories.js`, and use `React.createElement` for any custom `render` functions. +- **Option B (TypeScript/JSX):** Use the templates in sections 1–3 above in a `.stories.tsx` file with `import type` and JSX. That requires a Storybook setup that transpiles TypeScript/JSX (e.g. running `npx storybook@latest init` and choosing the TypeScript option, or adding the right loaders in `.storybook/main.ts`). diff --git a/docs/THEME-PRESETS.md b/docs/THEME-PRESETS.md index 70f9a76..b2976f3 100644 --- a/docs/THEME-PRESETS.md +++ b/docs/THEME-PRESETS.md @@ -1,109 +1,109 @@ -# Theme Presets - -plugin-ui ships with **theme presets** — ready-made `ThemeTokens` objects you can pass to `ThemeProvider` for consistent branding without defining every variable yourself. - -## Importing presets - -Presets are exported from the main package and from the themes subpath: - -```tsx -// From main entry -import { - ThemeProvider, - defaultTheme, - defaultDarkTheme, - dokanTheme, - dokanDarkTheme, - blueTheme, - blueDarkTheme, - greenTheme, - greenDarkTheme, - amberTheme, - amberDarkTheme, - slateTheme, - slateDarkTheme, - createTheme, - createDarkTheme, -} from "@wedevs/plugin-ui"; - -// Or from themes subpath -import { - defaultTheme, - dokanTheme, - createTheme, -} from "@wedevs/plugin-ui/themes"; -``` - -## Built-in presets - -| Preset | Description | Use case | -|--------|-------------|----------| -| **defaultTheme** / **defaultDarkTheme** | Purple primary, neutral base | Generic / fallback | -| **dokanTheme** / **dokanDarkTheme** | Purple (#7047EB), smaller radius | Dokan / marketplace | -| **blueTheme** / **blueDarkTheme** | Blue primary | WeMail, communication | -| **greenTheme** / **greenDarkTheme** | Green primary | Growth, success, nature | -| **amberTheme** / **amberDarkTheme** | Amber/orange primary | Warm, alerts | -| **slateTheme** / **slateDarkTheme** | Slate/neutral primary | Minimal, professional | - -Each preset overrides only a subset of tokens (typically `primary`, `primaryForeground`, `ring`, `radius`). Light presets merge with plugin-ui’s default light tokens; dark presets merge with `defaultDarkTheme`. - -## Using presets with ThemeProvider - -```tsx -import { ThemeProvider, dokanTheme, dokanDarkTheme } from "@wedevs/plugin-ui"; - -function App() { - return ( - - {/* Your app */} - - ); -} -``` - -## Creating custom themes from presets - -Use **createTheme** (light) and **createDarkTheme** (dark) to start from a base and override only what you need: - -```tsx -import { createTheme, createDarkTheme, blueTheme, blueDarkTheme } from "@wedevs/plugin-ui"; - -// Extend a preset -const myTheme = createTheme({ - ...blueTheme, - primary: "oklch(0.55 0.22 350)", // Your brand pink - radius: "0.375rem", -}); - -const myDarkTheme = createDarkTheme({ - ...blueDarkTheme, - primary: "oklch(0.75 0.18 350)", -}); -``` - -**createTheme** merges your tokens over `defaultTheme`. -**createDarkTheme** merges over `defaultDarkTheme`. -Spreading a preset (e.g. `...blueTheme`) then overriding specific keys is the usual pattern. - -## Defining a theme from scratch - -You can pass any `ThemeTokens` object. For the full list of tokens and how they map to CSS, see [VARIABLES-MAPPING.md](./VARIABLES-MAPPING.md). Minimal example: - -```tsx -const customTheme = { - primary: "oklch(0.62 0.21 350)", - primaryForeground: "oklch(1 0 0)", - ring: "oklch(0.62 0.21 350)", - radius: "0.5rem", -}; -``` - -## Summary - -- Use **presets** for quick, consistent branding (Dokan, WeMail, green, amber, slate, default). -- Use **createTheme** / **createDarkTheme** to extend defaults or a preset with a few overrides. -- Use **ThemeProvider** `tokens` and `darkTokens` with presets or custom `ThemeTokens` so your app respects the theme and variable mapping. +# Theme Presets + +plugin-ui ships with **theme presets** — ready-made `ThemeTokens` objects you can pass to `ThemeProvider` for consistent branding without defining every variable yourself. + +## Importing presets + +Presets are exported from the main package and from the themes subpath: + +```tsx +// From main entry +import { + ThemeProvider, + defaultTheme, + defaultDarkTheme, + dokanTheme, + dokanDarkTheme, + blueTheme, + blueDarkTheme, + greenTheme, + greenDarkTheme, + amberTheme, + amberDarkTheme, + slateTheme, + slateDarkTheme, + createTheme, + createDarkTheme, +} from "@wedevs/plugin-ui"; + +// Or from themes subpath +import { + defaultTheme, + dokanTheme, + createTheme, +} from "@wedevs/plugin-ui/themes"; +``` + +## Built-in presets + +| Preset | Description | Use case | +|--------|-------------|----------| +| **defaultTheme** / **defaultDarkTheme** | Purple primary, neutral base | Generic / fallback | +| **dokanTheme** / **dokanDarkTheme** | Purple (#7047EB), smaller radius | Dokan / marketplace | +| **blueTheme** / **blueDarkTheme** | Blue primary | WeMail, communication | +| **greenTheme** / **greenDarkTheme** | Green primary | Growth, success, nature | +| **amberTheme** / **amberDarkTheme** | Amber/orange primary | Warm, alerts | +| **slateTheme** / **slateDarkTheme** | Slate/neutral primary | Minimal, professional | + +Each preset overrides only a subset of tokens (typically `primary`, `primaryForeground`, `ring`, `radius`). Light presets merge with plugin-ui’s default light tokens; dark presets merge with `defaultDarkTheme`. + +## Using presets with ThemeProvider + +```tsx +import { ThemeProvider, dokanTheme, dokanDarkTheme } from "@wedevs/plugin-ui"; + +function App() { + return ( + + {/* Your app */} + + ); +} +``` + +## Creating custom themes from presets + +Use **createTheme** (light) and **createDarkTheme** (dark) to start from a base and override only what you need: + +```tsx +import { createTheme, createDarkTheme, blueTheme, blueDarkTheme } from "@wedevs/plugin-ui"; + +// Extend a preset +const myTheme = createTheme({ + ...blueTheme, + primary: "oklch(0.55 0.22 350)", // Your brand pink + radius: "0.375rem", +}); + +const myDarkTheme = createDarkTheme({ + ...blueDarkTheme, + primary: "oklch(0.75 0.18 350)", +}); +``` + +**createTheme** merges your tokens over `defaultTheme`. +**createDarkTheme** merges over `defaultDarkTheme`. +Spreading a preset (e.g. `...blueTheme`) then overriding specific keys is the usual pattern. + +## Defining a theme from scratch + +You can pass any `ThemeTokens` object. For the full list of tokens and how they map to CSS, see [VARIABLES-MAPPING.md](./VARIABLES-MAPPING.md). Minimal example: + +```tsx +const customTheme = { + primary: "oklch(0.62 0.21 350)", + primaryForeground: "oklch(1 0 0)", + ring: "oklch(0.62 0.21 350)", + radius: "0.5rem", +}; +``` + +## Summary + +- Use **presets** for quick, consistent branding (Dokan, WeMail, green, amber, slate, default). +- Use **createTheme** / **createDarkTheme** to extend defaults or a preset with a few overrides. +- Use **ThemeProvider** `tokens` and `darkTokens` with presets or custom `ThemeTokens` so your app respects the theme and variable mapping. diff --git a/docs/VARIABLES-MAPPING.md b/docs/VARIABLES-MAPPING.md index 3f0086b..28cf4fe 100644 --- a/docs/VARIABLES-MAPPING.md +++ b/docs/VARIABLES-MAPPING.md @@ -1,188 +1,188 @@ -# Variables Mapping - -This document describes how design tokens and CSS variables are mapped across the plugin-ui theme system. - -## Overview - -The theme system has three layers: - -1. **ThemeTokens (TypeScript)** — camelCase keys in `ThemeProvider` (e.g. `primaryForeground`) -2. **Base CSS variables** — kebab-case custom properties on `.pui-root` (e.g. `--primary-foreground`) -3. **Tailwind theme** — `@theme inline` mappings for utilities (e.g. `--color-primary-foreground: var(--primary-foreground)`) - ---- - -## 1. ThemeTokens → Base CSS Variables - -**Where:** `src/providers/theme-provider.tsx` - -Theme tokens (JS object) are converted to CSS custom properties and applied as inline styles on the `.pui-root` container. - -**Conversion rule:** camelCase → kebab-case with `--` prefix. - -- Implemented by `toKebabCase()` and `tokensToCssVariables()`. -- Example: `primaryForeground` → `--primary-foreground` -- Chart tokens: `chart1` → `--chart-1`, `chart2` → `--chart-2`, etc. - -| ThemeToken (camelCase) | CSS variable (base) | -|------------------------|---------------------| -| `background` | `--background` | -| `foreground` | `--foreground` | -| `card` | `--card` | -| `cardForeground` | `--card-foreground` | -| `popover` | `--popover` | -| `popoverForeground` | `--popover-foreground` | -| `primary` | `--primary` | -| `primaryForeground` | `--primary-foreground` | -| `secondary` | `--secondary` | -| `secondaryForeground` | `--secondary-foreground` | -| `muted` | `--muted` | -| `mutedForeground` | `--muted-foreground` | -| `accent` | `--accent` | -| `accentForeground` | `--accent-foreground` | -| `destructive` | `--destructive` | -| `destructiveForeground` | `--destructive-foreground` | -| `success` | `--success` | -| `successForeground` | `--success-foreground` | -| `warning` | `--warning` | -| `warningForeground` | `--warning-foreground` | -| `info` | `--info` | -| `infoForeground` | `--info-foreground` | -| `border` | `--border` | -| `input` | `--input` | -| `ring` | `--ring` | -| `chart1` … `chart5` | `--chart-1` … `--chart-5` | -| `sidebar` | `--sidebar` | -| `sidebarForeground` | `--sidebar-foreground` | -| `sidebarPrimary` | `--sidebar-primary` | -| `sidebarPrimaryForeground` | `--sidebar-primary-foreground` | -| `sidebarAccent` | `--sidebar-accent` | -| `sidebarAccentForeground` | `--sidebar-accent-foreground` | -| `sidebarBorder` | `--sidebar-border` | -| `sidebarRing` | `--sidebar-ring` | -| `fontSans` | `--font-sans` | -| `fontSerif` | `--font-serif` | -| `fontMono` | `--font-mono` | -| `radius` | `--radius` | -| *(any custom key)* | `--` | - -**Modal:** When `Modal` renders in a portal (outside the main `ThemeProvider` tree), it creates its own `.pui-root` and copies the same token → CSS variable mapping so theme applies inside the modal. See `src/components/ui/modal.tsx` (CSS variable copy logic). - ---- - -## 2. Base CSS Variables (defaults) - -**Where:** `src/styles.css` (`.pui-root` and `.pui-root.dark`) - -Default values for the base variables are defined in CSS. They are overridden when `ThemeProvider` passes `tokens` (and `darkTokens`), which are converted and applied as inline styles. - -Base variables include: - -- **Colors:** `--background`, `--foreground`, `--card`, `--card-foreground`, `--popover`, `--popover-foreground`, `--primary`, `--primary-foreground`, `--secondary`, `--secondary-foreground`, `--muted`, `--muted-foreground`, `--accent`, `--accent-foreground`, `--destructive`, `--destructive-foreground`, `--success`, `--success-foreground`, `--warning`, `--warning-foreground`, `--info`, `--info-foreground`, `--border`, `--input`, `--ring`, `--chart-1` … `--chart-5`, `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-primary-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring` -- **Typography:** `--font-sans`, `--font-serif`, `--font-mono` -- **Radius:** `--radius` -- **Shadows:** `--shadow-2xs`, `--shadow-xs`, `--shadow-sm`, `--shadow`, `--shadow-md`, `--shadow-lg`, `--shadow-xl`, `--shadow-2xl` - ---- - -## 3. Tailwind v4 theme mapping - -**Where:** `src/styles.css` (`@theme inline { ... }`) - -Tailwind v4 expects theme variables with specific names. The base variables are mapped into this theme so utility classes work. - -### Colors - -| Tailwind theme variable | Maps from (base) | -|------------------------|------------------| -| `--color-background` | `var(--background)` | -| `--color-foreground` | `var(--foreground)` | -| `--color-card` | `var(--card)` | -| `--color-card-foreground` | `var(--card-foreground)` | -| `--color-popover` | `var(--popover)` | -| `--color-popover-foreground` | `var(--popover-foreground)` | -| `--color-primary` | `var(--primary)` | -| `--color-primary-foreground` | `var(--primary-foreground)` | -| `--color-secondary` | `var(--secondary)` | -| `--color-secondary-foreground` | `var(--secondary-foreground)` | -| `--color-muted` | `var(--muted)` | -| `--color-muted-foreground` | `var(--muted-foreground)` | -| `--color-accent` | `var(--accent)` | -| `--color-accent-foreground` | `var(--accent-foreground)` | -| `--color-destructive` | `var(--destructive)` | -| `--color-destructive-foreground` | `var(--destructive-foreground)` | -| `--color-success` | `var(--success)` | -| `--color-success-foreground` | `var(--success-foreground)` | -| `--color-warning` | `var(--warning)` | -| `--color-warning-foreground` | `var(--warning-foreground)` | -| `--color-info` | `var(--info)` | -| `--color-info-foreground` | `var(--info-foreground)` | -| `--color-border` | `var(--border)` | -| `--color-input` | `var(--input)` | -| `--color-ring` | `var(--ring)` | -| `--color-chart-1` … `--color-chart-5` | `var(--chart-1)` … `var(--chart-5)` | -| `--color-sidebar` | `var(--sidebar)` | -| `--color-sidebar-foreground` | `var(--sidebar-foreground)` | -| `--color-sidebar-primary` | `var(--sidebar-primary)` | -| `--color-sidebar-primary-foreground` | `var(--sidebar-primary-foreground)` | -| `--color-sidebar-accent` | `var(--sidebar-accent)` | -| `--color-sidebar-accent-foreground` | `var(--sidebar-accent-foreground)` | -| `--color-sidebar-border` | `var(--sidebar-border)` | -| `--color-sidebar-ring` | `var(--sidebar-ring)` | - -Usage examples: `bg-background`, `text-foreground`, `bg-primary`, `text-primary-foreground`, `border-border`, `ring-ring`, etc. - -### Typography - -| Tailwind theme variable | Maps from (base) | -|------------------------|------------------| -| `--font-sans` | `var(--font-sans)` | -| `--font-serif` | `var(--font-serif)` | -| `--font-mono` | `var(--font-mono)` | - -Usage: `font-sans`, `font-serif`, `font-mono`. - -### Border radius - -| Tailwind theme variable | Formula | -|------------------------|--------| -| `--radius-sm` | `calc(var(--radius) - 4px)` | -| `--radius-md` | `calc(var(--radius) - 2px)` | -| `--radius-lg` | `var(--radius)` | -| `--radius-xl` | `calc(var(--radius) + 4px)` | -| `--radius-2xl` | `calc(var(--radius) + 8px)` | - -Usage: `rounded-sm`, `rounded-md`, `rounded-lg`, `rounded-xl`, `rounded-2xl`. - -### Shadows - -| Tailwind theme variable | Maps from (base) | -|------------------------|------------------| -| `--shadow-2xs` | `var(--shadow-2xs)` | -| `--shadow-xs` | `var(--shadow-xs)` | -| `--shadow-sm` | `var(--shadow-sm)` | -| `--shadow` | `var(--shadow)` | -| `--shadow-md` | `var(--shadow-md)` | -| `--shadow-lg` | `var(--shadow-lg)` | -| `--shadow-xl` | `var(--shadow-xl)` | -| `--shadow-2xl` | `var(--shadow-2xl)` | - -Usage: `shadow-sm`, `shadow`, `shadow-md`, etc. - ---- - -## Flow summary - -``` -ThemeTokens (JS) → tokensToCssVariables() → inline styles on .pui-root - ↓ - Base CSS variables (--primary, etc.) - ↓ - @theme inline in styles.css - ↓ - Tailwind theme (--color-primary, etc.) - ↓ - Utilities: bg-primary, text-foreground, rounded-md, … -``` - -**Custom tokens:** Any extra key on `ThemeTokens` is converted with the same rule (`camelCase` → `--kebab-case`) and set on `.pui-root`. To use them in Tailwind you must either reference `var(--your-token)` in CSS or extend the `@theme inline` block with the corresponding `--color-*` / other theme variable. +# Variables Mapping + +This document describes how design tokens and CSS variables are mapped across the plugin-ui theme system. + +## Overview + +The theme system has three layers: + +1. **ThemeTokens (TypeScript)** — camelCase keys in `ThemeProvider` (e.g. `primaryForeground`) +2. **Base CSS variables** — kebab-case custom properties on `.pui-root` (e.g. `--primary-foreground`) +3. **Tailwind theme** — `@theme inline` mappings for utilities (e.g. `--color-primary-foreground: var(--primary-foreground)`) + +--- + +## 1. ThemeTokens → Base CSS Variables + +**Where:** `src/providers/theme-provider.tsx` + +Theme tokens (JS object) are converted to CSS custom properties and applied as inline styles on the `.pui-root` container. + +**Conversion rule:** camelCase → kebab-case with `--` prefix. + +- Implemented by `toKebabCase()` and `tokensToCssVariables()`. +- Example: `primaryForeground` → `--primary-foreground` +- Chart tokens: `chart1` → `--chart-1`, `chart2` → `--chart-2`, etc. + +| ThemeToken (camelCase) | CSS variable (base) | +|------------------------|---------------------| +| `background` | `--background` | +| `foreground` | `--foreground` | +| `card` | `--card` | +| `cardForeground` | `--card-foreground` | +| `popover` | `--popover` | +| `popoverForeground` | `--popover-foreground` | +| `primary` | `--primary` | +| `primaryForeground` | `--primary-foreground` | +| `secondary` | `--secondary` | +| `secondaryForeground` | `--secondary-foreground` | +| `muted` | `--muted` | +| `mutedForeground` | `--muted-foreground` | +| `accent` | `--accent` | +| `accentForeground` | `--accent-foreground` | +| `destructive` | `--destructive` | +| `destructiveForeground` | `--destructive-foreground` | +| `success` | `--success` | +| `successForeground` | `--success-foreground` | +| `warning` | `--warning` | +| `warningForeground` | `--warning-foreground` | +| `info` | `--info` | +| `infoForeground` | `--info-foreground` | +| `border` | `--border` | +| `input` | `--input` | +| `ring` | `--ring` | +| `chart1` … `chart5` | `--chart-1` … `--chart-5` | +| `sidebar` | `--sidebar` | +| `sidebarForeground` | `--sidebar-foreground` | +| `sidebarPrimary` | `--sidebar-primary` | +| `sidebarPrimaryForeground` | `--sidebar-primary-foreground` | +| `sidebarAccent` | `--sidebar-accent` | +| `sidebarAccentForeground` | `--sidebar-accent-foreground` | +| `sidebarBorder` | `--sidebar-border` | +| `sidebarRing` | `--sidebar-ring` | +| `fontSans` | `--font-sans` | +| `fontSerif` | `--font-serif` | +| `fontMono` | `--font-mono` | +| `radius` | `--radius` | +| *(any custom key)* | `--` | + +**Modal:** When `Modal` renders in a portal (outside the main `ThemeProvider` tree), it creates its own `.pui-root` and copies the same token → CSS variable mapping so theme applies inside the modal. See `src/components/ui/modal.tsx` (CSS variable copy logic). + +--- + +## 2. Base CSS Variables (defaults) + +**Where:** `src/styles.css` (`.pui-root` and `.pui-root.dark`) + +Default values for the base variables are defined in CSS. They are overridden when `ThemeProvider` passes `tokens` (and `darkTokens`), which are converted and applied as inline styles. + +Base variables include: + +- **Colors:** `--background`, `--foreground`, `--card`, `--card-foreground`, `--popover`, `--popover-foreground`, `--primary`, `--primary-foreground`, `--secondary`, `--secondary-foreground`, `--muted`, `--muted-foreground`, `--accent`, `--accent-foreground`, `--destructive`, `--destructive-foreground`, `--success`, `--success-foreground`, `--warning`, `--warning-foreground`, `--info`, `--info-foreground`, `--border`, `--input`, `--ring`, `--chart-1` … `--chart-5`, `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-primary-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring` +- **Typography:** `--font-sans`, `--font-serif`, `--font-mono` +- **Radius:** `--radius` +- **Shadows:** `--shadow-2xs`, `--shadow-xs`, `--shadow-sm`, `--shadow`, `--shadow-md`, `--shadow-lg`, `--shadow-xl`, `--shadow-2xl` + +--- + +## 3. Tailwind v4 theme mapping + +**Where:** `src/styles.css` (`@theme inline { ... }`) + +Tailwind v4 expects theme variables with specific names. The base variables are mapped into this theme so utility classes work. + +### Colors + +| Tailwind theme variable | Maps from (base) | +|------------------------|------------------| +| `--color-background` | `var(--background)` | +| `--color-foreground` | `var(--foreground)` | +| `--color-card` | `var(--card)` | +| `--color-card-foreground` | `var(--card-foreground)` | +| `--color-popover` | `var(--popover)` | +| `--color-popover-foreground` | `var(--popover-foreground)` | +| `--color-primary` | `var(--primary)` | +| `--color-primary-foreground` | `var(--primary-foreground)` | +| `--color-secondary` | `var(--secondary)` | +| `--color-secondary-foreground` | `var(--secondary-foreground)` | +| `--color-muted` | `var(--muted)` | +| `--color-muted-foreground` | `var(--muted-foreground)` | +| `--color-accent` | `var(--accent)` | +| `--color-accent-foreground` | `var(--accent-foreground)` | +| `--color-destructive` | `var(--destructive)` | +| `--color-destructive-foreground` | `var(--destructive-foreground)` | +| `--color-success` | `var(--success)` | +| `--color-success-foreground` | `var(--success-foreground)` | +| `--color-warning` | `var(--warning)` | +| `--color-warning-foreground` | `var(--warning-foreground)` | +| `--color-info` | `var(--info)` | +| `--color-info-foreground` | `var(--info-foreground)` | +| `--color-border` | `var(--border)` | +| `--color-input` | `var(--input)` | +| `--color-ring` | `var(--ring)` | +| `--color-chart-1` … `--color-chart-5` | `var(--chart-1)` … `var(--chart-5)` | +| `--color-sidebar` | `var(--sidebar)` | +| `--color-sidebar-foreground` | `var(--sidebar-foreground)` | +| `--color-sidebar-primary` | `var(--sidebar-primary)` | +| `--color-sidebar-primary-foreground` | `var(--sidebar-primary-foreground)` | +| `--color-sidebar-accent` | `var(--sidebar-accent)` | +| `--color-sidebar-accent-foreground` | `var(--sidebar-accent-foreground)` | +| `--color-sidebar-border` | `var(--sidebar-border)` | +| `--color-sidebar-ring` | `var(--sidebar-ring)` | + +Usage examples: `bg-background`, `text-foreground`, `bg-primary`, `text-primary-foreground`, `border-border`, `ring-ring`, etc. + +### Typography + +| Tailwind theme variable | Maps from (base) | +|------------------------|------------------| +| `--font-sans` | `var(--font-sans)` | +| `--font-serif` | `var(--font-serif)` | +| `--font-mono` | `var(--font-mono)` | + +Usage: `font-sans`, `font-serif`, `font-mono`. + +### Border radius + +| Tailwind theme variable | Formula | +|------------------------|--------| +| `--radius-sm` | `calc(var(--radius) - 4px)` | +| `--radius-md` | `calc(var(--radius) - 2px)` | +| `--radius-lg` | `var(--radius)` | +| `--radius-xl` | `calc(var(--radius) + 4px)` | +| `--radius-2xl` | `calc(var(--radius) + 8px)` | + +Usage: `rounded-sm`, `rounded-md`, `rounded-lg`, `rounded-xl`, `rounded-2xl`. + +### Shadows + +| Tailwind theme variable | Maps from (base) | +|------------------------|------------------| +| `--shadow-2xs` | `var(--shadow-2xs)` | +| `--shadow-xs` | `var(--shadow-xs)` | +| `--shadow-sm` | `var(--shadow-sm)` | +| `--shadow` | `var(--shadow)` | +| `--shadow-md` | `var(--shadow-md)` | +| `--shadow-lg` | `var(--shadow-lg)` | +| `--shadow-xl` | `var(--shadow-xl)` | +| `--shadow-2xl` | `var(--shadow-2xl)` | + +Usage: `shadow-sm`, `shadow`, `shadow-md`, etc. + +--- + +## Flow summary + +``` +ThemeTokens (JS) → tokensToCssVariables() → inline styles on .pui-root + ↓ + Base CSS variables (--primary, etc.) + ↓ + @theme inline in styles.css + ↓ + Tailwind theme (--color-primary, etc.) + ↓ + Utilities: bg-primary, text-foreground, rounded-md, … +``` + +**Custom tokens:** Any extra key on `ThemeTokens` is converted with the same rule (`camelCase` → `--kebab-case`) and set on `.pui-root`. To use them in Tailwind you must either reference `var(--your-token)` in CSS or extend the `@theme inline` block with the corresponding `--color-*` / other theme variable. diff --git a/docs/breadcrumb-component.md b/docs/breadcrumb-component.md index 67162b3..ea4321d 100644 --- a/docs/breadcrumb-component.md +++ b/docs/breadcrumb-component.md @@ -1,392 +1,392 @@ -# Breadcrumb Component - -A navigation breadcrumb component following ShadCN design patterns for displaying hierarchical navigation paths. - -## Installation - -```bash -npm install @wedevs/plugin-ui -``` - -## Import - -```tsx -import { - Breadcrumb, - BreadcrumbList, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbPage, - BreadcrumbSeparator, - BreadcrumbEllipsis -} from "@wedevs/plugin-ui"; -``` - -## Basic Usage - -```tsx - - - - Home - - - - Current Page - - - -``` - -## Multi-Level Navigation - -```tsx - - - - Settings - - - - Shipping - - - - Service - - - - Settings - - - -``` - -## With Icons - -### Icon on Each Item -```tsx - - - - - - Settings - - - - - - - Shipping - - - - - - - Settings - - - - -``` - -### With Home Icon -```tsx - - - - - - Home - - - - - Products - - - - Electronics - - - - Laptop - - - -``` - -## Custom Separators - -### Slash Separator -```tsx - - - - Home - - - / - - - Documentation - - - / - - - Breadcrumbs - - - -``` - -### Dot Separator -```tsx - - - - Home - - - - - - Blog - - - - - - Article - - - -``` - -## With Ellipsis - -For long paths, use ellipsis to collapse middle items: - -```tsx - - - - Home - - - - - - - - Components - - - - Breadcrumb - - - -``` - -## Responsive Breadcrumbs - -Hide certain items on smaller screens: - -```tsx - - - - Home - - - - Products - - - - Category - - - - Current Page - - - -``` - -## Component Structure - -### Breadcrumb -The root navigation element. - -```tsx - - {/* BreadcrumbList */} - -``` - -### BreadcrumbList -Ordered list container for breadcrumb items. - -```tsx - - {/* BreadcrumbItems */} - -``` - -### BreadcrumbItem -Individual breadcrumb item container. - -```tsx - - {/* BreadcrumbLink or BreadcrumbPage */} - -``` - -### BreadcrumbLink -Clickable link for navigation. - -```tsx -Label -``` - -### BreadcrumbPage -Current page indicator (non-clickable). - -```tsx -Current Page -``` - -### BreadcrumbSeparator -Visual separator between items. Defaults to ChevronRight icon. - -```tsx - -{/* or */} - - / - -``` - -### BreadcrumbEllipsis -Collapsed items indicator. - -```tsx - -``` - -## Complete Example - -```tsx -import { Home, ChevronRight } from "lucide-react"; - - - - - - - Home - - - - - Products - - - - - Electronics - - - - - Laptops - - - -``` - -## With Dropdown Menu - -For complex hierarchies, you can integrate dropdown menus: - -```tsx - - - - Home - - - - setShowMenu(!showMenu)} /> - {/* Dropdown menu implementation */} - - - - Current - - - -``` - -## Props Reference - -### Breadcrumb Props - -Extends standard HTML `