npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@espressif/dashboard-ui-components

v1.1.1

Published

Reusable React UI components, page layouts, and utilities — Espressif's dashboard design system.

Readme

@espressif/dashboard-ui-components

npm version license node

Reusable React UI components, page layouts, and utilities — Espressif's dashboard design system.

Built with React, TypeScript, Tailwind CSS v4, and Radix UI primitives.

Live Demo


Table of Contents


Features

  • Reusable, composable React components
  • Layout shells for dashboards and entry pages
  • TypeScript support with exported types
  • Tailwind CSS v4 design tokens and shared preset
  • ESM sub-path exports (/components, /layouts, /common, /utils) so apps can narrow the entry graph; fine-grained per-component bundle shrinking is limited by the pre-built chunks (see Bundle size, Babel warnings, and imports)
  • Pure components — no internal state management, routing, or API calls
  • Published to public npm (semver ranges, pre-built ESM, no consumer-side build)

Tech Stack

  • React 18 / 19
  • TypeScript
  • Tailwind CSS v4
  • Radix UI primitives
  • Framer Motion
  • Vite (tsup bundler)

Installation

This library is published to public npm under the @espressif scope. No registry configuration or authentication is required — consumers download a pre-built tarball at install time (no clone, no consumer-side build).

npm install @espressif/dashboard-ui-components

Requires Node.js 20.19+. Any package manager works — the package ships prebuilt ESM, so there is no consumer-side build step.


Setup

1. Import Styles

Import the library's design tokens and base styles in your global Tailwind CSS entry. This one line is all a Tailwind v4 consumer needs — the imported file already tells Tailwind (via @source) to scan the library's compiled dist/ for utility classes, so you do not need to add a second @source in your app or list the package in a content array.

/* app/globals.css (or your Tailwind entry CSS) */
@import "@espressif/dashboard-ui-components/styles";

2. Peer Dependencies

Required:

npm install react react-dom tailwindcss

Optional (only if you use specific components):

npm install @tanstack/react-table  # DataTable component
npm install react-hook-form        # Form component
npm install shiki                  # Code component
npm install recharts               # Chart components

Optional: JS-config projects (tailwind.config.ts)

Tailwind v4 is CSS-first, so a tailwind.config.ts is not required. If your app still ships one — for example a project migrated from v3, or a framework that generates it — pull in the shared preset so token utilities (bg-primary, text-sidebar-foreground, …) are recognised:

// tailwind.config.ts
import espPreset from '@espressif/dashboard-ui-components/tailwind-preset'

export default {
  presets: [espPreset],
  content: [
    './src/**/*.{ts,tsx}',
    './node_modules/@espressif/dashboard-ui-components/dist/**/*.{js,mjs}',
  ],
}

Theming

The library exposes every design token as a CSS custom property. Consumer apps retheme by redeclaring those variables in their own CSS after the @import. No fork, no Tailwind config change, no build step.

Overriding an existing token (e.g. sidebar colour)

Colours are defined in two scopes: :root for light mode and .dark for dark mode. To restyle a token consistently, override in both scopes — a :root-only override will be shadowed by the library's .dark block when the app switches to dark mode.

/* app/globals.css */
@import "@espressif/dashboard-ui-components/styles";

:root {
  --color-sidebar: #10b981;
  --color-sidebar-foreground: #ffffff;
  --color-sidebar-accent: #059669;
  --color-sidebar-accent-foreground: #ffffff;
  --color-sidebar-primary: #047857;
  --color-sidebar-primary-foreground: #ffffff;
  --color-sidebar-ring: #10b981;
}

.dark {
  --color-sidebar: #064e3b;
  --color-sidebar-foreground: #d1fae5;
  --color-sidebar-accent: #065f46;
  --color-sidebar-accent-foreground: #ffffff;
  /* …repeat for every sidebar-* token you want changed in dark mode */
}

Every component that uses bg-sidebar, text-sidebar-foreground, etc. now picks up the new palette automatically — no component-level props needed.

Adding a brand-new token

