@partodata/ui
Persian-first / RTL-native React component library for افکارسنجی (public-opinion monitoring), social listening, and influencer analytics. Supabase-style design language adapted for Farsi typography.
What's inside
- 170+ component entries — primitives, layout, forms, charts, data viz
- Domain-aware for افکارسنجی: 5 audience-stance tokens, 9-emotion scale, 4-level severity, status pulse, 10 platform tokens (incl. TV / Radio / Press), action types, score tiers
- Two themes: Light + Dark, both production-grade with full token coverage
- RTL native: CSS Logical Properties throughout — no physical left/right
- Trilingual: Persian (canonical), Arabic, English labels for every domain primitive
- Charts: 8 wrappers over Recharts + custom Visx heatmap and word-cloud, all locale-aware (Persian-digit ticks, theme-resolved colors)
- Tables: DataTable v2 with multi-column sort, sticky footer, column resize, virtualization, infinite scroll, expansion, pinning, column visibility, comparison view, CSV export
Install
pnpm add @partodata/ui
# Peer deps:
pnpm add react react-dom
# Optional — only if you want next-themes integration:
pnpm add next-themes
The library externalizes its own dependencies (recharts, @radix-ui/*, @visx/*, etc.) so they install transparently via npm. No manual peer-dep wiring required.
Setup
1. One CSS import (Tailwind v4)
In your app's CSS entry, one import wires the DS component styles + tokens, the
role→utility map (so bg-card, border-border, text-foreground/60, bg-brand
resolve in your markup too), and the dark: variant against the DS dual selectors:
/* app/globals.css */
@import 'tailwindcss';
@import '@partodata/ui/tailwind.css';
The individual styles.css + theme.css imports remain for advanced use, but
tailwind.css keeps the import order correct and adds the dark: variant.
2. Dark-default <html>
Dark is the base :root theme; light is an explicit opt-in ([data-theme='light'] /
.light). The recommended shell:
<html lang="fa" dir="rtl" className="dark" data-theme="dark">
Theme-switching apps must keep both markers synced — with next-themes:
attribute={['class', 'data-theme']}.
3. Next.js — transpile the package
// next.config.mjs
export default { transpilePackages: ['@partodata/ui'] }
Tailwind v3 (compat)
Skip the CSS import; use the JS config — presets: [partoConfig], a content
glob over ./node_modules/@partodata/ui/dist/**/*.{js,cjs},
darkMode: ['class', '[data-theme="dark"]'], and bridge tokens with bare var()
(never hsl()). Migrating from v1? Run npx parto-migrate-v2 --consumer ./src --write
and see MIGRATION-v2.md. Full recipe in the
installation guide.
Tree-shake-friendly subpath imports
For bundle-conscious apps, import each primitive from its own subpath. A
single Button import drops from ~343 KB (full barrel) to ~11 KB —
a 97% reduction:
// Bundle-conscious — each component pays for itself only
import { Button } from '@partodata/ui/button'
import { Input } from '@partodata/ui/input'
import { IranProvinceHeat } from '@partodata/ui/iran-province-heat'
import { PartoLineChart } from '@partodata/ui/line-chart'
// Or ergonomic barrel — works as before, fine if you import many components
import { Button, Input, Card, Avatar, Badge } from '@partodata/ui'
Subpaths are exported for the 25 highest-traffic components (primitives,
big surfaces, charts, domain cards, hooks). See package.json exports
for the full list. Less common components remain available via the
barrel import.
Design-system governance (the consumer gate)
The package ships the same two static gates the design system enforces on itself, so your app stays on-token, on-scale, and RTL-correct. Wire both — they catch different things and neither needs any extra install.
1. ESLint plugin — @partodata/ui/eslint-plugin
Eight flat-config rules (no hardcoded/palette colors, no off-scale text, no
physical CSS, data-slot, WCAG on-color pairing, no muted body text, no
letter-spacing on Persian). Enable the recommended set at error:
// eslint.config.js
import parto from '@partodata/ui/eslint-plugin'
export default [
{
files: ['src/**/*.{ts,tsx}'],
plugins: { parto },
rules: parto.configs.recommended.rules,
},
]
The plugin is CommonJS; a default import (ESM) or require (CJS) both resolve
to it. See eslint-rules/README.md for the per-rule reference and interop note.
2. parto-design-lint bin — the class-string / CSS scanner
A dependency-free CLI (Node built-ins only) that greps your source for the
contract violations ESLint's AST rules can miss in raw class strings and CSS:
arbitrary text-[…] / leading-[…] / max-w-[…px] / rounded-[…px], hex and
rgb()/hsl() literals, raw palette classes, and physical direction utilities.
# package.json → "scripts": { "design-lint": "parto-design-lint src" }
npx parto-design-lint src # scans ./src (default), exit 1 on any finding
npx parto-design-lint src --json # machine-readable output for CI
Exit codes: 0 clean · 1 violations · 2 zero files scanned (a
misconfigured path can never masquerade as "clean"). This replaces the
per-app design-lint.sh copies products used to hand-maintain — one shipped,
unit-tested implementation instead of N drifting bash scripts.
Quick start
import { Button, IranProvinceHeat, FilterProvider } from '@partodata/ui'
export default function Page() {
return (
<FilterProvider initialState={{ q: '', province: null }}>
<Button variant="primary">شروع</Button>
<IranProvinceHeat
data={{ tehran: 4820, isfahan: 2140, fars: 1480 }}
valueSuffix=" نظر"
topN={8}
hideMissing
showRank
/>
</FilterProvider>
)
}
Documentation
Full docs (Fumadocs v16) run locally on port 4200:
pnpm dev
The component manifest at component-manifest.md is auto-generated and lists every public export with its variants and data-slots — handy for AI agents.