Use Tailwind v4's @theme block to declare a new token. Tailwind will generate the matching utilities (bg-brand, text-brand, border-brand, …):

@import "@espressif/dashboard-ui-components/styles";

@theme {
  --color-brand: #ff7a00;
  --color-brand-foreground: #ffffff;
}
<button className="bg-brand text-brand-foreground">Ship it</button>

What can be themed

All tokens live in the library's src/styles/colors.css and src/styles/globals.css. Grouped for reference:

Brand colours--color-primary + -light / -dark / -foreground, --color-secondary + -foreground / -container / -container-foreground, --color-tertiary (same shape as secondary)

Status colours--color-error, --color-warning, --color-success, --color-info (each with -light / -dark / -foreground), plus --color-destructive + -foreground, --color-disabled + -foreground

Surface / neutral colours--color-background, --color-foreground (+ -secondary, -muted, -disabled), --color-card, --color-popover, --color-muted, --color-accent (each with -foreground), --color-surface (+ -foreground, -variant, -variant-foreground, -container-lowest, -container-low, -container, -container-high, -container-highest), --color-border, --color-input, --color-ring

Sidebar--color-sidebar, -foreground, -primary, -primary-foreground, -accent, -accent-foreground, -border, -ring

Header--color-header, --color-header-border

Charts--color-chart-1 through --color-chart-10, plus --color-chart-success, -error, -warning, -disabled, -gray, -primary, -secondary

Layout dimensions (light and dark share the same value; override in :root): --espd-sidebar-width, --espd-sidebar-width-collapsed, --espd-sidebar-width-mobile, --espd-header-height, --espd-banner-height, --espd-footer-height, --espd-hidden-footer-height, --espd-sidebar-item-font-size, --espd-sidebar-flyout-group-label-font-size

Typography and radius--font-sans (whole font stack; override in @theme to replace), --radius-lg (drives -md and -sm)

Tips

  • Put your overrides after the @import line; CSS cascade order is what makes the override win.
  • Prefer redeclaring existing tokens (in :root / .dark) over adding new ones — the built-in tokens are what every component reads from.
  • If you only need light mode, a single :root block is enough. If you support both, always mirror the change in .dark.
  • Overriding --font-sans requires an @theme block so Tailwind picks it up:
    @theme {
      --font-sans: "Inter", system-ui, sans-serif;
    }

Customising the app logo

WorkspaceLayout and EntryLayout shipped originally with a required appName: AppName prop pointing at Espressif-internal presets (espressif, rainmaker, insights, …). External consumers should omit appName and use one of the two consumer paths below instead. The internal preset path is still available for Espressif apps.

Option 1 — Pass any React node (simplest)

Every logo slot on the layouts accepts an optional ReactNode. Render whatever you like — an <img>, an <svg>, a Next.js <Image>, a component from your own design system. No wrapper, no aspect-ratio math.

import { WorkspaceLayout } from '@espressif/dashboard-ui-components/layouts'

<WorkspaceLayout
  logo={<img src="/logo.svg" alt="Acme" style={{ height: 32 }} />}
  logoCollapsed={<img src="/logo-mark.svg" alt="" style={{ height: 24 }} />}
  logoMobile={<img src="/logo-mark.svg" alt="" style={{ height: 20 }} />}
  footerBranding={<a href="https://acme.com">© Acme, Inc.</a>}
  sidebarConfig={/* … */}
>
  {/* … */}
</WorkspaceLayout>

logoCollapsed falls back to logo; logoMobile falls back to logo. Pass footerBranding={false} to hide the sidebar-footer branding entirely.

EntryLayout accepts the single logo prop:

<EntryLayout logo={<img src="/logo.svg" alt="Acme" />} heading="Welcome">
  {/* … */}
</EntryLayout>

Option 2 — Reuse the built-in AppLogo container with your own assets

If you want the library's animated container (light/dark switching, collapse animation, sidebar-width bounds) but with your own images, pass a LogoAssetConfig object to logoAssets. Layouts feed it into <AppLogo> for you.

import type { LogoAssetConfig } from '@espressif/dashboard-ui-components/common'

const acmeLogoAssets: LogoAssetConfig = {
  full: {
    light: { src: '/assets/logo/acme.png',    width: 198, height: 28 },
    dark:  { src: '/assets/logo/acme-dm.png', width: 198, height: 28 },
  },
  minimal: {
    light: { src: '/assets/logo/acme-mark.svg', width: 26, height: 32 },
    dark:  { src: '/assets/logo/acme-mark.svg', width: 39, height: 32 },
  },
}

<WorkspaceLayout
  logoAssets={acmeLogoAssets}
  footerBranding={<a href="https://acme.com">© Acme, Inc.</a>}
  sidebarConfig={/* … */}
>
  {/* … */}
</WorkspaceLayout>

The shape is { full: { light, dark? }, minimal?: { light, dark? } }. Optional fields fall back sensibly:

  • dark omitted → uses light for both modes.
  • minimal omitted → the sidebar collapsed state and mobile top-bar reuse full.
  • Simplest case (one asset works everywhere):
    { full: { light: { src: '/logo.svg', width: 180, height: 36 } } }

Each asset can also carry a per-asset alt; otherwise the component-level alt is used.

Overflow behaviour. For logoAssets (and the AppLogo component in general), the sidebar and mobile-header slots enforce hard CSS caps: the rendered <img> is clamped by the wrapping container's max-width, max-height, and object-fit: contain. Numeric values in your asset config are treated as intrinsic-size hints — even wildly oversized width / height values shrink to fit the sidebar rail (~224px expanded, ~40px collapsed) and the header height. You don't have to trim your source images to the pixel.

For Tier 1 (logo / logoCollapsed / logoMobile / footerBranding — a raw React node) the layout renders your JSX verbatim without a bounding wrapper, so you own the sizing. Set max-height, object-fit, or a fixed size on your own node if you're rendering large source images.

You can also use <AppLogo> directly if you're rendering it outside the layout:

import { AppLogo } from '@espressif/dashboard-ui-components/common'

<AppLogo logo={acmeLogoAssets} />
<AppLogo logo={acmeLogoAssets} minimal darkMode width={120} />

width, when passed, overrides the asset's width and scales height proportionally.

Option 3 — Internal Espressif preset (Espressif apps only)

Espressif apps continue to use the shorthand:

<WorkspaceLayout appName="rainmaker" sidebarConfig={/* … */}>
  {/* … */}
</WorkspaceLayout>

Setting appName also enables the default Espressif footer link. External consumers should not use this path; AppName is a closed union of Espressif-internal product ids.

Sidebar footer branding

WorkspaceLayout's sidebar footer branding is controlled by footerBranding:

| Value | Result | |-------|--------| | undefined (default) | Shows the Espressif logo + link when appName is an internal preset. Renders nothing when appName is omitted. | | false | Hides the footer branding entirely. | | A React node | Renders your node verbatim (no opacity wrapper — style it however you like). |

The older sidebarConfig.hideCompanyBranding: true still works as a shorthand for footerBranding={false}. Prefer footerBranding in new code.


Usage

Prefer sub-path entry points over the package root unless you need components, common modules, and layouts from a single entry:

// Recommended: import only the surface you need (narrower module graph than the root barrel)
import { Button, Card, Alert } from '@espressif/dashboard-ui-components/components'
import { WorkspaceLayout } from '@espressif/dashboard-ui-components/layouts'
import { AppLogo } from '@espressif/dashboard-ui-components/common'
import { cn } from '@espressif/dashboard-ui-components/utils'
// Root entry — re-exports components, common, and layouts together.
// Use when you intentionally depend on multiple surfaces; otherwise prefer sub-paths above.
import { Button, Card, Alert } from '@espressif/dashboard-ui-components'

Use named imports for the symbols you use. Avoid import * as DashboardUI from '…' for this package (worse tree-shaking hints and noisier bundles).

Types can be imported alongside values:

import {
  DateRangePicker,
  type DateRangePickerPresetId,
} from '@espressif/dashboard-ui-components/components'

Bundle size, Babel warnings, and imports

Why you may see [BABEL] … deoptimised the styling … exceeds the max of 500KB

This library is shipped as pre-bundled ESM (built with tsup). Published files include large shared chunks (for example dist/chunk-*.js). When your toolchain runs Babel on those files (common with Metro or other setups that transform node_modules), Babel switches to compact code generation for very large single files and logs an informational message. It does not mean the build is invalid; it means Babel is skipping expensive “pretty” output for that file.

What makes the shipped JS large

  • Coarse chunks: The /components entry re-exports the full public component surface. Unused named exports can often be dropped by your bundler, but anything statically reachable from what you import stays (shared chunk code, assets pulled in by those modules).
  • Eager assets: Some components ship inlined data (for example Lottie JSON for AnimatedCard, raw SVG for Illustration, and image assets embedded at build time). Importing those components pulls that payload into your graph.
  • Dependencies: Radix UI, Framer Motion, Lottie, cmdk, lucide-react, date-fns, etc. are bundled into the library build (see package.json). Heavy peers such as recharts and shiki are not bundled; install them only when you use chart or syntax-highlighted code components. The markdown stack (react-markdown, remark-gfm, rehype-slug) is likewise not bundled, but it is a regular dependency — it installs with the library and needs no action from you, and bundlers drop it from your app build when MarkdownContent is unused.

How to keep consumer builds sensible

  1. Use @espressif/dashboard-ui-components/components, /layouts, /common, or /utils instead of the root package when you do not need everything together.
  2. Install optional peer dependencies only for features you use (@tanstack/react-table, react-hook-form, shiki, recharts).
  3. Prefer production builds when measuring size; dev servers and Babel-on-vendor paths often look worse than optimized output.
  4. For web apps, use a bundle analyzer on your app if you need to chase kilobytes; expect a sizable vendor chunk for a full-featured UI kit.

Not supported for app code: importing from @espressif/dashboard-ui-components/core or deep paths that are not listed under package.json exports. Stick to the documented entry points.


AI agent rules (Cursor & Claude Code)

This package ships the same consumption guidance — supported import surfaces, optional peers, styling, and bundle expectations — for two AI coding agents:

| Agent | Shipped file | |-------|--------------| | Cursor | .cursor/rules/dashboard-ui-consumption.mdc | | Claude Code | .claude/rules/dashboard-ui-consumption.md |

Cursor

Cursor reads rules from each repo's own .cursor/rules/ directory; there is no extends mechanism for .mdc files. Copy the rule from node_modules into your repo:

mkdir -p .cursor/rules
cp node_modules/@espressif/dashboard-ui-components/.cursor/rules/dashboard-ui-consumption.mdc \
   .cursor/rules/

Claude Code

Claude Code loads project memory from CLAUDE.md and any files it @-imports. Reference the shipped guide directly from your CLAUDE.md so it stays in sync with the installed version:

@./node_modules/@espressif/dashboard-ui-components/.claude/rules/dashboard-ui-consumption.md

Or copy it into your repo if you prefer to commit a local copy:

mkdir -p .claude/rules
cp node_modules/@espressif/dashboard-ui-components/.claude/rules/dashboard-ui-consumption.md \
   .claude/rules/

Keep both current after upgrades

{
  "scripts": {
    "sync-agent-rules": "mkdir -p .cursor/rules .claude/rules && cp -f node_modules/@espressif/dashboard-ui-components/.cursor/rules/*.mdc .cursor/rules/ && cp -f node_modules/@espressif/dashboard-ui-components/.claude/rules/*.md .claude/rules/"
  }
}

Re-run after upgrading the library to pick up guidance changes. Commit the copied files so your team and CI agents see the same rules.


Available Components

Components (/components)

| Component | Description | |-----------|-------------| | Accordion | Animated expandable content sections | | Avatar | User avatar with image and fallback | | List / ListGroup / ListItem | Structured list display; rows are buttons by default, or real links when an item sets href and List is given a linkComponent adapter. Styling hooks: className / itemClassName / separatorClassName / ListGroup.labelClassName / ListItem.className, plus the always-present classes in LIST_CLASS_NAMES (.espd-list, .espd-list-group-label, .espd-list-item, .espd-list-custom-item, .espd-list-separator) | | Sidebar | Collapsible sidebar navigation | | Menu | Context and action menus | | Tooltip | Hover tooltips | | Sheet | Slide-out panel | | Dropdown Menu | Dropdown action menu | | ScrollArea | Custom scrollable container | | Form / Label / Checkbox | Form controls | | Button / ButtonGroup | Standard and grouped buttons | | Card | Content card container | | Input / InputPassword / Textarea | Text input fields | | AsyncCombobox | Async searchable combobox | | SearchBox / AdvancedSearchBox | Search inputs | | Breadcrumb | Navigation breadcrumbs | | Separator | Visual divider | | Skeleton / TableSkeleton | Loading placeholders | | Table / DataTable | Data tables with sorting and pagination | | Tabs | Tabbed content panels | | Dialog / ConfirmationDialog / FullscreenDialog | Modal dialogs | | Pagination | Page navigation controls | | Badge | Status and label badges | | Alert | Alert banners (optionally dismissible) | | Link | Styled anchor element | | Code | Syntax-highlighted code blocks | | MarkdownContent | Renders a markdown string with library primitives (GFM, heading anchors; raw HTML is escaped, not parsed) | | CopiableText / CopyButton | Copy-to-clipboard utilities | | ProgressBar | Progress indicator | | Collapsible / CollapsibleCard | Expandable content sections | | SimpleCard / BasicDetailsCard / NoDataCard | Specialized card variants | | PreviewCard | Content preview card | | PageContainer | Page wrapper with header | | DynamicList | Dynamic item list | | RequirementList | Met/unmet checklist (password rules, prerequisites); check for satisfied rows, muted circle for outstanding ones, with screen-reader state labels | | TogglableItemsList | Toggleable item list | | GlitchText / GradientText / HighlightText / ShimmeringText | Text effects | | LottieAnimationContainer | Lottie animation wrapper | | FullSizeError / InlineError | Error display components |

Layouts (/layouts)

| Component | Description | |-----------|-------------| | WorkspaceLayout | Full dashboard shell with sidebar, header, and footer | | EntryLayout | Authentication and entry page layout |

Common (/common)

| Component | Description | |-----------|-------------| | AppLogo | Application logo | | ProfileCard | User profile display card | | CopyrightCard | Copyright notice | | PageLoader | Full-page loading indicator | | MyAccountMenu | Account menu component | | FooterCard | Footer content card |


Updating the Library

How you upgrade depends on whether the new version fits inside your existing semver range.

Patch updates within the current range

If package.json already pins "^0.1.0" and the registry has a newer patch like 0.1.3, pull it in with:

npm update @espressif/dashboard-ui-components

package.json is not modified; package-lock.json is updated to the new patch. Commit the lockfile change.

Plain npm install (no args) does not pick up newer patches — it respects the existing lockfile. Use npm update (or npm install @espressif/dashboard-ui-components with the package name) to re-resolve.

Minor or major bumps (range needs to widen)

When the registry has a new minor or major version outside your current range, edit the range explicitly:

npm install @espressif/dashboard-ui-components@^0.2.0

Both package.json and package-lock.json change. Commit both.

Check the installed version

npm ls @espressif/dashboard-ui-components

See available versions

Browse the package's versions on npm for the full list of published releases.


Project Structure

src
 ├── assets              # Static assets (images, icons)
 ├── common              # Shared components (AppLogo, ProfileCard, etc.)
 ├── components          # Composed UI components
 ├── core                # Core primitives, hooks, and utilities
 ├── layouts             # Page layout shells
 ├── styles              # CSS design tokens and global styles
 ├── types               # Shared TypeScript types
 ├── utils               # Utility functions
 └── index.ts            # Main barrel export

Development

Prerequisites

  • Node.js 24.18.1 — pinned in .nvmrc and .node-version
  • npm 11.16.0 — the version bundled with that Node

If you use nvm, the repo includes an .nvmrc file:

nvm install
nvm use

npm ci and every npm run script hard-fail with EBADDEVENGINES on a different toolchain — the requirement is declared in devEngines in package.json. This is a development-only requirement; consumers of the published package only need Node 20.19+ (see Installation).

The source repository is maintained internally by Espressif. The sections below are for maintainers working in that repository.

Getting Started

Install dependencies:

npm install

Run the demo app:

npm run dev:demo

The demo resolves this package from src/ via Vite aliases. To iterate on the built output (dist/) the same way consumers do, run a watch build in another terminal:

npm run dev:lib

Developing alongside a consumer app (local package)

Consumer apps usually live in separate repositories. They depend on published artifacts: package.json exports point at dist/, not TypeScript in src/. Now that this library is published to public npm, consumers install it with a semver range (for example npm install @espressif/dashboard-ui-components@^0.1.0) with no registry configuration. Keeping dev:lib running while the consumer uses file: or a packed tarball is the closest day-to-day analogue to that install layout. So when you link the library locally, the consumer still loads whatever is in dist/. Deleting the consumer’s package-lock.json and reinstalling does not rebuild dist/; only npm run build or npm run dev:lib in this repo does.

A prepublishOnly script runs tsup when npm publish is invoked (i.e., in CI before the package is uploaded to the registry). It does not run on npm install or npm ci — local or CI — so contributor installs are fast and consumer installs from the registry never trigger a build.

Typical workflow (matches a future npm install):

  1. In the library repo: npm run dev:lib (rebuilds dist/ on every change).
  2. In the consumer repo: npm run dev (or your app’s dev server).

Point the consumer at your local checkout using either:

  • file: in the consumer’s package.json:

    "@espressif/dashboard-ui-components": "file:../path/to/esp-ui-component-library"

    With npm 10, local file: dependencies are often installed as a symlink; confirm with readlink node_modules/@espressif/dashboard-ui-components (macOS/Linux) or your OS equivalent.

  • npm link: in the library repo run npm link, then in the consumer run npm link @espressif/dashboard-ui-components.

Vite consumers: pre-bundling can cache stale copies of the package. If changes do not show up, exclude the package from dependency optimization (and clear the cache once if needed):

// vite.config.ts
export default defineConfig({
  optimizeDeps: {
    exclude: ['@espressif/dashboard-ui-components'],
  },
  // …
})

If you use server.watch.ignored, ensure the path to the linked library is not ignored.

Duplicate React: npm link can pull in a second copy of react and break hooks/context. Fix with npm overrides, align versions, or follow npm link patterns so the app and the library share one React instance.

Optional — fastest HMR (dev only): in the consumer, resolve.alias can map @espressif/dashboard-ui-components (and any subpaths you import, e.g. /layouts) to this repo’s src/ entry files. This library’s source uses the @/ alias to src/; mirror the same mapping as in demo/vite.config.ts (@ → library src) so internal imports resolve. Validate before release against dist/ (or a packed tarball), because production consumers use the built package, not raw src/.

Optional — publish-like install (separate clones): run npm pack in this repo, then in the consumer npm install /absolute/or/relative/path/to/espressif-dashboard-ui-components-0.x.x.tgz. That exercises the same file layout as the published tarball without going through the registry. Alternatively, install directly from public npm — npm install @espressif/dashboard-ui-components@<version> — which needs no .npmrc in the consumer. yalc is another option for pushing local packages into a consumer without a monorepo.

When to run npm install in the consumer again: when this library’s version or dependency list changes, or when switching between git/tag installs and file:. You should not need to delete package-lock.json for routine UI edits if watch + linking work.

Troubleshooting: if nothing updates — check dist/ timestamps, that dev:lib is running, Vite’s node_modules/.vite cache, and duplicate React. If the browser throws does not provide an export named 'useSyncExternalStore' from .../use-sync-external-store/shim/...: the shim is CommonJS; Vite must pre-bundle it, and resolve.preserveSymlinks: true often breaks dedupe with file: deps (resolution stays under the library’s nested node_modules). Fix:

  1. Turn off preserveSymlinks (omit it or set false).
  2. Keep resolve.dedupe: ['react', 'react-dom', 'use-sync-external-store'].
  3. In the consumer run npm install use-sync-external-store so a copy exists at the app root.
  4. Add optimizeDeps.include: ['use-sync-external-store', 'use-sync-external-store/shim'] alongside your existing exclude for @espressif/dashboard-ui-components.
  5. Delete node_modules/.vite and restart dev.
resolve: {
  dedupe: ['react', 'react-dom', 'use-sync-external-store'],
  // do not set preserveSymlinks: true for local file: / linked UI libraries
},
optimizeDeps: {
  exclude: ['@espressif/dashboard-ui-components'],
  include: ['use-sync-external-store', 'use-sync-external-store/shim'],
},

Monorepos: npm/pnpm workspaces are optional if an app ever lives in the same repo; they are not required for the flows above.

Deploying the Demo

To deploy the demo to AWS (S3 + CloudFront):

  1. In demo/, copy .env.example to .env and set S3_BUCKET and CLOUDFRONT_ID.
  2. Ensure AWS credentials are configured (default profile).
  3. From the repo root:
npm run deploy:demo

This builds the demo, syncs demo/dist to the S3 bucket, and creates a CloudFront invalidation. The live demo is served from https://dqsptdnp5qxr0.cloudfront.net.

Build the library:

npm run build

Developing Using Cursor

This project includes Cursor AI skills that automate common scaffolding and audit tasks. Open the project in Cursor and use the example prompts below to trigger each skill.

| Skill | Description | How to Use | |-------|-------------|------------| | Create Component | Scaffold a new UI component in src/components/ with all required files, barrel exports, and demo entry | "Create a new component called StatusIndicator" | | Create Layout | Scaffold a new page layout in src/layouts/ with all required files, barrel exports, and demo entry | "Create a new layout called DashboardLayout" | | Create Common Module | Scaffold a new common module in src/common/ with all required files, barrel exports, and demo entry | "Create a new common module called ThemeSwitcher" | | Add Demo | Add a demo entry for an existing component or common module that is missing one | "Add a demo for the Badge component" | | Audit Exports | Audit the library for missing exports, missing demos, and wiring inconsistencies | "Audit component exports and demo coverage" |


Versioning

This project follows Semantic Versioning.

MAJOR.MINOR.PATCH

| Version | Meaning | |---------|---------| | 0.1.0 | Initial release | | 0.2.0 | New components or layouts added | | 0.2.1 | Bug fix | | 1.0.0 | First stable release / breaking changes |


Contributing

  1. Create a feature branch from release/staging
  2. Make your changes (new components, layouts, bug fixes, etc.)
  3. Follow existing component patterns and file structure
  4. Add TypeScript types for all props
  5. Keep components pure — no internal API calls, routing, or state management
  6. Raise a merge request to merge your feature branch into release/staging
  7. Get the MR reviewed and approved

Example branch name:

feature/add-date-picker

Release Process

The release and publishing flow is maintained internally by Espressif and is documented in RELEASING.md (not included in the published npm package). In short: pushing a vX.Y.Z tag triggers CI, which builds, runs typecheck + tests on Node 24.18, and publishes the same tarball to public npm and the internal GitLab registry.


License

Licensed under the Apache License 2.0.

© 2026 Espressif Systems (Shanghai) CO LTD

Third-party software & trademarks

This package depends on third-party open-source software (all under permissive licenses — MIT, Apache-2.0, ISC). Those dependencies are installed separately via npm and remain under their own licenses; see THIRD-PARTY-NOTICES.md for the list, and NOTICE for attribution and trademark information.

"Espressif", the Espressif logo, and Espressif product logos are trademarks of Espressif Systems (Shanghai) CO LTD. The Apache-2.0 license does not grant any trademark rights (see Section 6 of the License).