Compare commits

...
83 Commits
Author SHA1 Message Date
hanzo-dev 103b8c1f91 fix: rename turbo.json pipeline → tasks (Turbo 2.0) 2026-03-28 14:52:09 -07:00
hanzo-dev c93c0f0a98 chore: gitignore generated static API files (build artifacts) 2026-03-28 14:41:32 -07:00
hanzo-dev d4c5b88519 fix: force-static on all API routes for CF Pages export
Add dynamic=force-static + generateStaticParams to registry routes.
Replace server-only chat/search routes with static stubs.
Remove conflicting /api/registry/route.ts (dir collision).
Update next.config.mjs for CF_PAGES env.
2026-03-28 00:15:05 -07:00
hanzo-dev 98632915fe chore: add generated static registry API files 2026-03-27 21:29:16 -07:00
hanzo-dev 7f19d72d28 fix: add missing AI SDK deps for chat and search routes
The chat API route (app/api/chat/route.ts) imports @ai-sdk/openai-compatible
and ai but neither was declared in app/package.json. Also skip puppeteer
Chrome download in .npmrc since it's only used for optional screenshot
capture and its postinstall failure breaks pnpm install in CI.
2026-03-27 20:28:30 -07:00
hanzo-dev 1fcf9dce94 feat: add AI chat and search API routes for ui.hanzo.ai
- /api/chat: streaming RAG chat about UI components (zen-coder-flash)
- /api/search: proxy to Hanzo Cloud search-docs with publishable key
- lib/search.ts: search config (cloud backend, pk-hanzo-ui-search-2026)

Same pattern as docs.hanzo.ai AI search. Users can chat on the site
and ask questions about components.
2026-03-27 19:31:18 -07:00
hanzo-dev 2515b596b7 feat: add registry API routes and static build for MCP tool
Add Next.js API routes at /api/registry/ for dynamic component queries
(server mode). Add build-registry-api.mts script that generates static
JSON files at /api/registry/ for CF Pages / static hosting.

Endpoints:
- /api/registry — component list
- /api/registry/components/{name} — component with source
- /api/registry/search?q= — search
- /api/registry/index — full manifest (single payload)

Static files built during `pnpm build` step.
2026-03-27 19:15:54 -07:00
hanzo-dev 2ad4de8410 fix: add component-helpers dep to checkbox/button/list-item 2026-03-27 17:26:12 -07:00
hanzo-dev 88863caa79 fix: ALL UI deps 3.0.0->3.0.1, publish 3.0.2 2026-03-27 16:50:15 -07:00
hanzo-dev 86c312b7d4 fix: purge workspace:*, publish 3.0.1 2026-03-27 15:54:38 -07:00
hanzo-dev becf552ac5 feat: all UI packages at 3.0.0 (unified) 2026-03-27 14:52:23 -07:00
hanzo-dev 6894aac69d fix: ALL UI packages 2.0.8, internal deps point to correct published versions 2026-03-27 14:18:17 -07:00
hanzo-dev 14219cf9b5 fix: ALL UI packages 2.0.7, helpers->component-helpers in all dist 2026-03-27 13:35:34 -07:00
hanzo-dev 54447b5025 fix: button/list-item import useCurrentColor from component-helpers, bump 2.0.6 2026-03-27 12:04:16 -07:00
hanzo-dev aca5c3cd41 fix: all imports @hanzo/gui-* -> @hanzogui/*, TamaguiRoot -> GuiRoot in dist, bump 2.0.5 2026-03-26 20:27:38 -07:00
hanzo-dev 1a9f041acc fix: repair JSON formatting in package.json 2026-03-24 20:32:33 -07:00
hanzo-dev 793cf731ab fix: correct package names @hanzo/gui-* -> @hanzogui/* 2026-03-24 20:31:50 -07:00
hanzo-dev 45df2ea8b9 fix: bump UI packages to 2.0.4 with consistent deps 2026-03-24 20:31:14 -07:00
hanzo-dev 1a6cc7888b fix: resolve workspace:* to 2.0.1, bump UI packages 2026-03-24 19:22:01 -07:00
hanzo-dev 179ae0de2e docs: update LLM.md with upstream sync info and current structure 2026-03-24 19:13:53 -07:00
hanzo-dev 51c4d12dce feat: sync upstream shadcn/ui — shadcn@4.1.0, v4 app, font system, chart improvements
Upstream sync from shadcn-ui/ui (1686 commits since last merge base).

Key changes:
- packages/shadcn upgraded to 4.1.0 (CLI font transformers, scaffold from github, chart colors)
- apps/v4 replaces apps/www as the primary docs/registry app
- Font system: 25+ heading font registries, font markers utility, @supports override
- Chart improvements: recharts v3 compat, radial chart fixes
- templates/ updated with monorepo variants (astro, vite, react-router, start)
- skills/shadcn AI skill definitions added
- deprecated/ removed (auth, auth-firebase, cli, www)

Preserved Hanzo-only paths: app/, pkg/, demo/, docs/, template/next/
2026-03-24 19:04:31 -07:00
hanzo-dev 6fe7078b06 fix: update checkout client 2026-03-24 18:42:34 -07:00
hanzo-dev 3cdad07e32 feat: migrate tamagui primitives to @hanzogui/*, rename tokens/tamagui to tokens/gui 2026-03-24 18:42:34 -07:00
hanzo-dev 8a492a2d18 feat: migrate UI packages from @hanzo/gui-* to @hanzogui/* scope, purge all tamagui refs, v2.0.0 2026-03-24 18:42:34 -07:00
Darkhorse7starsandhanzo-dev fb6da37f9f fix: ESLint 10 config compatibility and cleanup temp files
- Rewrite app/eslint.config.mjs to use @typescript-eslint/parser directly
  (fixes "Class extends value undefined" error with typescript-eslint 8.x)
- Add typescript-eslint override in root package.json for ESLint 10 compat
- Remove deprecated .eslintignore (use flat config ignores instead)
- Delete temp/backup files (.bak, .old, .tmp)

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-03-23 16:55:41 -05:00
Darkhorse7starsandhanzo-dev 41d618d1d8 fix: resolve CI type check and lint failures
- Downgrade app/ react-resizable-panels to v3 (code uses v3 API names)
- Add missing @eslint/js dependency for eslint.config.mjs
- Fix TS18048 in chart-line-dots-custom.tsx (null check cx/cy)
- Fix TS7006 in ai-code.tsx (explicit any types for monaco callbacks)

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-03-23 12:41:22 -05:00
Darkhorse7starsandhanzo-dev 4cb8cac44c fix: use react-resizable-panels v3 API for compatibility
Changed imports from v4 names (Group, Separator) to v3 names
(PanelGroup, PanelResizeHandle) to fix build errors in consumers
using react-resizable-panels v3. Updated peer dep to ^3.0.0.

Bump @hanzo/ui to 5.5.1.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-03-23 12:22:29 -05:00
hanzo-dev 5aee8715d7 fix: resolve 34 missing gui workspace deps to published npm versions
The gui-* packages were imported from hanzo/gui but 34 underlying
utility/core packages (gui-build, gui-core, gui-helpers, gui-web, etc.)
were not included in the workspace. Changed their workspace:* refs to
the published 2.0.0-rc.29 versions so pnpm can resolve from registry.
2026-03-22 19:02:28 -07:00
hanzo-dev aa7f45b29d chore: brand + gui publish artifacts 2026-03-22 18:38:17 -07:00
hanzo-dev 65d32dda4c feat: add @hanzo/gui-* UI component packages from hanzo/gui rebrand 2026-03-21 13:08:01 -07:00
hanzo-dev 9ba70a4e2c chore: update all dependencies to latest 2026-03-21 11:44:34 -07:00
hanzo-dev 5850560681 chore: bump @hanzo/ui to 5.5.0 (dash components) 2026-03-18 14:52:20 -07:00
hanzo-dev 189b34971f fix: clean names — dash/layout, dash/sidebar, dash/table, dash/form, dash/crud 2026-03-18 14:49:17 -07:00
hanzo-dev e010864f26 fix: rename admin->dash in tsup build config 2026-03-18 14:47:05 -07:00
hanzo-dev e74efbab46 chore: bump @hanzo/ui to 5.4.0 (adds @hanzo/ui/dash) 2026-03-18 14:40:24 -07:00
hanzo-dev 4884736c69 feat: add @hanzo/ui/dash — shared dashboard UI (layout, data table, forms, CRUD) 2026-03-18 14:39:39 -07:00
hanzo-dev bb48d51d70 fix: update repository URL from react-sdk to ui for npm provenance 2026-03-16 22:03:53 -07:00
hanzo-dev 3d88cc75eb chore: bump @hanzo/ui to 5.3.42 2026-03-16 21:51:19 -07:00
hanzo-dev bc8945ff4e fix: rename Developer tier to Pay As You Go
"Developer" is the free/OSS tier, not a paid plan. Rename to
"Pay As You Go" to match actual billing semantics.
2026-03-16 21:50:12 -07:00
hanzo-dev 9627ebbbbc feat: move upgrade CTA to top, improve zero-usage display
- Upgrade CTA now renders first (above usage card) when not subscribed
- "No usage yet" instead of "— tokens" when zero usage
- "Credits available" with balance instead of "$0.00 API spend"
- "Start using Hanzo AI to see stats" subtitle for zero-usage state
2026-03-16 21:38:50 -07:00
hanzo-dev 80147c328f feat(navigation): expand AppSwitcher from 6 to 26 apps with grouped layout
Unified app switcher is the single source of truth for cross-app navigation.
All services now accessible from every Hanzo app via HanzoHeader.

- DEFAULT_HANZO_APPS: 26 apps across 7 groups (Core, AI, Observability,
  Infrastructure, Apps, Business, Resources)
- OrgDomains: white-label domain mapping for all 4 orgs (hanzo, lux, zoo, pars)
  expanded from 7 to 27 fields
- getAppsForOrg(): returns org-aware URLs for all 26 apps
- AppSwitcher: grouped 2-column grid with section headers, scrollable
2026-03-13 04:08:28 -07:00
hanzo-dev 29b3652711 fix: expand BillingSection type to include all billing sections 2026-03-11 18:29:53 -07:00
hanzo-dev 891818443a feat(@hanzo/ui): add billing module to dist build
Add billing/index tsup entry so @hanzo/ui/billing resolves from npm.
Update package.json export to point to dist instead of source.
Bumps to v5.3.41.
2026-03-11 17:55:00 -07:00
hanzo-dev 532d8e692c feat(@hanzo/ui): add navigation/hanzo-shell build entry
Add separate tsup entry point for navigation/hanzo-shell so the
wildcard package export ./navigation/* resolves to a real dist file.
Bumps version to 5.3.40.
2026-03-11 17:40:01 -07:00
hanzo-dev e45833b5f2 feat: add hard refresh and settings to HanzoHeader shell 2026-03-11 11:38:55 -07:00
hanzo-dev dffa6cef34 feat: add @hanzo/ui/auth package with IAMLoginButton and AuthGuard
New auth components for unified IAM integration:
- IAMLoginButton: "Sign in with Hanzo" button that initiates OAuth flow
- AuthGuard: wrapper that redirects to IAM when unauthenticated
- Re-exports useHanzoAuth, UserOrgDropdown, HanzoUser/HanzoOrg types
Adds ./auth and ./auth/* subpath exports to package.json.
2026-03-10 16:59:46 -07:00
hanzo-dev 04407e9cb5 feat(shell): org-aware app switcher, white-label domains, hanzo.space
- chat.hanzo.ai → hanzo.chat (canonical domain)
- Add hanzo.space (Storage) to DEFAULT_HANZO_APPS
- Add ORG_DOMAINS map: hanzo, lux, zoo, pars with per-org domains
- Add getAppsForOrg() for white-label app switcher URLs
- HanzoHeader now resolves org-specific domains automatically
- UserOrgDropdown Account/Billing links adapt to current org
- Export OrgDomains type and getAppsForOrg from hanzo-shell
2026-03-10 16:06:51 -07:00
hanzo-dev 9f0e37d8c3 fix(billing): export BillingSection type from @hanzo/ui/billing 2026-03-10 15:32:03 -07:00
hanzo-dev bf18ef7a35 chore: gitignore dist-test output 2026-03-03 10:43:12 -08:00
hanzo-dev edda5a41b8 feat: add UserAvatar with three-tier fallback (photo → Gravatar → generative SVG)
BeamAvatar: deterministic face-like SVG from any string, zero deps, boring-avatars beam algorithm
UserAvatar: three-tier fallback — uploaded photo, Gravatar SHA-256, generative beam SVG
UserOrgDropdown now uses UserAvatar instead of simple letter initials
2026-03-03 20:37:01 -08:00
Zach Kelling 320885b073 chore(ui): bump to v5.3.38
Published with HanzoCommandPalette component.
2026-03-03 17:35:30 -08:00
Zach Kelling d97b670580 feat(ui): add HanzoCommandPalette — shared Cmd+K command palette
Zero-dependency React component for cross-app command navigation.
Supports extensible app-specific commands, keyboard nav, search filtering.
2026-03-03 17:33:30 -08:00
Zach Kelling b8ecd6c408 fix(ci): resolve TypeScript build errors blocking deployment
- resizable.tsx: update react-resizable-panels v4 imports (PanelGroup→Group, PanelResizeHandle→Separator)
- blob.ts: wrap Buffer in Uint8Array for BlobPart compatibility
- tsconfig.build.json: exclude typo-plugin JS from type-checked build
2026-03-03 14:33:02 -08:00
Zach Kelling 4086d54305 test: add static CI test to validate all docs URLs have matching MDX files
Runs 228 tests checking that every href in docs.ts config resolves to
an actual MDX file. Catches 404s before deployment without needing a
running server. Reports orphaned MDX files not in nav config.
2026-03-03 14:25:06 -08:00
Zach Kelling 6831117b97 feat: full docs modernization — zero 404s, 585 pages, dep updates
- Enable all 15 finance component docs (from .disabled → .mdx)
- Create stock-market.mdx, 8 chart docs, 8 block docs, 2 framework docs
- Add direction.tsx component (RTL/LTR support from upstream shadcn)
- Add xs/icon-xs button sizes, data-variant/data-size attributes
- Fix build-registry.mts to handle default exports in namespace re-exports
- Add default props to order-entry, positions-list, orders-history, trading-panel
- Fix 18 navigation stubs → re-export from root implementations
- Add 44 orphaned MDX files to nav (installation, dark-mode, desktop, guides, etc)
- Migrate 6 framer-motion imports → motion/react
- Bump React 19.2.0→19.2.4, Next.js 16.0.1→16.1.6, motion→12.34.0
- Align lucide-react, sonner v2, tailwind-merge v3, postcss-selector-parser v7
- Remove framer-motion dep (superseded by motion)
- Fix @types/react-dom, TypeScript versions across workspaces
2026-03-03 07:34:51 -08:00
hanzo-dev 6ea1042d44 feat: @hanzo/shop payment popup + Square billing components
- @hanzo/shop: new embeddable 3-tab topup widget (card/crypto/bank)
  - HanzoTopup dialog, TopupButton trigger
  - CardInput (Square Web Payments SDK), CryptoInput (MPC chains), BankInput (ACH)
  - hooks: useSquareCard, useTopup
- @hanzo/commerce client: add getWalletAddress, addBankAccount, addCryptoWallet, topup
- @hanzo/ui billing: comprehensive billing dashboard components
  - OverviewDashboard, PaymentManager (Square), CreditsPanel, UsagePanel
  - InvoicesPayments, TransactionsPanel, SquareCardForm
  - CostExplorer, SpendAlerts, AccountMembers, AccountSwitcher
  - BillingSettings, SupportTiersPanel, PromotionsPanel
  - GuidedSetup, AnimatedCard, StatusBar
2026-03-02 11:09:00 -08:00
hanzo-dev d9f090bb53 feat(commerce): update commerce client SDK 2026-03-01 19:40:49 -08:00
hanzo-dev e1184390dc feat(@hanzo/commerce): extend client with checkout, coupons, referrals, affiliates
Add comprehensive commerce client methods:
- validateCoupon / applyCoupon (pre-checkout validation)
- createCheckoutSession (hosted checkout, returns checkoutUrl)
- tokenizeCard (S2S card tokenization without external SDK)
- referrals: createReferralProgram, getReferralLink, trackReferral, getReferralStats
- affiliates: registerAffiliate, getAffiliateStats, getAffiliateCommissions
- Backwards-compatible: commerceUrl still works, baseUrl preferred
- Default baseUrl: https://api.hanzo.ai
- Request timeout support (timeoutMs, default 15s)
2026-03-01 16:20:08 -08:00
hanzo-dev a42e158a07 fix(ci): add NEXT_PUBLIC_APP_URL env and coverage json-summary reporter 2026-03-01 16:01:01 -08:00
hanzo-dev 6347fe0aeb feat: remove @hanzo/auth, all auth goes through @hanzo/iam
- Move pkg/auth and pkg/auth-firebase to deprecated/ (excluded from workspace)
- Remove @hanzo/auth from commerce and checkout peerDependencies
- Remove useAuth() from payment-step-form; contact form defaults to empty strings
- Update pnpm lockfile

Production auth is now handled entirely by IAM (hanzo.id). The legacy
auth package is preserved in deprecated/ for reference.
2026-03-01 15:39:33 -08:00
hanzo-dev f08abfe55a chore: update pnpm lockfile 2026-03-01 15:27:19 -08:00
hanzo-dev fbe58782d9 chore(@hanzo/ui): bump to 5.3.37 — publish model components 2026-03-01 15:25:31 -08:00
hanzo-dev 16ffaea878 feat(@hanzo/ui): add shared model components — ModelCard, ModelTable, ModelLibrary, ZenEnso
Adds @hanzo/ui/models export with data-agnostic model UI components
that work across hanzo.ai, zen-docs, and any other Hanzo site:

- ZenModelLike/ModelFamilyLike interfaces (structural compatibility
  with @hanzo/zen-models, no cross-package dependency needed)
- ModelCard: rich clickable card with status badges, spec, action buttons
- ModelTable: static table view with pricing and context columns
- ModelLibrary: full catalog with family sections and filter toggle
- ZenEnso: animated SVG enso circle logo component

Fix ./models ESM export path (dist/models/index.mjs not dist/src/models/).
2026-03-01 15:19:28 -08:00
hanzo-dev a4f1a1c16f feat: add HanzoShell shared navigation components
New pkg/ui/src/navigation/hanzo-shell/ module:
- HanzoHeader: sticky 14px header with logo/breadcrumb/app-switcher/user-dropdown
- HanzoMark: official H-mark SVG with hover origami animation + brand context menu
  (right-click → Brand Guidelines, Press Kit, Download Logo, Copy SVG, hanzo.ai)
- AppSwitcher: 3x3-grid icon → dropdown listing all Hanzo apps
- UserOrgDropdown: avatar/initials, org switcher (from IAM groups), sign-out
- useHanzoAuth: zero-dep hook reads hanzo-auth-token from localStorage,
  fetches iam.hanzo.ai/api/userinfo, maps IAM groups to org list
- DEFAULT_HANZO_APPS: Account, Billing, Console, Chat, Platform

Design tokens: bg-[#09090b]/90, border-white/[0.07], text-[11-13px], monochrome
Exported from @hanzo/ui navigation index for use across all Hanzo properties.
2026-03-01 12:38:58 -08:00
hanzo-dev 8ed0e27d73 [cleanup] remove AI slop files 2026-02-28 12:25:38 -08:00
hanzo-dev 9958dde439 feat: add @hanzo/og programmatic OG image package
Shared package for generating OpenGraph/social images across all Hanzo
sites. Supports 6 layout variants (page, model, code, stat, split,
minimal) with inline styles for next/og ImageResponse compatibility.
Includes HANZO_AI_THEME and HANZO_INDUSTRIES_THEME brand presets.
2026-02-28 10:45:00 -08:00
hanzo-dev e64e9cdbdc perf: reduce @hanzo/ui bundle 74% by removing duplicate entries and enabling minification
- Remove duplicate tsup entries: primitives-export and primitives/index
  both compiled same primitives/index-standard.ts → save 2×436K=870K
- Enable minify:true; safe because 'use client' banner is added post-build
  via onSuccess hook, not as source directive
- Update ./primitives export in package.json to point to ./dist/index.mjs
- Result: dist 10M → 2.6M, index.mjs 436K → 314K (minified)

Published as @hanzo/ui@5.3.36
2026-02-28 10:27:43 -08:00
hanzo-dev 095773b2da chore: bump versions — @hanzo/ui@5.3.35, @hanzo/auth-firebase@1.0.0, @hanzo/commerce@7.5.1 2026-02-27 21:30:24 -08:00
hanzo-dev 926985e5ad chore: update generated registry and color files from build 2026-02-27 21:28:17 -08:00
hanzo-dev 24ace5354a fix: resolve build errors — add avatar/org to HanzoUserInfoStore, fix Zod v3/v4 type mismatch, generate d.ts files in @hanzo/ui build 2026-02-27 21:20:16 -08:00
hanzo-dev 412ee93a25 chore: untrack .claude/settings.local.json, add to gitignore 2026-02-27 20:42:26 -08:00
hanzo-dev 751111a0d7 chore: update pnpm-lock.yaml 2026-02-27 19:35:51 -08:00
hanzo-dev 8dab1688f7 Remove Firebase from @hanzo/auth and @hanzo/commerce
Firebase has been completely removed from core packages:
- @hanzo/auth v2.8.0: stub implementations replace Firebase auth/wallet
- @hanzo/commerce v7.5.0: no-op order persistence replaces Firestore
- Firebase code preserved in @hanzo/auth-firebase (optional package)
- Zero Firebase imports in auth and commerce packages
2026-02-27 19:35:51 -08:00
Zoo Queenandhanzo-dev 7887485e95 feat(commerce): add billingRefund method for correction deposits
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-02-20 19:07:18 -08:00
Zoo Queenandhanzo-dev 3e1283365a refactor: clean Commerce API client — one class, no aliases
Commerce is THE client for the Commerce API. No aliases, no backwards
compat wrappers. import { Commerce } from '@hanzo/commerce'

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-02-17 21:06:22 -08:00
Zoo Queenandhanzo-dev 8919c32d0f feat: rename BillingClient to CommerceClient, add @hanzo/commerce/client
CommerceClient is the canonical Commerce API client. @hanzo/commerce/billing
re-exports as BillingClient for backwards compat.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-02-17 21:05:35 -08:00
Zoo Queenandhanzo-dev be95271bc4 fix: update repository URLs to hanzoai/ui for npm provenance
npm provenance requires repository.url to match the publishing repo.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-02-17 21:02:20 -08:00
Zoo Queenandhanzo-dev f2752141ff feat: add BillingClient to @hanzo/commerce, fix npm-publish token
- billing.ts: canonical billing client for Commerce API
- Exports at @hanzo/commerce/billing
- Fix workspace:* peerDeps (resolve to version ranges for npm compat)
- Fix npm-publish workflow to use NPM_TOKEN secret

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-02-17 20:59:46 -08:00
Zoo Queenandhanzo-dev b6311022f3 fix: add avatar and organization fields to Firebase auth store
All HanzoUserInfo implementations must include the new fields.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-02-17 20:55:38 -08:00
Zoo Queenandhanzo-dev 07f1182d52 feat: add IAM auth service as primary auth provider
- IamAuthService: OIDC/PKCE-based auth, auto-registers when IAM config present
- Enhanced types: avatar, organization fields on HanzoUserInfo
- AuthServiceConf: IAM fields (iamServerUrl, iamClientId, etc.)
- Firebase becomes optional integration, IAM is the default
- Fix npm-publish workflow: add id-token permission for provenance

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-02-17 20:52:49 -08:00
Zoo Queenandhanzo-dev bacd545b67 fix: resolve pnpm version conflict in publish workflow
Remove explicit version to let packageManager field in package.json
control the pnpm version.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-02-17 20:47:34 -08:00
Zoo Queenandhanzo-dev d95693b61a chore: bump @hanzo/auth to 2.7.0
Adds shared OrgProjectSwitcher component for unified org/project
selection across all Hanzo apps.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-02-17 20:45:28 -08:00
Zoo Queenandhanzo-dev dce79e1309 feat: add shared OrgProjectSwitcher component to @hanzo/auth
Props-driven org/project/environment switcher used across all
Hanzo services. Supports single-org display, multi-org dropdown,
project selector, and environment badge.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-02-17 20:38:07 -08:00
7812 changed files with 176353 additions and 270273 deletions
-17
View File
@@ -1,17 +0,0 @@
{
"permissions": {
"allow": [
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push:*)",
"Bash(pnpm registry:build:*)",
"Bash(pnpm build:*)",
"Bash(pnpm add:*)",
"Bash(pnpm dev)",
"Bash(jq:*)",
"Bash(curl:*)"
],
"deny": [],
"ask": []
}
}
+3
View File
@@ -0,0 +1,3 @@
{
"extends": ["@commitlint/config-conventional"]
}
+40
View File
@@ -4,3 +4,43 @@ updates:
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/templates/astro-app"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/templates/astro-monorepo"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/templates/next-app"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/templates/next-monorepo"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/templates/react-router-app"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/templates/react-router-monorepo"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/templates/start-app"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/templates/start-monorepo"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/templates/vite-app"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/templates/vite-monorepo"
schedule:
interval: "weekly"
+1 -1
View File
@@ -4,7 +4,7 @@
import { exec } from "child_process"
import fs from "fs"
const pkgJsonPath = "pkg/cli/package.json"
const pkgJsonPath = "packages/shadcn/package.json"
try {
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath))
exec("git rev-parse --short HEAD", (err, stdout) => {
+1 -1
View File
@@ -4,7 +4,7 @@
import { exec } from "child_process"
import fs from "fs"
const pkgJsonPath = "pkg/cli/package.json"
const pkgJsonPath = "packages/shadcn/package.json"
try {
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath))
exec("git rev-parse --short HEAD", (err, stdout) => {
+13 -7
View File
@@ -18,11 +18,11 @@ jobs:
with:
node-version: 20
- uses: pnpm/action-setup@v2.2.4
- uses: pnpm/action-setup@v4
name: Install pnpm
id: pnpm-install
with:
version: 8.6.1
version: 9.0.6
run_install: false
- name: Get pnpm store directory
@@ -39,7 +39,7 @@ jobs:
- name: Install dependencies
run: pnpm install
#- run: pnpm lint
- run: pnpm lint
format:
runs-on: ubuntu-latest
@@ -54,11 +54,11 @@ jobs:
with:
node-version: 20
- uses: pnpm/action-setup@v2.2.4
- uses: pnpm/action-setup@v4
name: Install pnpm
id: pnpm-install
with:
version: 8.6.1
version: 9.0.6
run_install: false
- name: Get pnpm store directory
@@ -77,6 +77,9 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Build packages
run: pnpm --filter=shadcn build
- run: pnpm format:check
tsc:
@@ -92,11 +95,11 @@ jobs:
with:
node-version: 20
- uses: pnpm/action-setup@v2.2.4
- uses: pnpm/action-setup@v4
name: Install pnpm
id: pnpm-install
with:
version: 8.6.1
version: 9.0.6
run_install: false
- name: Get pnpm store directory
@@ -113,4 +116,7 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Build packages
run: pnpm --filter=shadcn build
- run: pnpm typecheck
+5 -1
View File
@@ -1,5 +1,9 @@
name: NPM Publish
permissions:
contents: write
id-token: write
on:
workflow_dispatch:
inputs:
@@ -78,7 +82,7 @@ jobs:
- name: Bump version and publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: true
run: |
# Set up npm and pnpm auth
+4 -4
View File
@@ -10,7 +10,7 @@ on:
jobs:
comment:
if: |
github.repository_owner == 'hanzoai-ui' &&
github.repository_owner == 'shadcn-ui' &&
${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
name: Write comment to the PR
@@ -28,8 +28,8 @@ jobs:
for (const artifact of allArtifacts.data.artifacts) {
// Extract the PR number and package version from the artifact name
const match = /^npm-package-hanzoai-ui@(.*?)-pr-(\d+)/.exec(artifact.name);
const match = /^npm-package-shadcn@(.*?)-pr-(\d+)/.exec(artifact.name);
if (match) {
require("fs").appendFileSync(
process.env.GITHUB_ENV,
@@ -49,7 +49,7 @@ jobs:
A new prerelease is available for testing:
```sh
npx hanzoai-ui@${{ env.BETA_PACKAGE_VERSION }}
pnpm dlx shadcn@${{ env.BETA_PACKAGE_VERSION }}
```
- name: "Remove the autorelease label once published"
+20 -16
View File
@@ -7,10 +7,15 @@ on:
types: [labeled]
branches:
- main
permissions:
id-token: write
contents: read
jobs:
prerelease:
if: |
github.repository_owner == 'hanzoai' &&
github.repository_owner == 'shadcn-ui' &&
contains(github.event.pull_request.labels.*.name, '🚀 autorelease')
name: Build & Publish a beta release to NPM
runs-on: ubuntu-latest
@@ -18,32 +23,31 @@ jobs:
steps:
- name: Checkout Repo
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Use PNPM
uses: pnpm/action-setup@v2.2.4
uses: pnpm/action-setup@v4
with:
version: 8.6.1
version: 9.0.6
- name: Use Node.js 18
uses: actions/setup-node@v3
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
registry-url: "https://registry.npmjs.org"
cache: "pnpm"
- name: Update npm for OIDC support
run: npm install -g npm@latest
- name: Install NPM Dependencies
run: pnpm install
- name: Modify package.json version
run: node .github/version-script-beta.js
- name: Authenticate to NPM
run: echo "//registry.npmjs.org/:_authToken=$NPM_ACCESS_TOKEN" >> pkg/cli/.npmrc
env:
NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
- name: Publish Beta to NPM
run: pnpm pub:beta
@@ -51,10 +55,10 @@ jobs:
id: package-version
uses: martinbeentjes/npm-get-version-action@main
with:
path: pkg/cli
path: packages/shadcn
- name: Upload packaged artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: npm-package-hanzoai-ui@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
path: pkg/cli/dist/index.js
name: npm-package-shadcn@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
path: packages/shadcn/dist/index.js
-2
View File
@@ -57,8 +57,6 @@ jobs:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
+1 -1
View File
@@ -14,7 +14,7 @@ permissions:
jobs:
release:
if: ${{ github.repository_owner == 'hanzoai-ui' }}
if: ${{ github.repository_owner == 'shadcn-ui' }}
name: Create a PR for release workflow
runs-on: ubuntu-latest
steps:
+29 -59
View File
@@ -1,75 +1,45 @@
name: E2E and Visual Tests
name: Test
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
# Run tests weekly on Sunday at 2 AM UTC to catch breaking updates
- cron: '0 2 * * 0'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
NODE_OPTIONS: --max-old-space-size=8192
branches: ["*"]
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
node-version: [20.x]
name: pnpm test
env:
NEXT_PUBLIC_APP_URL: http://localhost:4000
NEXT_PUBLIC_V0_URL: https://v0.dev
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v2
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 22
- uses: pnpm/action-setup@v4
name: Install pnpm
id: pnpm-install
with:
version: 9.0.6
run_install: false
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install
- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps
- name: Build the project
run: pnpm build
env:
NODE_OPTIONS: --max-old-space-size=8192
- name: Run component health check
run: pnpm health-check
continue-on-error: true
- name: Run E2E tests
run: pnpm test:e2e
timeout-minutes: 30
env:
CI: true
- name: Run visual regression tests
run: pnpm test:visual
timeout-minutes: 20
continue-on-error: true
env:
CI: true
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: tests/reports/playwright-report/
retention-days: 30
- run: pnpm test
+76
View File
@@ -13,6 +13,44 @@ on:
- "apps/v4/registry/directory.json"
jobs:
check-registry-sync:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
name: check-registry-sync
permissions:
contents: read
pull-requests: write
steps:
- name: Check changed files
id: changed
env:
GH_TOKEN: ${{ github.token }}
run: |
CHANGED_FILES=$(gh pr diff ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --name-only)
DIRECTORY_CHANGED=false
REGISTRIES_CHANGED=false
if echo "$CHANGED_FILES" | grep -q "^apps/v4/registry/directory.json$"; then
DIRECTORY_CHANGED=true
fi
if echo "$CHANGED_FILES" | grep -q "^apps/v4/public/r/registries.json$"; then
REGISTRIES_CHANGED=true
fi
echo "directory_changed=$DIRECTORY_CHANGED" >> $GITHUB_OUTPUT
echo "registries_changed=$REGISTRIES_CHANGED" >> $GITHUB_OUTPUT
- name: Flag missing registries.json update
if: steps.changed.outputs.directory_changed == 'true' && steps.changed.outputs.registries_changed == 'false'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh pr edit ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --add-label "registries: invalid"
gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --body "can you run \`pnpm registry:build\` and commit the json files please?"
exit 1
validate:
runs-on: ubuntu-latest
name: pnpm validate:registries
@@ -28,6 +66,44 @@ jobs:
with:
node-version: 20
- name: Block reserved registry namespaces
env:
RESERVED_NAMESPACES: "@shadcn,@ui,@blocks,@components,@block,@component,@util,@utils,@registry,@lib,@hook,@hooks,@theme,@themes,@chart,@charts"
run: |
node <<'EOF'
const fs = require("node:fs")
const files = [
"apps/v4/public/r/registries.json",
"apps/v4/registry/directory.json",
]
const reservedNamespaces = new Set(
process.env.RESERVED_NAMESPACES.split(",").filter(Boolean)
)
function readNames(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8")).map(
(entry) => entry.name
)
}
const violations = files.flatMap((filePath) => {
return readNames(filePath)
.filter((name) => reservedNamespaces.has(name))
.map((name) => `${filePath}: ${name}`)
})
if (violations.length > 0) {
console.error("Reserved registry namespaces are not allowed:")
for (const violation of violations) {
console.error(`- ${violation}`)
}
process.exit(1)
}
EOF
- uses: pnpm/action-setup@v4
name: Install pnpm
id: pnpm-install
+3
View File
@@ -78,3 +78,6 @@ playwright-report/
.fleet
.notes
.claude/settings.local.json
pkg/ui/dist-test/
app/public/api/
+1
View File
@@ -1,2 +1,3 @@
auto-install-peers=true
link-workspace-packages=true
puppeteer_skip_download=true
+8
View File
@@ -0,0 +1,8 @@
dist
node_modules
.next
build
.contentlayer
**/fixtures
deprecated
apps/v4/registry/styles/**/*.css
+30 -48
View File
@@ -1,10 +1,10 @@
# Contributing
Thanks for your interest in contributing to ui.hanzo.com. We're happy to have you here.
Thanks for your interest in contributing to ui.shadcn.com. We're happy to have you here.
Please take a moment to review this document before submitting your first pull request. We also strongly recommend that you check for open issues and pull requests to see if someone else is working on something similar.
If you need any help, feel free to reach out to [@hanzo](https://x.com/hanzoai).
If you need any help, feel free to reach out to [@shadcn](https://twitter.com/shadcn).
## About this repository
@@ -20,28 +20,25 @@ This repository is structured as follows:
```
apps
└── www
└── v4
├── app
├── components
├── content
└── registry
── default
│ ├── example
│ └── ui
└── new-york
── new-york-v4
├── example
└── ui
packages
└── cli
└── shadcn
```
| Path | Description |
| --------------------- | ---------------------------------------- |
| `apps/www/app` | The Next.js application for the website. |
| `apps/www/components` | The React components for the website. |
| `apps/www/content` | The content for the website. |
| `apps/www/registry` | The registry for the components. |
| `packages/cli` | The `hanzo-ui` package. |
| Path | Description |
| -------------------- | ---------------------------------------- |
| `apps/v4/app` | The Next.js application for the website. |
| `apps/v4/components` | The React components for the website. |
| `apps/v4/content` | The content for the website. |
| `apps/v4/registry` | The registry for the components. |
| `packages/shadcn` | The `shadcn` package. |
## Development
@@ -79,76 +76,61 @@ You can use the `pnpm --filter=[WORKSPACE]` command to start the development pro
#### Examples
1. To run the `ui.hanzo.com` website:
1. To run the `ui.shadcn.com` website:
```bash
pnpm --filter=www dev
pnpm --filter=v4 dev
```
2. To run the `hanzo-ui` package:
2. To run the `shadcn` package:
```bash
pnpm --filter=hanzo-ui dev
pnpm --filter=shadcn dev
```
## Running the CLI Locally
To run the CLI locally, you can follow the workflow:
1. Start by running the registry (main site) to make sure the components are up to date:
1. Start by running the dev server:
```bash
pnpm v4:dev
pnpm dev
```
2. Run the development script for the CLI:
2. In another terminal tab, test the CLI by running:
```bash
pnpm hanzo:dev
```
3. In another terminal tab, test the CLI by running:
```bash
pnpm hanzo
pnpm shadcn
```
To test the CLI in a specific app, use a command like:
```bash
pnpm hanzo <init | add | ...> -c ~/Desktop/my-app
```
4. To run the tests for the CLI:
```bash
pnpm --filter=hanzo test
pnpm shadcn <init | add | ...> -c ~/Desktop/my-app
```
This workflow ensures that you are running the most recent version of the registry and testing the CLI properly in your local environment.
## Documentation
The documentation for this project is located in the `www` workspace. You can run the documentation locally by running the following command:
The documentation for this project is located in the `v4` workspace. You can run the documentation locally by running the following command:
```bash
pnpm --filter=www dev
pnpm --filter=v4 dev
```
Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/www/content/docs` directory.
Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/v4/content/docs` directory.
## Components
We use a registry system for developing components. You can find the source code for the components under `apps/www/registry`. The components are organized by styles.
We use a registry system for developing components. You can find the source code for the components under `apps/v4/registry`. The components are organized by styles.
```bash
apps
└── www
└── v4
└── registry
── default
│ ├── example
│ └── ui
└── new-york
── new-york-v4
├── example
└── ui
```
@@ -157,7 +139,7 @@ When adding or modifying components, please ensure that:
1. You make the changes for every style.
2. You update the documentation.
3. You run `pnpm build:registry` to update the registry.
3. You run `pnpm registry:build` to update the registry.
## Commit Convention
@@ -196,9 +178,9 @@ If you have a request for a new component, please open a discussion on GitHub. W
## CLI
The `hanzo-ui` package is a CLI for adding components to your project. You can find the documentation for the CLI [here](https://ui.hanzo.com/docs/cli).
The `shadcn` package is a CLI for adding components to your project. You can find the documentation for the CLI [here](https://ui.shadcn.com/docs/cli).
Any changes to the CLI should be made in the `packages/cli` directory. If you can, it would be great if you could add tests for your changes.
Any changes to the CLI should be made in the `packages/shadcn` directory. If you can, it would be great if you could add tests for your changes.
## Testing
+33 -7
View File
@@ -10,7 +10,7 @@ React component library (shadcn/ui fork). 161 components, 24+ blocks, two themes
```
ui/
app/ Documentation site (Next.js 15.3.1, React 19)
app/ Hanzo documentation site (Next.js 15.3.1, React 19)
registry/ Component registry (SOURCE OF TRUTH)
default/ui/ 150+ components
default/example/ Usage demos
@@ -18,13 +18,26 @@ ui/
new-york/ Alternative theme
content/docs/ MDX documentation
scripts/ Build scripts
apps/
v4/ Upstream shadcn v4 docs/registry app (port 4000)
packages/
shadcn/ shadcn CLI v4.1.0 (font system, chart colors, scaffold)
tests/ Integration tests for shadcn CLI
og/ OG image generation (Hanzo-only)
pkg/
ui/ Core library (npm)
auth/ Auth components (Firebase optional since v2.6.0)
auth-firebase/ Firebase auth (opt-in package)
commerce/ E-commerce components
react/ React primitives
brand/ Branding system
brands/ White-label configs (Zoo, Lux)
commerce/ E-commerce components
checkout/ Checkout flow
shop/ Shop components
agent-ui/ AI agent UI components
gui/ GUI component packages (@hanzogui/*)
tokens/ Design tokens
skills/
shadcn/ AI skill definitions for shadcn CLI
templates/ Project templates (next, vite, astro, react-router, start + monorepo variants)
template/next/ Hanzo-customized Next.js template
```
## Critical: Build Order
@@ -78,7 +91,19 @@ import { cn } from '@hanzo/ui/lib/utils'
## Tech Stack
React 18.3.1 (19 experimental), Next.js 15.3.1, Tailwind CSS (OKLCH colors), Radix UI, Turborepo + pnpm, Fumadocs (MDX), class-variance-authority.
React 19, Next.js 15.3+, Tailwind CSS 4 (OKLCH colors), Radix UI, Turborepo + pnpm, Fumadocs (MDX), class-variance-authority.
## Upstream Sync
Remote `shadcn` points to `/Users/z/work/shadcn/ui` (local clone of shadcn-ui/ui).
hanzoai/ui is NOT a GitHub fork -- no shared object store, so large merges can fail on push.
Last sync: 2026-03-24 (shadcn@4.1.0, commit 8bec9c123)
Strategy: file-level checkout from shadcn/main for specific directories (not git merge).
- Take theirs: packages/shadcn/, packages/tests/, apps/, templates/, scripts/, skills/
- Keep ours: app/, pkg/, demo/, docs/, template/next/, pnpm-workspace.yaml, package.json
- Remove: deprecated/ (upstream deleted it)
- Regenerate: pnpm-lock.yaml after sync
## Key Features
@@ -95,11 +120,12 @@ React 18.3.1 (19 experimental), Next.js 15.3.1, Tailwind CSS (OKLCH colors), Rad
- Firebase split to optional `@hanzo/auth-firebase` package (Jan 2025)
- `@hanzo/auth` v2.6.0 uses pluggable provider registry: `registerAuthProvider('firebase', FirebaseAuthService)`
## Component Stats (2025-10-18)
## Component Stats
- 161 total files, ~127 implemented, ~34 stubs
- Unique: 9 3D components, 12 AI components, 13 animation components, 15 nav variants
- 3x more components than upstream shadcn/ui (161 vs 58)
- shadcn CLI: v4.1.0 with font transformers, chart color picker, scaffold from github
## Rules
+1 -1
View File
@@ -6,4 +6,4 @@ We will investigate all legitimate reports and do our best to quickly fix the pr
Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our Open Source Software.
To do this, please visit the security tab of the repository and click the "Report a vulnerability" button.
To do this, please visit the security tab of the repository and click the [Report a vulnerability](https://github.com/shadcn-ui/ui/security/advisories/new) button.
+430 -8
View File
File diff suppressed because one or more lines are too long
+12 -5
View File
@@ -1,8 +1,15 @@
// @ts-nocheck
import { dynamic } from '@hanzo/docs-mdx/runtime/dynamic';
import * as Config from '../source.config';
import { dynamic } from "@hanzo/docs-mdx/runtime/dynamic"
const create = await dynamic<typeof Config, import("@hanzo/docs-mdx/runtime/types").InternalTypeConfig & {
DocData: {
import * as Config from "../source.config"
const create = await dynamic<
typeof Config,
import("@hanzo/docs-mdx/runtime/types").InternalTypeConfig & {
DocData: {}
}
}>(Config, {"configPath":"source.config.ts","environment":"next","outDir":".docs"}, {"doc":{"passthroughs":["extractedReferences"]}});
>(
Config,
{ configPath: "source.config.ts", environment: "next", outDir: ".docs" },
{ doc: { passthroughs: ["extractedReferences"] } }
)
+388 -192
View File
File diff suppressed because one or more lines are too long
-12
View File
@@ -1,12 +0,0 @@
node_modules/
.next/
out/
build/
dist/
next-env.d.ts
__registry__/
.source/
*.config.js
*.config.mjs
.turbo/
coverage/
-40
View File
@@ -1,40 +0,0 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"plugins": ["@typescript-eslint", "react"],
"ignorePatterns": [
"__registry__/**",
".source/**",
".next/**",
"out/**",
"build/**",
"dist/**",
".turbo/**",
"node_modules/**"
],
"rules": {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-empty-object-type": "warn",
"@typescript-eslint/ban-ts-comment": "warn",
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-require-imports": "warn",
"@typescript-eslint/no-this-alias": "warn",
"@typescript-eslint/triple-slash-reference": "warn",
"@typescript-eslint/ban-types": "off",
"react/no-unescaped-entities": "warn",
"react/jsx-no-comment-textnodes": "warn",
"react/no-find-dom-node": "warn",
"prefer-const": "warn"
}
}
+11
View File
@@ -203,6 +203,17 @@ export const Index: Record<string, any> = {
subcategory: "undefined",
chunks: []
},
"direction": {
name: "direction",
type: "components:ui",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/ui/direction")),
source: "",
files: ["registry/default/ui/direction.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"dialog": {
name: "dialog",
type: "components:ui",
+143
View File
@@ -0,0 +1,143 @@
/**
* docs-no-404.test.ts
*
* Static validation: every URL in docs.ts config has a matching MDX file.
* No server needed — runs in CI before deployment.
*
* Run: pnpm test (via vitest)
*/
import { describe, it, expect } from 'vitest'
import fs from 'node:fs'
import path from 'node:path'
const CONTENT_DIR = path.resolve(__dirname, '../content/docs')
const CONFIG_PATH = path.resolve(__dirname, '../config/docs.ts')
/** Extract all href values from docs.ts config */
function extractUrlsFromConfig(): string[] {
const content = fs.readFileSync(CONFIG_PATH, 'utf-8')
const hrefRegex = /href:\s*["'`]([^"'`]+)["'`]/g
const urls: string[] = []
let match
while ((match = hrefRegex.exec(content)) !== null) {
urls.push(match[1])
}
return urls.filter(u => u.startsWith('/docs'))
}
/** Convert a /docs/... URL to its expected MDX file path */
function urlToMdxPath(url: string): string[] {
// /docs -> index.mdx
// /docs/charts/bar -> charts/bar.mdx OR charts/bar/index.mdx
const slug = url.replace(/^\/docs\/?/, '') || ''
if (!slug) {
return [path.join(CONTENT_DIR, 'index.mdx')]
}
return [
path.join(CONTENT_DIR, `${slug}.mdx`),
path.join(CONTENT_DIR, slug, 'index.mdx'),
]
}
/** Recursively find all MDX files in content/docs */
function findAllMdxFiles(dir: string, prefix = ''): string[] {
const results: string[] = []
if (!fs.existsSync(dir)) return results
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) {
results.push(...findAllMdxFiles(fullPath, `${prefix}${entry.name}/`))
} else if (entry.name.endsWith('.mdx')) {
const slug = entry.name === 'index.mdx'
? prefix.replace(/\/$/, '')
: `${prefix}${entry.name.replace('.mdx', '')}`
if (slug) {
results.push(`/docs/${slug}`)
} else {
results.push('/docs')
}
}
}
return results
}
describe('Documentation pages — zero 404s', () => {
const configUrls = extractUrlsFromConfig()
const mdxPages = findAllMdxFiles(CONTENT_DIR)
it('should find docs config with URLs', () => {
expect(configUrls.length).toBeGreaterThan(100)
})
it('should find MDX content files', () => {
expect(mdxPages.length).toBeGreaterThan(100)
})
describe('every config URL has a matching MDX file', () => {
for (const url of configUrls) {
it(`${url}`, () => {
const possiblePaths = urlToMdxPath(url)
const exists = possiblePaths.some(p => fs.existsSync(p))
if (!exists) {
throw new Error(
`Missing MDX file for ${url}. Expected one of:\n` +
possiblePaths.map(p => ` - ${p}`).join('\n')
)
}
})
}
})
describe('every MDX file is reachable from config', () => {
const configSet = new Set(configUrls)
const unreachable: string[] = []
for (const page of mdxPages) {
if (!configSet.has(page)) {
unreachable.push(page)
}
}
it('should have no orphaned MDX files (or document known exceptions)', () => {
// Allow a small number of known orphans (category index pages, etc.)
const KNOWN_ORPHANS = new Set([
'/docs/ai/voice-settings',
'/docs/components/animation-components',
])
const unexpected = unreachable.filter(u => !KNOWN_ORPHANS.has(u))
if (unexpected.length > 0) {
console.warn(`Orphaned MDX files (not in nav config):\n${unexpected.map(u => ` ${u}`).join('\n')}`)
}
// Warn but don't fail for orphans — they just won't be in nav
expect(true).toBe(true)
})
})
it('summary: config URLs vs MDX files', () => {
const configSet = new Set(configUrls)
const mdxSet = new Set(mdxPages)
const missingMdx = configUrls.filter(u => {
const paths = urlToMdxPath(u)
return !paths.some(p => fs.existsSync(p))
})
const orphanedMdx = mdxPages.filter(p => !configSet.has(p))
console.log(`\n📊 Docs coverage report:`)
console.log(` Config URLs: ${configUrls.length}`)
console.log(` MDX files: ${mdxPages.length}`)
console.log(` Missing MDX: ${missingMdx.length}`)
console.log(` Orphaned: ${orphanedMdx.length}`)
if (missingMdx.length > 0) {
console.error(`\n❌ URLs with no MDX file:\n${missingMdx.map(u => ` ${u}`).join('\n')}`)
}
if (orphanedMdx.length > 0) {
console.warn(`\n⚠️ MDX files not in nav:\n${orphanedMdx.map(u => ` ${u}`).join('\n')}`)
}
// This is the hard check — every config URL must have content
expect(missingMdx).toEqual([])
})
})
+29 -29
View File
@@ -1,8 +1,9 @@
"use client"
import * as React from "react"
import { AlertCircle, RefreshCw } from "lucide-react"
import { Index } from "@/__registry__"
import { AlertCircle, RefreshCw } from "lucide-react"
import { Badge } from "@/registry/default/ui/badge"
import { Button } from "@/registry/default/ui/button"
import { Card } from "@/registry/default/ui/card"
@@ -64,24 +65,21 @@ function BlockPreview({ blockName }: { blockName: string }) {
setState({ loaded: true, error: true, showContent: true })
}, [])
const handleRetry = React.useCallback(
(e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
setState({ loaded: false, error: false, showContent: false })
if (iframeRef.current) {
// Force reload by resetting src
const src = iframeRef.current.src
iframeRef.current.src = ""
setTimeout(() => {
if (iframeRef.current) {
iframeRef.current.src = src
}
}, 0)
}
},
[]
)
const handleRetry = React.useCallback((e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
setState({ loaded: false, error: false, showContent: false })
if (iframeRef.current) {
// Force reload by resetting src
const src = iframeRef.current.src
iframeRef.current.src = ""
setTimeout(() => {
if (iframeRef.current) {
iframeRef.current.src = src
}
}, 0)
}
}, [])
React.useEffect(() => {
return () => {
@@ -92,7 +90,10 @@ function BlockPreview({ blockName }: { blockName: string }) {
}, [])
return (
<div ref={containerRef} className="relative aspect-[4/3] min-h-[400px] bg-muted/50">
<div
ref={containerRef}
className="relative aspect-[4/3] min-h-[400px] bg-muted/50"
>
{/* Loading skeleton */}
{!state.showContent && (
<div className="absolute inset-0 flex items-center justify-center">
@@ -273,15 +274,14 @@ export default function BuilderPage() {
<h3 className="text-base font-semibold leading-tight">
{blockName}
</h3>
{block?.category &&
block?.category !== "undefined" && (
<Badge
variant="outline"
className="shrink-0 text-xs font-medium"
>
{block.category}
</Badge>
)}
{block?.category && block?.category !== "undefined" && (
<Badge
variant="outline"
className="shrink-0 text-xs font-medium"
>
{block.category}
</Badge>
)}
</div>
{block?.subcategory &&
block?.subcategory !== "undefined" && (
File diff suppressed because it is too large Load Diff
-427
View File
@@ -1,427 +0,0 @@
"use client"
import * as React from "react"
import dynamic from "next/dynamic"
import { Index } from "@/__registry__"
import {
closestCenter,
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
} from "@dnd-kit/core"
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import { Download, Eye, GripVertical, Plus, Trash2 } from "lucide-react"
import { Button } from "@/registry/new-york/ui/button"
import { Card } from "@/registry/new-york/ui/card"
import { Input } from "@/registry/new-york/ui/input"
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
import { Separator } from "@/registry/new-york/ui/separator"
// Dynamic block component loader
const DynamicBlock = ({ blockName, scale = 1 }: { blockName: string; scale?: number }) => {
const [BlockComponent, setBlockComponent] = React.useState<React.ComponentType | null>(null)
const [error, setError] = React.useState(false)
React.useEffect(() => {
import(`@/registry/default/block/${blockName}`)
.then((mod) => {
setBlockComponent(() => mod.default)
setError(false)
})
.catch((err) => {
console.error(`Failed to load block ${blockName}:`, err)
setError(true)
})
}, [blockName])
if (error) {
return (
<div className="flex h-full items-center justify-center bg-muted/50 p-4 text-center">
<p className="text-xs text-muted-foreground">Failed to load {blockName}</p>
</div>
)
}
if (!BlockComponent) {
return (
<div className="flex h-full items-center justify-center bg-muted/50">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
</div>
)
}
return (
<div style={{ transform: `scale(${scale})`, transformOrigin: "top left" }}>
<BlockComponent />
</div>
)
}
interface PageBlock {
id: string
blockName: string
}
export default function PageBuilder() {
const [blocks, setBlocks] = React.useState<string[]>([])
const [availableBlocks, setAvailableBlocks] = React.useState<string[]>([])
const [pageBlocks, setPageBlocks] = React.useState<PageBlock[]>([])
const [activeId, setActiveId] = React.useState<string | null>(null)
const [filter, setFilter] = React.useState("")
const [viewport, setViewport] = React.useState<"desktop" | "tablet" | "mobile">("desktop")
React.useEffect(() => {
// Get block IDs from the registry index
const blockIds = Object.keys(Index.default || {}).filter((key) => {
const item = Index.default[key]
return item?.type === "components:block"
})
setBlocks(blockIds)
setAvailableBlocks(blockIds)
}, [])
const filteredBlocks = availableBlocks.filter((block) =>
block.toLowerCase().includes(filter.toLowerCase())
)
const addBlock = (blockName: string) => {
setPageBlocks([...pageBlocks, { id: crypto.randomUUID(), blockName }])
}
const removeBlock = (id: string) => {
setPageBlocks(pageBlocks.filter((b) => b.id !== id))
}
const handleDragStart = (event: DragStartEvent) => {
setActiveId(event.active.id as string)
}
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event
setActiveId(null)
if (!over || active.id === over.id) return
setPageBlocks((items) => {
const oldIndex = items.findIndex((item) => item.id === active.id)
const newIndex = items.findIndex((item) => item.id === over.id)
return arrayMove(items, oldIndex, newIndex)
})
}
const generatePageCode = () => {
const imports = pageBlocks
.map((block) => `import ${toPascalCase(block.blockName)} from "@/registry/default/block/${block.blockName}"`)
.join("\n")
const components = pageBlocks
.map((block) => ` <${toPascalCase(block.blockName)} />`)
.join("\n")
return `"use client"
import * as React from "react"
${imports}
export default function CustomPage() {
return (
<div className="flex min-h-screen flex-col">
${components}
</div>
)
}
`
}
const copyCode = async () => {
const code = generatePageCode()
await navigator.clipboard.writeText(code)
// TODO: Show toast notification
}
const downloadCode = () => {
const code = generatePageCode()
const blob = new Blob([code], { type: "text/typescript" })
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = "page.tsx"
a.click()
URL.revokeObjectURL(url)
}
const deployWithHanzo = () => {
// TODO: Integrate with Hanzo deployment API
const code = generatePageCode()
console.log("Deploying with Hanzo:", code)
// This would call hanzo deployment service
window.open("https://hanzo.ai/deploy", "_blank")
}
const toPascalCase = (str: string) => {
return str
.split("-")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join("")
}
const viewportWidths = {
desktop: "100%",
tablet: "768px",
mobile: "375px",
}
return (
<div className="flex h-screen max-h-screen gap-4 p-6">
{/* Left Sidebar - Block Library */}
<div className="w-64 space-y-4">
<div>
<h2 className="text-lg font-semibold">Block Library</h2>
<p className="text-sm text-muted-foreground">
Drag blocks to build your page
</p>
</div>
<Input
placeholder="Filter blocks..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
<ScrollArea className="h-[calc(100vh-200px)]">
<div className="space-y-4">
{filteredBlocks.map((block) => (
<Card
key={block}
className="cursor-grab overflow-hidden transition-colors hover:bg-muted"
onClick={() => addBlock(block)}
>
{/* 1/4 Scale Block Preview */}
<div className="relative h-32 overflow-hidden bg-muted/50">
<div className="pointer-events-none">
<DynamicBlock blockName={block} scale={0.25} />
</div>
{/* Overlay with block name and add button */}
<div className="absolute inset-0 flex items-center justify-center bg-background/0 opacity-0 transition-opacity hover:bg-background/80 hover:opacity-100">
<div className="flex items-center gap-2">
<Plus className="h-5 w-5" />
<span className="text-sm font-medium">Add to page</span>
</div>
</div>
</div>
<div className="border-t p-2">
<p className="truncate text-xs font-medium">{block}</p>
</div>
</Card>
))}
</div>
</ScrollArea>
</div>
<Separator orientation="vertical" />
{/* Center - Page Builder Canvas */}
<div className="flex-1 space-y-4">
<div className="flex items-center justify-between">
<div>
<h2 className="text-lg font-semibold">Page Builder</h2>
<p className="text-sm text-muted-foreground">
{pageBlocks.length} blocks in page
</p>
</div>
<div className="flex items-center gap-2">
{/* Viewport Controls */}
<div className="flex rounded-lg border">
<Button
variant={viewport === "mobile" ? "default" : "ghost"}
size="sm"
onClick={() => setViewport("mobile")}
className="rounded-r-none"
>
Mobile
</Button>
<Button
variant={viewport === "tablet" ? "default" : "ghost"}
size="sm"
onClick={() => setViewport("tablet")}
className="rounded-none border-x"
>
Tablet
</Button>
<Button
variant={viewport === "desktop" ? "default" : "ghost"}
size="sm"
onClick={() => setViewport("desktop")}
className="rounded-l-none"
>
Desktop
</Button>
</div>
<Separator orientation="vertical" className="h-8" />
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={copyCode}
disabled={pageBlocks.length === 0}
>
<svg
className="mr-2 h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<rect width="13" height="13" x="9" y="9" rx="2" ry="2" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
Copy Code
</Button>
<Button
variant="outline"
size="sm"
onClick={downloadCode}
disabled={pageBlocks.length === 0}
>
<Download className="mr-2 h-4 w-4" />
Download
</Button>
<Button
variant="default"
size="sm"
onClick={deployWithHanzo}
disabled={pageBlocks.length === 0}
>
<svg
className="mr-2 h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
</svg>
Deploy with Hanzo
</Button>
</div>
</div>
</div>
<ScrollArea className="h-[calc(100vh-140px)] rounded-lg border bg-background">
<div className="flex min-h-full items-start justify-center p-4">
<div
style={{
width: viewportWidths[viewport],
maxWidth: "100%",
transition: "width 0.3s ease",
}}
>
<DndContext
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<SortableContext
items={pageBlocks.map((b) => b.id)}
strategy={verticalListSortingStrategy}
>
<div className="min-h-[600px] bg-background">
{pageBlocks.length === 0 ? (
<div className="flex h-96 items-center justify-center rounded-lg border border-dashed text-center">
<div className="space-y-2">
<p className="text-sm text-muted-foreground">
Your page is empty
</p>
<p className="text-xs text-muted-foreground">
Click blocks from the left to add them
</p>
</div>
</div>
) : (
pageBlocks.map((block) => (
<SortableBlock
key={block.id}
id={block.id}
blockName={block.blockName}
onRemove={() => removeBlock(block.id)}
/>
))
)}
</div>
</SortableContext>
<DragOverlay>
{activeId ? (
<div className="rounded-lg border bg-card p-4 shadow-lg">
<p className="text-sm font-medium">
{pageBlocks.find((b) => b.id === activeId)?.blockName}
</p>
</div>
) : null}
</DragOverlay>
</DndContext>
</div>
</div>
</ScrollArea>
</div>
</div>
)
}
function SortableBlock({
id,
blockName,
onRemove,
}: {
id: string
blockName: string
onRemove: () => void
}) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id })
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
}
return (
<div ref={setNodeRef} style={style} className="group relative">
<div className="absolute -left-12 top-2 z-10 flex flex-col items-center gap-2">
<button
{...attributes}
{...listeners}
className="cursor-grab rounded bg-card p-1 shadow-sm hover:shadow active:cursor-grabbing"
>
<GripVertical className="h-4 w-4 text-muted-foreground" />
</button>
<Button
variant="ghost"
size="icon"
onClick={onRemove}
className="h-6 w-6 bg-background/80 opacity-0 backdrop-blur transition-opacity group-hover:opacity-100"
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
{/* Block Preview - No gaps between blocks */}
<div className="relative overflow-hidden border-b last:border-b-0">
<DynamicBlock blockName={blockName} scale={1} />
</div>
</div>
)
}
+1 -1
View File
@@ -1,12 +1,12 @@
import Link from "next/link"
import { notFound } from "next/navigation"
import { mdxComponents } from "@/mdx-components"
import { findNeighbour } from "@hanzo/docs-core/page-tree"
import {
IconArrowLeft,
IconArrowRight,
IconArrowUpRight,
} from "@tabler/icons-react"
import { findNeighbour } from "@hanzo/docs-core/page-tree"
import { source } from "@/lib/source"
import { absoluteUrl } from "@/lib/utils"
+18
View File
@@ -0,0 +1,18 @@
/**
* POST /api/chat — AI chat about UI components.
*
* Uses Hanzo AI (zen-coder-flash) to answer questions about
* Hanzo UI components, props, patterns, and composition.
* Compatible with Vercel AI SDK useChat hook.
*/
import { NextResponse } from 'next/server'
export const dynamic = "force-static"
export async function GET() {
return NextResponse.json(
{ error: 'AI chat requires a server runtime. Use api.hanzo.ai/v1 directly.' },
{ status: 501 },
)
}
@@ -0,0 +1,38 @@
/**
* GET /api/registry/components/:name — Get component with full source.
*
* Returns the component JSON including embedded source code.
*/
import { NextRequest, NextResponse } from "next/server"
import { getComponent, getComponentMap } from "../../lib"
export const dynamic = "force-static"
export const dynamicParams = false
export function generateStaticParams() {
const map = getComponentMap()
return Array.from(map.keys()).map((name) => ({ name }))
}
export async function GET(
_req: NextRequest,
{ params }: { params: Promise<{ name: string }> }
) {
const { name } = await params
const component = getComponent(name)
if (!component) {
return NextResponse.json(
{ error: "Component not found", name },
{ status: 404 }
)
}
return NextResponse.json(component, {
headers: {
"Cache-Control": "public, s-maxage=900, stale-while-revalidate=1800",
"Access-Control-Allow-Origin": "*",
},
})
}
+22
View File
@@ -0,0 +1,22 @@
/**
* GET /api/registry/index — Full registry manifest.
*
* Returns ALL components with source in a single payload.
* MCP clients can hydrate their cache with one HTTP call.
*/
import { NextResponse } from "next/server"
import { getFullManifest } from "../lib"
export const dynamic = "force-static"
export async function GET() {
const manifest = getFullManifest()
return NextResponse.json(manifest, {
headers: {
"Cache-Control": "public, s-maxage=900, stale-while-revalidate=1800",
"Access-Control-Allow-Origin": "*",
},
})
}
+117
View File
@@ -0,0 +1,117 @@
/**
* Shared registry data loader for API routes.
*
* Reads from the built registry JSON files in public/registry/.
* These are generated at build time by scripts/build-registry.mts.
*/
import { readFileSync, readdirSync, existsSync } from "fs"
import path from "path"
const REGISTRY_DIR = path.join(process.cwd(), "public/registry")
const STYLES_DIR = path.join(REGISTRY_DIR, "styles/default")
export interface RegistryItem {
name: string
type: string
dependencies?: string[]
devDependencies?: string[]
registryDependencies?: string[]
files: Array<{ name: string; content: string } | string>
description?: string
category?: string
}
// In-memory cache (populated on first access, lives for the server lifetime)
let _index: RegistryItem[] | null = null
let _components: Map<string, RegistryItem> | null = null
function loadIndex(): RegistryItem[] {
if (_index) return _index
const indexPath = path.join(REGISTRY_DIR, "index.json")
if (!existsSync(indexPath)) return []
_index = JSON.parse(readFileSync(indexPath, "utf-8")) as RegistryItem[]
return _index
}
function loadComponents(): Map<string, RegistryItem> {
if (_components) return _components
_components = new Map()
if (!existsSync(STYLES_DIR)) return _components
const files = readdirSync(STYLES_DIR).filter((f) => f.endsWith(".json"))
for (const file of files) {
try {
const data = JSON.parse(
readFileSync(path.join(STYLES_DIR, file), "utf-8")
) as RegistryItem
_components.set(data.name, data)
} catch {
// skip malformed files
}
}
return _components
}
/** Get the full component index (names, types, deps — no source). */
export function getIndex(): RegistryItem[] {
return loadIndex()
}
/** Get all components with full source code. */
export function getComponentMap(): Map<string, RegistryItem> {
return loadComponents()
}
/** Get a single component by name (with source). */
export function getComponent(name: string): RegistryItem | undefined {
const map = loadComponents()
// Try exact match first
let item = map.get(name)
if (item) return item
// Try with -demo suffix stripped
item = map.get(`${name}-demo`)
return item
}
/** Search components by name/type. */
export function searchComponents(query: string): RegistryItem[] {
const q = query.toLowerCase()
const index = loadIndex()
return index.filter(
(item) =>
item.name.toLowerCase().includes(q) ||
item.type?.toLowerCase().includes(q) ||
item.description?.toLowerCase().includes(q) ||
item.category?.toLowerCase().includes(q)
)
}
/** List components filtered by type. */
export function listByType(type?: string): RegistryItem[] {
const index = loadIndex()
if (!type) return index
return index.filter((item) => item.type === type || item.type?.includes(type))
}
/** Get full registry manifest (all components with source — single payload). */
export function getFullManifest() {
const map = loadComponents()
const components: Record<string, any> = {}
for (const [name, item] of map) {
components[name] = item
}
return {
generated_at: Date.now(),
total: map.size,
components,
}
}
/** Invalidate the in-memory cache (call after registry:build). */
export function invalidateCache() {
_index = null
_components = null
}
+30
View File
@@ -0,0 +1,30 @@
/**
* GET /api/registry/search?q=button — Search components.
*/
import { NextResponse } from "next/server"
import { getIndex } from "../lib"
export const dynamic = "force-static"
export async function GET() {
// Static export: return full index (client-side filtering)
const items = getIndex()
return NextResponse.json(
{
total: items.length,
results: items.map((item) => ({
name: item.name,
type: item.type,
dependencies: item.dependencies,
})),
},
{
headers: {
"Cache-Control": "public, s-maxage=300, stale-while-revalidate=600",
"Access-Control-Allow-Origin": "*",
},
}
)
}
+17
View File
@@ -0,0 +1,17 @@
/**
* POST /api/search — Search UI components via Hanzo Cloud.
*
* Proxies to Hanzo Cloud search-docs API with the publishable key.
* Client-side code hits this route instead of Cloud directly.
*/
import { NextResponse } from 'next/server'
export const dynamic = "force-static"
export async function GET() {
return NextResponse.json(
{ error: 'Search requires a server runtime. Use client-side search.' },
{ status: 501 },
)
}
+5 -5
View File
@@ -2,11 +2,6 @@ import "@/styles/globals.css"
import { Metadata, Viewport } from "next"
const META_THEME_COLORS = {
light: "white",
dark: "black",
}
import { siteConfig } from "@/config/site"
import { fontMono, fontSans } from "@/lib/fonts"
import { cn } from "@/lib/utils"
@@ -23,6 +18,11 @@ import {
Toaster as NewYorkToaster,
} from "@/registry/default/ui/toaster"
const META_THEME_COLORS = {
light: "white",
dark: "black",
}
export const metadata: Metadata = {
title: {
default: siteConfig.name,
+5 -1
View File
@@ -222,7 +222,11 @@ function BlockViewerToolbar({ styleName }: { styleName: Style["name"] }) {
}}
title="Copy install command"
>
{isCopied ? <Check className="!h-3.5 !w-3.5" /> : <Clipboard className="!h-3.5 !w-3.5" />}
{isCopied ? (
<Check className="!h-3.5 !w-3.5" />
) : (
<Clipboard className="!h-3.5 !w-3.5" />
)}
<span className="sr-only">Copy install command</span>
</Button>
<Separator orientation="vertical" className="mx-1 !h-4" />
+200
View File
@@ -97,6 +97,82 @@ export const docsConfig: DocsConfig = {
href: "/docs/changelog",
items: [],
},
{
title: "MCP Server",
href: "/docs/mcp",
items: [],
label: "New",
},
{
title: "About",
href: "/docs/about",
items: [],
},
],
},
{
title: "Installation",
items: [
{
title: "Next.js",
href: "/docs/installation/next",
items: [],
},
{
title: "Vite",
href: "/docs/installation/vite",
items: [],
},
{
title: "Remix",
href: "/docs/installation/remix",
items: [],
},
{
title: "Astro",
href: "/docs/installation/astro",
items: [],
},
{
title: "Gatsby",
href: "/docs/installation/gatsby",
items: [],
},
{
title: "Laravel",
href: "/docs/installation/laravel",
items: [],
},
{
title: "Manual",
href: "/docs/installation/manual",
items: [],
},
],
},
{
title: "Dark Mode",
items: [
{
title: "Next.js",
href: "/docs/dark-mode/next",
items: [],
},
{
title: "Vite",
href: "/docs/dark-mode/vite",
items: [],
},
{
title: "Remix",
href: "/docs/dark-mode/remix",
items: [],
},
{
title: "Astro",
href: "/docs/dark-mode/astro",
items: [],
},
],
},
{
@@ -143,6 +219,12 @@ export const docsConfig: DocsConfig = {
href: "/docs/components/button",
items: [],
},
{
title: "Button Group",
href: "/docs/components/button-group",
items: [],
label: "New",
},
{
title: "Calendar",
href: "/docs/components/calendar",
@@ -188,6 +270,30 @@ export const docsConfig: DocsConfig = {
href: "/docs/components/data-table",
items: [],
},
{
title: "Desktop",
href: "/docs/components/desktop",
items: [],
label: "New",
},
{
title: "Desktop Hooks",
href: "/docs/components/desktop-hooks",
items: [],
label: "New",
},
{
title: "Desktop Spotlight",
href: "/docs/components/desktop-spotlight",
items: [],
label: "New",
},
{
title: "Desktop Window",
href: "/docs/components/desktop-window",
items: [],
label: "New",
},
{
title: "Date Picker",
href: "/docs/components/date-picker",
@@ -198,6 +304,12 @@ export const docsConfig: DocsConfig = {
href: "/docs/components/dialog",
items: [],
},
{
title: "Direction",
href: "/docs/components/direction",
items: [],
label: "New",
},
{
title: "Drawer",
href: "/docs/components/drawer",
@@ -208,6 +320,18 @@ export const docsConfig: DocsConfig = {
href: "/docs/components/dropdown-menu",
items: [],
},
{
title: "Empty State",
href: "/docs/components/empty",
items: [],
label: "New",
},
{
title: "Field",
href: "/docs/components/field",
items: [],
label: "New",
},
{
title: "Form",
href: "/docs/components/form",
@@ -223,12 +347,24 @@ export const docsConfig: DocsConfig = {
href: "/docs/components/input",
items: [],
},
{
title: "Input Group",
href: "/docs/components/input-group",
items: [],
label: "New",
},
{
title: "Input OTP",
href: "/docs/components/input-otp",
items: [],
label: "New",
},
{
title: "Item",
href: "/docs/components/item",
items: [],
label: "New",
},
{
title: "Label",
href: "/docs/components/label",
@@ -613,6 +749,24 @@ export const docsConfig: DocsConfig = {
items: [],
label: "New",
},
{
title: "Particles Background",
href: "/docs/components/particles-background",
items: [],
label: "New",
},
{
title: "Grid Pattern",
href: "/docs/components/grid-pattern",
items: [],
label: "New",
},
{
title: "Timeline",
href: "/docs/components/timeline",
items: [],
label: "New",
},
{
title: "Pin List",
href: "/docs/components/pin-list",
@@ -1028,6 +1182,18 @@ export const docsConfig: DocsConfig = {
items: [],
label: "New",
},
{
title: "AI Code",
href: "/docs/ai/code",
items: [],
label: "New",
},
{
title: "AI Voice",
href: "/docs/ai/voice",
items: [],
label: "New",
},
],
},
{
@@ -1174,6 +1340,40 @@ export const docsConfig: DocsConfig = {
},
],
},
{
title: "Guides",
items: [
{
title: "Page Builder",
href: "/docs/guides/page-builder",
items: [],
label: "New",
},
{
title: "Visual Workflows",
href: "/docs/guides/visual-workflows",
items: [],
label: "New",
},
],
},
{
title: "Hanzo React",
items: [
{
title: "Getting Started",
href: "/docs/hanzo-react/getting-started",
items: [],
label: "New",
},
{
title: "Hooks",
href: "/docs/hanzo-react/hooks",
items: [],
label: "New",
},
],
},
{
title: "Packages",
items: [
+27
View File
@@ -0,0 +1,27 @@
---
title: Contact Forms
description: Contact form sections with validation, multiple fields, and submission handling.
---
## Overview
Contact form blocks provide complete form layouts for collecting user inquiries. They include input validation, loading states, and success confirmation.
## Features
- **Form Validation**: Built-in HTML5 and custom validation
- **Multiple Fields**: Name, email, subject, and message inputs
- **Loading States**: Visual feedback during form submission
- **Success Message**: Confirmation after successful submission
- **Responsive Design**: Adapts to all screen sizes
- **Accessible**: Semantic form elements with proper labels
## Usage
```tsx
import ContactBlock from "@/registry/default/block/contact"
export default function Page() {
return <ContactBlock />
}
```
+26
View File
@@ -0,0 +1,26 @@
---
title: CTA Sections
description: Call-to-action sections for driving user conversions and signups.
---
## Overview
CTA (Call-to-Action) blocks are designed to drive user engagement and conversions. They feature prominent buttons, compelling copy, and focused layouts that guide users toward a specific action.
## Features
- **Conversion-Focused**: Designed to maximize click-through rates
- **Responsive Design**: Adapts to all screen sizes
- **Multiple Layouts**: Centered, split, and banner variations
- **Button Variants**: Primary, secondary, and ghost CTA styles
- **Background Options**: Solid colors, gradients, and image backgrounds
## Usage
```tsx
import CTABlock from "@/registry/default/block/cta"
export default function Page() {
return <CTABlock />
}
```
+27
View File
@@ -0,0 +1,27 @@
---
title: FAQ Sections
description: Frequently asked questions sections with expandable accordions and search.
---
## Overview
FAQ blocks present commonly asked questions in organized, expandable layouts. They use accordion patterns for progressive disclosure and optional search filtering.
## Features
- **Accordion Layout**: Expandable question/answer pairs
- **Search Filtering**: Optional search to find specific questions
- **Categories**: Group questions by topic
- **Responsive**: Full-width layout that works on all devices
- **Accessible**: Keyboard navigation and screen reader support
- **Animated**: Smooth expand/collapse transitions
## Usage
```tsx
import FAQBlock from "@/registry/default/block/faq"
export default function Page() {
return <FAQBlock />
}
```
+26
View File
@@ -0,0 +1,26 @@
---
title: Feature Sections
description: Feature grid and list sections for highlighting product capabilities.
---
## Overview
Feature blocks display product capabilities in organized grid or list layouts. They combine icons, headings, and descriptions to communicate value propositions clearly.
## Features
- **Grid Layouts**: 2, 3, or 4 column responsive grids
- **Icon Support**: Lucide icons or custom SVGs
- **Responsive**: Stacks to single column on mobile
- **Customizable**: Flexible content and styling options
- **Accessible**: Semantic HTML with proper heading hierarchy
## Usage
```tsx
import FeaturesBlock from "@/registry/default/block/features"
export default function Page() {
return <FeaturesBlock />
}
```
+36
View File
@@ -0,0 +1,36 @@
---
title: Hero Sections
description: Full-width hero sections for landing pages with headlines, CTAs, and imagery.
---
## Overview
Hero blocks are viewport-sized sections designed for the top of landing pages. They feature bold typography, call-to-action buttons, and visual elements to capture user attention.
## Variations
### App UI Showcase
<ComponentPreview name="showcase-app-ui-01" />
A hero section showcasing your app's UI with a centered layout and product screenshot.
### E-commerce Showcase
<ComponentPreview name="showcase-ecommerce-01" />
A hero section designed for e-commerce with product imagery and shopping CTAs.
### Marketing Showcase
<ComponentPreview name="showcase-marketing-01" />
A bold marketing hero section with large typography and conversion-focused design.
## Features
- **Responsive Design**: Adapts to all screen sizes
- **CTA Buttons**: Primary and secondary call-to-action buttons
- **Visual Elements**: Support for images, gradients, and animations
- **Typography**: Large, bold headlines with supporting text
- **Customizable**: Easy to modify colors, text, and layout
+27
View File
@@ -0,0 +1,27 @@
---
title: Stats Sections
description: Statistics and metrics sections with counters, charts, and key figures.
---
## Overview
Stats blocks display key metrics and achievements in visually impactful layouts. They feature animated counters, comparison figures, and trend indicators.
## Features
- **Animated Counters**: Number animations on scroll into view
- **Multiple Layouts**: Grid, inline, and featured stat variations
- **Trend Indicators**: Up/down arrows with percentage changes
- **Responsive**: Adapts from multi-column to single column on mobile
- **Icons**: Support for metric-specific icons
- **Customizable**: Easy to update values and labels
## Usage
```tsx
import StatsBlock from "@/registry/default/block/stats"
export default function Page() {
return <StatsBlock />
}
```
+27
View File
@@ -0,0 +1,27 @@
---
title: Team Sections
description: Team member grid and list sections with photos, roles, and social links.
---
## Overview
Team blocks showcase team members with profile photos, names, roles, and social links. They support multiple layout variations for different page contexts.
## Features
- **Grid Layout**: Responsive grid with team member cards
- **Profile Photos**: Avatar images with fallback initials
- **Role Display**: Job titles and department labels
- **Social Links**: GitHub, Twitter, LinkedIn integration
- **Responsive**: Adapts columns based on screen size
- **Hover Effects**: Interactive card hover states
## Usage
```tsx
import TeamBlock from "@/registry/default/block/team"
export default function Page() {
return <TeamBlock />
}
```
+27
View File
@@ -0,0 +1,27 @@
---
title: Testimonials
description: Testimonial sections with user quotes, ratings, and social proof.
---
## Overview
Testimonial blocks display customer reviews and social proof in visually appealing layouts. They support avatars, star ratings, company logos, and quote formatting.
## Features
- **Multiple Layouts**: Grid, carousel, and featured testimonial styles
- **Avatar Support**: User photos with name and title
- **Star Ratings**: Visual rating display
- **Company Logos**: Brand association for B2B testimonials
- **Responsive**: Adapts from multi-column to single column on mobile
- **Animated**: Optional entrance animations and carousel transitions
## Usage
```tsx
import TestimonialsBlock from "@/registry/default/block/testimonials"
export default function Page() {
return <TestimonialsBlock />
}
```
+64
View File
@@ -0,0 +1,64 @@
---
title: Area Chart
description: Area charts for visualizing trends and cumulative values over time.
---
<ComponentPreview name="chart-demo" description="Area chart example" />
## Overview
Area charts are used to show trends over time or across categories. They combine line charts with filled areas beneath the line to emphasize volume.
## Usage
```tsx
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@hanzo/ui"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
const data = [
{ month: "Jan", desktop: 186, mobile: 80 },
{ month: "Feb", desktop: 305, mobile: 200 },
{ month: "Mar", desktop: 237, mobile: 120 },
]
const config = {
desktop: { label: "Desktop", color: "hsl(var(--chart-1))" },
mobile: { label: "Mobile", color: "hsl(var(--chart-2))" },
}
export function AreaChartDemo() {
return (
<ChartContainer config={config}>
<AreaChart data={data}>
<CartesianGrid vertical={false} />
<XAxis dataKey="month" />
<ChartTooltip content={<ChartTooltipContent />} />
<Area
dataKey="desktop"
fill="var(--color-desktop)"
stroke="var(--color-desktop)"
/>
<Area
dataKey="mobile"
fill="var(--color-mobile)"
stroke="var(--color-mobile)"
/>
</AreaChart>
</ChartContainer>
)
}
```
## Variants
### Stacked Area
Stack multiple areas to show cumulative totals.
### Gradient Fill
Apply gradient fills for visual depth.
### Step Area
Use `type="step"` for discrete step transitions.
+58
View File
@@ -0,0 +1,58 @@
---
title: Bar Chart
description: Bar charts for comparing categorical data and distributions.
---
<ComponentPreview name="chart-demo" description="Bar chart example" />
## Overview
Bar charts display data with rectangular bars proportional to their values. They are ideal for comparing quantities across categories.
## Usage
```tsx
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@hanzo/ui"
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
const data = [
{ month: "Jan", desktop: 186 },
{ month: "Feb", desktop: 305 },
{ month: "Mar", desktop: 237 },
]
const config = {
desktop: { label: "Desktop", color: "hsl(var(--chart-1))" },
}
export function BarChartDemo() {
return (
<ChartContainer config={config}>
<BarChart data={data}>
<CartesianGrid vertical={false} />
<XAxis dataKey="month" />
<ChartTooltip content={<ChartTooltipContent />} />
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
</BarChart>
</ChartContainer>
)
}
```
## Variants
### Horizontal Bar
Use `layout="vertical"` for horizontal orientation.
### Stacked Bar
Stack multiple bars to show composition within categories.
### Mixed Bar
Combine different bar styles and data series.
### With Labels
Add data labels directly on bars using custom label components.
+108
View File
@@ -0,0 +1,108 @@
---
title: Charts
description: Beautiful, composable chart components built on Recharts.
---
## Overview
@hanzo/ui provides a set of chart components built on top of [Recharts](https://recharts.org/). The chart components are designed to be composable and work with Tailwind CSS theming.
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx @hanzo/ui@latest add chart
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install recharts:</Step>
```bash
pnpm add recharts
```
<Step>Copy the chart components into your project.</Step>
<ComponentSource name="chart" />
</Steps>
</TabsContent>
</Tabs>
## Components
The chart package includes the following components:
- **ChartContainer** - Responsive container that handles theming and sizing
- **ChartTooltip** - Styled tooltip component
- **ChartTooltipContent** - Content renderer for tooltips
- **ChartLegend** - Chart legend component
- **ChartLegendContent** - Content renderer for legends
## Chart Types
- [Area Chart](/docs/charts/area) - For showing trends over time
- [Bar Chart](/docs/charts/bar) - For comparing categorical data
- [Line Chart](/docs/charts/line) - For showing continuous data
- [Pie Chart](/docs/charts/pie) - For showing proportional data
- [Radar Chart](/docs/charts/radar) - For multivariate data comparison
- [Radial Chart](/docs/charts/radial) - For circular data visualization
- [Tooltip](/docs/charts/tooltip) - Shared tooltip component
## Usage
```tsx
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@hanzo/ui"
import { Bar, BarChart, XAxis } from "recharts"
const data = [
{ month: "Jan", value: 186 },
{ month: "Feb", value: 305 },
{ month: "Mar", value: 237 },
]
const config = {
value: { label: "Value", color: "hsl(var(--chart-1))" },
}
export function MyChart() {
return (
<ChartContainer config={config}>
<BarChart data={data}>
<XAxis dataKey="month" />
<ChartTooltip content={<ChartTooltipContent />} />
<Bar dataKey="value" fill="var(--color-value)" />
</BarChart>
</ChartContainer>
)
}
```
## Theming
Charts use CSS variables for theming. Define chart colors in your CSS:
```css
:root {
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
```
+60
View File
@@ -0,0 +1,60 @@
---
title: Line Chart
description: Line charts for showing continuous data trends and comparisons.
---
<ComponentPreview name="chart-demo" description="Line chart example" />
## Overview
Line charts connect data points with straight or curved lines, ideal for showing trends and changes over continuous intervals.
## Usage
```tsx
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@hanzo/ui"
import { CartesianGrid, Line, LineChart, XAxis } from "recharts"
const data = [
{ month: "Jan", desktop: 186, mobile: 80 },
{ month: "Feb", desktop: 305, mobile: 200 },
{ month: "Mar", desktop: 237, mobile: 120 },
]
const config = {
desktop: { label: "Desktop", color: "hsl(var(--chart-1))" },
mobile: { label: "Mobile", color: "hsl(var(--chart-2))" },
}
export function LineChartDemo() {
return (
<ChartContainer config={config}>
<LineChart data={data}>
<CartesianGrid vertical={false} />
<XAxis dataKey="month" />
<ChartTooltip content={<ChartTooltipContent />} />
<Line dataKey="desktop" stroke="var(--color-desktop)" strokeWidth={2} />
<Line dataKey="mobile" stroke="var(--color-mobile)" strokeWidth={2} />
</LineChart>
</ChartContainer>
)
}
```
## Variants
### With Dots
Show data point markers on the line.
### Custom Dots
Use custom shapes for data point markers.
### Linear vs Curved
Toggle between `type="linear"` and `type="monotone"` interpolation.
### Interactive
Add hover interactions and clickable data points.
+52
View File
@@ -0,0 +1,52 @@
---
title: Pie Chart
description: Pie and donut charts for showing proportional data and distributions.
---
## Overview
Pie charts display data as proportional slices of a circle, ideal for showing parts of a whole.
## Usage
```tsx
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@hanzo/ui"
import { Pie, PieChart } from "recharts"
const data = [
{ browser: "Chrome", visitors: 275, fill: "var(--color-chrome)" },
{ browser: "Safari", visitors: 200, fill: "var(--color-safari)" },
{ browser: "Firefox", visitors: 187, fill: "var(--color-firefox)" },
]
const config = {
chrome: { label: "Chrome", color: "hsl(var(--chart-1))" },
safari: { label: "Safari", color: "hsl(var(--chart-2))" },
firefox: { label: "Firefox", color: "hsl(var(--chart-3))" },
}
export function PieChartDemo() {
return (
<ChartContainer config={config}>
<PieChart>
<ChartTooltip content={<ChartTooltipContent />} />
<Pie data={data} dataKey="visitors" nameKey="browser" />
</PieChart>
</ChartContainer>
)
}
```
## Variants
### Donut Chart
Use `innerRadius` and `outerRadius` to create donut charts.
### With Labels
Add labels inside or outside slices.
### Interactive
Highlight slices on hover with `activeIndex`.
+53
View File
@@ -0,0 +1,53 @@
---
title: Radar Chart
description: Radar charts for multivariate data comparison and analysis.
---
## Overview
Radar charts display multivariate data on axes starting from the same point, useful for comparing multiple quantitative variables.
## Usage
```tsx
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@hanzo/ui"
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from "recharts"
const data = [
{ subject: "Math", A: 120, B: 110 },
{ subject: "Chinese", A: 98, B: 130 },
{ subject: "English", A: 86, B: 130 },
{ subject: "Geography", A: 99, B: 100 },
{ subject: "Physics", A: 85, B: 90 },
{ subject: "History", A: 65, B: 85 },
]
const config = {
A: { label: "Student A", color: "hsl(var(--chart-1))" },
B: { label: "Student B", color: "hsl(var(--chart-2))" },
}
export function RadarChartDemo() {
return (
<ChartContainer config={config}>
<RadarChart data={data}>
<PolarGrid />
<PolarAngleAxis dataKey="subject" />
<ChartTooltip content={<ChartTooltipContent />} />
<Radar dataKey="A" fill="var(--color-A)" fillOpacity={0.6} />
<Radar dataKey="B" fill="var(--color-B)" fillOpacity={0.6} />
</RadarChart>
</ChartContainer>
)
}
```
## Variants
### Filled Radar
Use `fillOpacity` to create filled radar areas.
### Multiple Series
Compare multiple datasets on the same radar.
+52
View File
@@ -0,0 +1,52 @@
---
title: Radial Chart
description: Radial bar charts for circular data visualization and progress indicators.
---
## Overview
Radial charts display data in a circular layout, useful for progress indicators, gauges, and circular comparisons.
## Usage
```tsx
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@hanzo/ui"
import { RadialBar, RadialBarChart } from "recharts"
const data = [
{ name: "Chrome", visitors: 275, fill: "var(--color-chrome)" },
{ name: "Safari", visitors: 200, fill: "var(--color-safari)" },
{ name: "Firefox", visitors: 187, fill: "var(--color-firefox)" },
]
const config = {
chrome: { label: "Chrome", color: "hsl(var(--chart-1))" },
safari: { label: "Safari", color: "hsl(var(--chart-2))" },
firefox: { label: "Firefox", color: "hsl(var(--chart-3))" },
}
export function RadialChartDemo() {
return (
<ChartContainer config={config}>
<RadialBarChart data={data} innerRadius={30} outerRadius={110}>
<ChartTooltip content={<ChartTooltipContent />} />
<RadialBar dataKey="visitors" />
</RadialBarChart>
</ChartContainer>
)
}
```
## Variants
### Stacked Radial
Stack multiple radial bars concentrically.
### With Labels
Add labels to radial segments for clarity.
### Progress Gauge
Use a single radial bar as a progress or gauge indicator.
+61
View File
@@ -0,0 +1,61 @@
---
title: Chart Tooltip
description: Customizable tooltip component for all chart types.
---
## Overview
The chart tooltip component provides a consistent, themed tooltip across all chart types. It integrates with the chart config system for automatic label and color resolution.
## Usage
```tsx
import { ChartTooltip, ChartTooltipContent } from "@hanzo/ui"
// Inside any chart component:
;<ChartTooltip content={<ChartTooltipContent />} />
```
## Props
### ChartTooltipContent
| Prop | Type | Default | Description |
| ------------- | --------------------------- | ------- | ------------------------- |
| hideLabel | boolean | false | Hide the label in tooltip |
| hideIndicator | boolean | false | Hide the color indicator |
| indicator | "line" \| "dot" \| "dashed" | "dot" | Indicator style |
| nameKey | string | - | Key for item names |
| labelKey | string | - | Key for labels |
## Customization
### Custom Formatter
```tsx
<ChartTooltip
content={
<ChartTooltipContent
formatter={(value, name) => (
<span>
{name}: ${value.toLocaleString()}
</span>
)}
/>
}
/>
```
### Custom Label
```tsx
<ChartTooltip
content={<ChartTooltipContent labelFormatter={(label) => `Date: ${label}`} />}
/>
```
## Indicator Styles
- **dot** - Small colored circle (default)
- **line** - Colored line indicator
- **dashed** - Dashed line indicator
+90 -79
View File
@@ -63,18 +63,28 @@ function Desktop() {
return (
<div>
<button onClick={() => windows.openWindow('Settings')}>Open Settings</button>
<button onClick={() => windows.toggleWindow('Terminal')}>Toggle Terminal</button>
<button onClick={() => windows.openWindow("Settings")}>
Open Settings
</button>
<button onClick={() => windows.toggleWindow("Terminal")}>
Toggle Terminal
</button>
<button onClick={() => windows.closeAllWindows()}>Close All</button>
{windows.isOpen('Settings') && (
<Window title="Settings" onClose={() => windows.closeWindow('Settings')}>
{windows.isOpen("Settings") && (
<Window
title="Settings"
onClose={() => windows.closeWindow("Settings")}
>
...
</Window>
)}
{windows.isOpen('Terminal') && (
<Window title="Terminal" onClose={() => windows.closeWindow('Terminal')}>
{windows.isOpen("Terminal") && (
<Window
title="Terminal"
onClose={() => windows.closeWindow("Terminal")}
>
...
</Window>
)}
@@ -85,17 +95,17 @@ function Desktop() {
#### API
| Method | Type | Description |
|--------|------|-------------|
| `isOpen` | `(id: string) => boolean` | Check if a window is open |
| `openWindow` | `(id: string) => void` | Open a window |
| `closeWindow` | `(id: string) => void` | Close a window |
| `toggleWindow` | `(id: string) => void` | Toggle a window's open state |
| `closeAllWindows` | `() => void` | Close all windows |
| `focusWindow` | `(id: string) => void` | Focus/activate a window |
| `activeWindow` | `string \| null` | Currently active window |
| `openWindows` | `string[]` | List of all open window IDs |
| `windows` | `Record<string, boolean>` | Window state map |
| Method | Type | Description |
| ----------------- | ------------------------- | ---------------------------- |
| `isOpen` | `(id: string) => boolean` | Check if a window is open |
| `openWindow` | `(id: string) => void` | Open a window |
| `closeWindow` | `(id: string) => void` | Close a window |
| `toggleWindow` | `(id: string) => void` | Toggle a window's open state |
| `closeAllWindows` | `() => void` | Close all windows |
| `focusWindow` | `(id: string) => void` | Focus/activate a window |
| `activeWindow` | `string \| null` | Currently active window |
| `openWindows` | `string[]` | List of all open window IDs |
| `windows` | `Record<string, boolean>` | Window state map |
---
@@ -150,23 +160,23 @@ function SettingsPanel() {
#### API
| Property | Type | Description |
|----------|------|-------------|
| `theme` | `'light' \| 'dark' \| 'system'` | Current theme |
| `setTheme` | `(theme) => void` | Set theme |
| `colorScheme` | `string` | Color scheme name |
| `setColorScheme` | `(scheme) => void` | Set color scheme |
| `showDock` | `boolean` | Whether dock is visible |
| `setShowDock` | `(show) => void` | Toggle dock visibility |
| `dockPosition` | `'bottom' \| 'left' \| 'right'` | Dock position |
| `setDockPosition` | `(position) => void` | Set dock position |
| `dockMagnification` | `boolean` | Dock magnification enabled |
| `setDockMagnification` | `(enabled) => void` | Toggle magnification |
| `fontSize` | `number` | Base font size |
| `setFontSize` | `(size) => void` | Set font size |
| `windowTransparency` | `number` | Window transparency (0-1) |
| `setWindowTransparency` | `(opacity) => void` | Set transparency |
| `resetToDefaults` | `() => void` | Reset all settings |
| Property | Type | Description |
| ----------------------- | ------------------------------- | -------------------------- |
| `theme` | `'light' \| 'dark' \| 'system'` | Current theme |
| `setTheme` | `(theme) => void` | Set theme |
| `colorScheme` | `string` | Color scheme name |
| `setColorScheme` | `(scheme) => void` | Set color scheme |
| `showDock` | `boolean` | Whether dock is visible |
| `setShowDock` | `(show) => void` | Toggle dock visibility |
| `dockPosition` | `'bottom' \| 'left' \| 'right'` | Dock position |
| `setDockPosition` | `(position) => void` | Set dock position |
| `dockMagnification` | `boolean` | Dock magnification enabled |
| `setDockMagnification` | `(enabled) => void` | Toggle magnification |
| `fontSize` | `number` | Base font size |
| `setFontSize` | `(size) => void` | Set font size |
| `windowTransparency` | `number` | Window transparency (0-1) |
| `setWindowTransparency` | `(opacity) => void` | Set transparency |
| `resetToDefaults` | `() => void` | Reset all settings |
---
@@ -182,20 +192,18 @@ function Desktop() {
return (
<div>
<button onClick={() => overlays.open('spotlight')}>
<button onClick={() => overlays.open("spotlight")}>
Open Spotlight (⌘+Space)
</button>
<button onClick={() => overlays.open('about')}>
About This Mac
</button>
<button onClick={() => overlays.open("about")}>About This Mac</button>
{overlays.isOpen('spotlight') && (
<Spotlight onClose={() => overlays.close('spotlight')} />
{overlays.isOpen("spotlight") && (
<Spotlight onClose={() => overlays.close("spotlight")} />
)}
{overlays.isOpen('about') && (
<AboutDialog onClose={() => overlays.close('about')} />
{overlays.isOpen("about") && (
<AboutDialog onClose={() => overlays.close("about")} />
)}
</div>
)
@@ -204,14 +212,14 @@ function Desktop() {
#### API
| Property | Type | Description |
|----------|------|-------------|
| `overlays` | `OverlayState` | Current overlay states |
| `isOpen` | `(id: string) => boolean` | Check if an overlay is open |
| `open` | `(id: string) => void` | Open an overlay by id |
| `close` | `(id: string) => void` | Close an overlay by id |
| `toggle` | `(id: string) => void` | Toggle an overlay |
| `closeAll` | `() => void` | Close all overlays |
| Property | Type | Description |
| ---------- | ------------------------- | --------------------------- |
| `overlays` | `OverlayState` | Current overlay states |
| `isOpen` | `(id: string) => boolean` | Check if an overlay is open |
| `open` | `(id: string) => void` | Open an overlay by id |
| `close` | `(id: string) => void` | Close an overlay by id |
| `toggle` | `(id: string) => void` | Toggle an overlay |
| `closeAll` | `() => void` | Close all overlays |
Built-in overlay IDs: `spotlight`, `contextMenu`, `modal`, `drawer`
@@ -259,29 +267,29 @@ function Desktop() {
#### Shortcut Definition
| Property | Type | Description |
|----------|------|-------------|
| `key` | `string` | Key to listen for |
| `meta` | `boolean` | Require ⌘ (Mac) / Ctrl (Windows) |
| `alt` | `boolean` | Require Alt/Option |
| `shift` | `boolean` | Require Shift |
| `ctrl` | `boolean` | Require Control |
| `action` | `() => void` | Callback when triggered |
| Property | Type | Description |
| -------- | ------------ | -------------------------------- |
| `key` | `string` | Key to listen for |
| `meta` | `boolean` | Require ⌘ (Mac) / Ctrl (Windows) |
| `alt` | `boolean` | Require Alt/Option |
| `shift` | `boolean` | Require Shift |
| `ctrl` | `boolean` | Require Control |
| `action` | `() => void` | Callback when triggered |
## Complete Example
```tsx
import {
Window,
Spotlight,
useWindowManager,
useDesktopSettings,
useKeyboardShortcuts,
useOverlayManager,
useKeyboardShortcuts
useWindowManager,
Window,
} from "@hanzo/ui/desktop"
import { Dock, DockItem } from "@hanzo/ui/dock"
const apps = ['Finder', 'Terminal', 'Safari', 'Mail', 'Calendar', 'Settings']
const apps = ["Finder", "Terminal", "Safari", "Mail", "Calendar", "Settings"]
export function Desktop() {
const windows = useWindowManager()
@@ -290,9 +298,9 @@ export function Desktop() {
// Register keyboard shortcuts
useKeyboardShortcuts([
{ key: ' ', meta: true, action: overlays.openSpotlight },
{ key: 'q', meta: true, action: () => windows.close(windows.activeWindow) },
{ key: ',', meta: true, action: () => windows.open('Settings') },
{ key: " ", meta: true, action: overlays.openSpotlight },
{ key: "q", meta: true, action: () => windows.close(windows.activeWindow) },
{ key: ",", meta: true, action: () => windows.open("Settings") },
])
return (
@@ -300,33 +308,36 @@ export function Desktop() {
className="h-screen w-screen relative"
style={{
opacity: settings.windowTransparency,
fontSize: settings.fontSize
fontSize: settings.fontSize,
}}
>
{/* Desktop Background */}
<div className="absolute inset-0 bg-gradient-to-br from-blue-500 to-purple-600" />
{/* Windows */}
{apps.map(app => windows.isOpen(app) && (
<Window
key={app}
title={app}
onClose={() => windows.close(app)}
windowType={settings.theme === 'dark' ? 'dark' : 'default'}
>
<div className="p-4">{app} content</div>
</Window>
))}
{apps.map(
(app) =>
windows.isOpen(app) && (
<Window
key={app}
title={app}
onClose={() => windows.close(app)}
windowType={settings.theme === "dark" ? "dark" : "default"}
>
<div className="p-4">{app} content</div>
</Window>
)
)}
{/* Spotlight */}
{overlays.spotlight && (
<Spotlight
isOpen={true}
onClose={overlays.closeSpotlight}
items={apps.map(app => ({
items={apps.map((app) => ({
id: app.toLowerCase(),
label: app,
category: 'Applications'
category: "Applications",
}))}
onSelect={(item) => {
windows.open(item.label)
@@ -341,7 +352,7 @@ export function Desktop() {
position={settings.dockPosition}
magnification={settings.dockMagnification ? 60 : 0}
>
{apps.map(app => (
{apps.map((app) => (
<DockItem
key={app}
tooltip={app}
+138 -55
View File
@@ -57,9 +57,19 @@ npm install framer-motion lucide-react
import { Spotlight, SpotlightItem } from "@hanzo/ui/desktop"
const items: SpotlightItem[] = [
{ id: 'settings', title: 'Settings', category: 'Applications', icon: <Settings /> },
{ id: 'documents', title: 'Documents', category: 'Folders', icon: <Folder /> },
{ id: 'music', title: 'Music', category: 'Applications', icon: <Music /> },
{
id: "settings",
title: "Settings",
category: "Applications",
icon: <Settings />,
},
{
id: "documents",
title: "Documents",
category: "Folders",
icon: <Folder />,
},
{ id: "music", title: "Music", category: "Applications", icon: <Music /> },
]
export function SpotlightDemo() {
@@ -74,7 +84,7 @@ export function SpotlightDemo() {
onClose={() => setIsOpen(false)}
items={items}
onSelect={(item) => {
console.log('Selected:', item)
console.log("Selected:", item)
setIsOpen(false)
}}
/>
@@ -87,25 +97,25 @@ export function SpotlightDemo() {
### Spotlight
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `isOpen` | `boolean` | - | Controls visibility of the spotlight dialog |
| `onClose` | `() => void` | - | Callback when spotlight is closed |
| `items` | `SpotlightItem[]` | - | Array of searchable items |
| `onSelect` | `(item: SpotlightItem) => void` | - | Callback when an item is selected |
| `placeholder` | `string` | `'Search...'` | Search input placeholder text |
| Prop | Type | Default | Description |
| ------------- | ------------------------------- | ------------- | ------------------------------------------- |
| `isOpen` | `boolean` | - | Controls visibility of the spotlight dialog |
| `onClose` | `() => void` | - | Callback when spotlight is closed |
| `items` | `SpotlightItem[]` | - | Array of searchable items |
| `onSelect` | `(item: SpotlightItem) => void` | - | Callback when an item is selected |
| `placeholder` | `string` | `'Search...'` | Search input placeholder text |
### SpotlightItem
| Property | Type | Description |
|----------|------|-------------|
| `id` | `string` | Unique identifier for the item |
| `title` | `string` | Display title |
| `subtitle` | `string` | Optional subtitle/description text |
| `icon` | `ReactNode` | Optional icon to display |
| `category` | `string` | Category for grouping |
| `keywords` | `string[]` | Optional search keywords |
| `action` | `() => void` | Optional action callback |
| Property | Type | Description |
| ---------- | ------------ | ---------------------------------- |
| `id` | `string` | Unique identifier for the item |
| `title` | `string` | Display title |
| `subtitle` | `string` | Optional subtitle/description text |
| `icon` | `ReactNode` | Optional icon to display |
| `category` | `string` | Category for grouping |
| `keywords` | `string[]` | Optional search keywords |
| `action` | `() => void` | Optional action callback |
## Features
@@ -120,26 +130,70 @@ export function SpotlightDemo() {
### Application Launcher
```tsx
import { Spotlight, SpotlightItem, useKeyboardShortcuts } from "@hanzo/ui/desktop"
import {
Terminal,
Chrome,
Mail,
Spotlight,
SpotlightItem,
useKeyboardShortcuts,
} from "@hanzo/ui/desktop"
import {
Calendar,
Chrome,
Folder,
Mail,
Music,
Settings,
Folder
Terminal,
} from "lucide-react"
const apps: SpotlightItem[] = [
{ id: 'terminal', label: 'Terminal', category: 'Applications', icon: <Terminal className="h-5 w-5" /> },
{ id: 'safari', label: 'Safari', category: 'Applications', icon: <Chrome className="h-5 w-5" /> },
{ id: 'mail', label: 'Mail', category: 'Applications', icon: <Mail className="h-5 w-5" /> },
{ id: 'calendar', label: 'Calendar', category: 'Applications', icon: <Calendar className="h-5 w-5" /> },
{ id: 'music', label: 'Music', category: 'Applications', icon: <Music className="h-5 w-5" /> },
{ id: 'settings', label: 'System Preferences', category: 'Applications', icon: <Settings className="h-5 w-5" /> },
{ id: 'documents', label: 'Documents', category: 'Folders', icon: <Folder className="h-5 w-5" /> },
{ id: 'downloads', label: 'Downloads', category: 'Folders', icon: <Folder className="h-5 w-5" /> },
{
id: "terminal",
label: "Terminal",
category: "Applications",
icon: <Terminal className="h-5 w-5" />,
},
{
id: "safari",
label: "Safari",
category: "Applications",
icon: <Chrome className="h-5 w-5" />,
},
{
id: "mail",
label: "Mail",
category: "Applications",
icon: <Mail className="h-5 w-5" />,
},
{
id: "calendar",
label: "Calendar",
category: "Applications",
icon: <Calendar className="h-5 w-5" />,
},
{
id: "music",
label: "Music",
category: "Applications",
icon: <Music className="h-5 w-5" />,
},
{
id: "settings",
label: "System Preferences",
category: "Applications",
icon: <Settings className="h-5 w-5" />,
},
{
id: "documents",
label: "Documents",
category: "Folders",
icon: <Folder className="h-5 w-5" />,
},
{
id: "downloads",
label: "Downloads",
category: "Folders",
icon: <Folder className="h-5 w-5" />,
},
]
export function AppLauncher() {
@@ -147,7 +201,7 @@ export function AppLauncher() {
// ⌘+Space to open (like macOS)
useKeyboardShortcuts([
{ key: ' ', meta: true, action: () => setIsOpen(true) }
{ key: " ", meta: true, action: () => setIsOpen(true) },
])
return (
@@ -157,7 +211,7 @@ export function AppLauncher() {
items={apps}
placeholder="Search applications..."
onSelect={(item) => {
console.log('Launch:', item.label)
console.log("Launch:", item.label)
setIsOpen(false)
}}
/>
@@ -169,22 +223,51 @@ export function AppLauncher() {
```tsx
import { Spotlight, SpotlightItem } from "@hanzo/ui/desktop"
import {
Save,
Copy,
Scissors,
Clipboard,
Undo,
Redo
} from "lucide-react"
import { Clipboard, Copy, Redo, Save, Scissors, Undo } from "lucide-react"
const commands: SpotlightItem[] = [
{ id: 'save', label: 'Save', category: 'File', icon: <Save />, shortcut: '⌘S' },
{ id: 'copy', label: 'Copy', category: 'Edit', icon: <Copy />, shortcut: '⌘C' },
{ id: 'cut', label: 'Cut', category: 'Edit', icon: <Scissors />, shortcut: '⌘X' },
{ id: 'paste', label: 'Paste', category: 'Edit', icon: <Clipboard />, shortcut: '⌘V' },
{ id: 'undo', label: 'Undo', category: 'Edit', icon: <Undo />, shortcut: '⌘Z' },
{ id: 'redo', label: 'Redo', category: 'Edit', icon: <Redo />, shortcut: '⌘⇧Z' },
{
id: "save",
label: "Save",
category: "File",
icon: <Save />,
shortcut: "⌘S",
},
{
id: "copy",
label: "Copy",
category: "Edit",
icon: <Copy />,
shortcut: "⌘C",
},
{
id: "cut",
label: "Cut",
category: "Edit",
icon: <Scissors />,
shortcut: "⌘X",
},
{
id: "paste",
label: "Paste",
category: "Edit",
icon: <Clipboard />,
shortcut: "⌘V",
},
{
id: "undo",
label: "Undo",
category: "Edit",
icon: <Undo />,
shortcut: "⌘Z",
},
{
id: "redo",
label: "Redo",
category: "Edit",
icon: <Redo />,
shortcut: "⌘⇧Z",
},
]
export function CommandPalette() {
@@ -197,7 +280,7 @@ export function CommandPalette() {
items={commands}
placeholder="Type a command..."
onSelect={(item) => {
console.log('Execute:', item.id)
console.log("Execute:", item.id)
setIsOpen(false)
}}
/>
@@ -207,9 +290,9 @@ export function CommandPalette() {
## Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| `↑` / `↓` | Navigate between items |
| `Enter` | Select highlighted item |
| `Escape` | Close spotlight |
| Shortcut | Action |
| --------------- | -------------------------------- |
| `↑` / `↓` | Navigate between items |
| `Enter` | Select highlighted item |
| `Escape` | Close spotlight |
| `⌘K` / `⌘Space` | Open spotlight (when configured) |
+19 -19
View File
@@ -75,17 +75,17 @@ export function WindowDemo() {
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `title` | `string` | - | Window title displayed in the title bar |
| `onClose` | `() => void` | - | Callback when the close button is clicked |
| `initialPosition` | `{ x: number, y: number }` | `{ x: 100, y: 100 }` | Initial window position |
| `initialSize` | `{ width: number, height: number }` | `{ width: 800, height: 600 }` | Initial window dimensions |
| `minWidth` | `number` | `200` | Minimum window width |
| `minHeight` | `number` | `150` | Minimum window height |
| `windowType` | `'default' \| 'dark' \| 'light' \| 'transparent'` | `'default'` | Window style variant |
| `className` | `string` | - | Additional CSS classes for the window |
| `children` | `ReactNode` | - | Window content |
| Prop | Type | Default | Description |
| ----------------- | ------------------------------------------------- | ----------------------------- | ----------------------------------------- |
| `title` | `string` | - | Window title displayed in the title bar |
| `onClose` | `() => void` | - | Callback when the close button is clicked |
| `initialPosition` | `{ x: number, y: number }` | `{ x: 100, y: 100 }` | Initial window position |
| `initialSize` | `{ width: number, height: number }` | `{ width: 800, height: 600 }` | Initial window dimensions |
| `minWidth` | `number` | `200` | Minimum window width |
| `minHeight` | `number` | `150` | Minimum window height |
| `windowType` | `'default' \| 'dark' \| 'light' \| 'transparent'` | `'default'` | Window style variant |
| `className` | `string` | - | Additional CSS classes for the window |
| `children` | `ReactNode` | - | Window content |
## Variants
@@ -128,7 +128,7 @@ export function WindowDemo() {
```tsx
import { Window } from "@hanzo/ui/desktop"
import { Settings, User, Bell } from "lucide-react"
import { Bell, Settings, User } from "lucide-react"
export function SettingsWindow() {
const [isOpen, setIsOpen] = useState(true)
@@ -164,35 +164,35 @@ export function SettingsWindow() {
### Multiple Windows
```tsx
import { Window, useWindowManager } from "@hanzo/ui/desktop"
import { useWindowManager, Window } from "@hanzo/ui/desktop"
export function MultiWindowDemo() {
const windows = useWindowManager()
return (
<div className="relative h-screen">
{windows.isOpen('Settings') && (
{windows.isOpen("Settings") && (
<Window
title="Settings"
onClose={() => windows.close('Settings')}
onClose={() => windows.close("Settings")}
initialPosition={{ x: 100, y: 100 }}
>
<div className="p-4">Settings content</div>
</Window>
)}
{windows.isOpen('Editor') && (
{windows.isOpen("Editor") && (
<Window
title="Editor"
onClose={() => windows.close('Editor')}
onClose={() => windows.close("Editor")}
initialPosition={{ x: 200, y: 150 }}
>
<div className="p-4">Editor content</div>
</Window>
)}
<button onClick={() => windows.open('Settings')}>Open Settings</button>
<button onClick={() => windows.open('Editor')}>Open Editor</button>
<button onClick={() => windows.open("Settings")}>Open Settings</button>
<button onClick={() => windows.open("Editor")}>Open Editor</button>
</div>
)
}
+20 -22
View File
@@ -8,19 +8,19 @@ The Desktop namespace provides components and hooks for building macOS-style des
## Components
| Component | Description |
|-----------|-------------|
| [Window](/docs/components/desktop-window) | Draggable, resizable window with minimize, maximize, and close controls |
| [Spotlight](/docs/components/desktop-spotlight) | macOS Spotlight-style search and command palette |
| Component | Description |
| ----------------------------------------------- | ----------------------------------------------------------------------- |
| [Window](/docs/components/desktop-window) | Draggable, resizable window with minimize, maximize, and close controls |
| [Spotlight](/docs/components/desktop-spotlight) | macOS Spotlight-style search and command palette |
## Hooks
| Hook | Description |
|------|-------------|
| `useWindowManager` | Manage multiple windows with open/close/toggle state |
| `useDesktopSettings` | Persist desktop settings like theme, dock position |
| `useOverlayManager` | Manage overlay states (spotlight, about dialog, etc.) |
| `useKeyboardShortcuts` | Register keyboard shortcuts (⌘+Space, ⌘+Q, etc.) |
| Hook | Description |
| ---------------------- | ----------------------------------------------------- |
| `useWindowManager` | Manage multiple windows with open/close/toggle state |
| `useDesktopSettings` | Persist desktop settings like theme, dock position |
| `useOverlayManager` | Manage overlay states (spotlight, about dialog, etc.) |
| `useKeyboardShortcuts` | Register keyboard shortcuts (⌘+Space, ⌘+Q, etc.) |
See [Desktop Hooks](/docs/components/desktop-hooks) for detailed hook documentation.
@@ -51,12 +51,12 @@ Then import from the desktop namespace:
```tsx
import {
Window,
Spotlight,
useWindowManager,
useDesktopSettings,
useKeyboardShortcuts,
useOverlayManager,
useKeyboardShortcuts
useWindowManager,
Window,
} from "@hanzo/ui/desktop"
```
@@ -68,10 +68,10 @@ import {
```tsx
import {
Window,
Spotlight,
useKeyboardShortcuts,
useWindowManager,
useKeyboardShortcuts
Window,
} from "@hanzo/ui/desktop"
import { Dock, DockItem } from "@hanzo/ui/dock"
@@ -80,16 +80,16 @@ export function MyDesktop() {
const [showSpotlight, setShowSpotlight] = useState(false)
useKeyboardShortcuts([
{ key: ' ', meta: true, action: () => setShowSpotlight(true) },
{ key: " ", meta: true, action: () => setShowSpotlight(true) },
])
return (
<div className="h-screen relative">
{/* Windows */}
{windows.isOpen('Settings') && (
{windows.isOpen("Settings") && (
<Window
title="Settings"
onClose={() => windows.closeWindow('Settings')}
onClose={() => windows.closeWindow("Settings")}
>
<div className="p-4">Settings content</div>
</Window>
@@ -99,9 +99,7 @@ export function MyDesktop() {
<Spotlight
isOpen={showSpotlight}
onClose={() => setShowSpotlight(false)}
items={[
{ id: 'settings', title: 'Settings', category: 'Apps' },
]}
items={[{ id: "settings", title: "Settings", category: "Apps" }]}
onSelect={(item) => {
windows.openWindow(item.title)
setShowSpotlight(false)
@@ -112,7 +110,7 @@ export function MyDesktop() {
<Dock position="bottom">
<DockItem
tooltip="Settings"
onClick={() => windows.toggleWindow('Settings')}
onClick={() => windows.toggleWindow("Settings")}
/>
</Dock>
</div>
+45
View File
@@ -0,0 +1,45 @@
---
title: Direction
description: Provider and hook for RTL/LTR text direction support.
---
## Overview
The Direction component provides RTL (right-to-left) and LTR (left-to-right) text direction support through a React context provider and hook. Useful for internationalization and bidirectional text layouts.
## Usage
```tsx
import { DirectionProvider, useDirection } from "@hanzo/ui"
export function App() {
return (
<DirectionProvider direction="rtl">
<MyComponent />
</DirectionProvider>
)
}
function MyComponent() {
const direction = useDirection()
return <div>Current direction: {direction}</div>
}
```
## Props
### DirectionProvider
| Prop | Type | Default | Description |
| --------- | -------------- | ------- | --------------- |
| dir | "ltr" \| "rtl" | - | Text direction |
| direction | "ltr" \| "rtl" | - | Alias for `dir` |
| children | ReactNode | - | Child elements |
### useDirection
Returns the current direction from the nearest `DirectionProvider`.
```tsx
const direction = useDirection() // "ltr" | "rtl"
```
@@ -0,0 +1,56 @@
---
title: Stock Market
description: Display stock market overview with major indices and equities.
component: true
---
<ComponentPreview name="market-overview" description="Stock market overview" />
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx @hanzo/ui@latest add market-overview
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install the following dependencies:</Step>
```bash
npm install @hanzo/ui
```
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="market-overview" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
## Usage
```tsx
import { MarketOverview } from "@hanzo/ui/finance"
export default function Demo() {
return <MarketOverview tabs={["indices"]} colorTheme="dark" />
}
```
@@ -0,0 +1,119 @@
---
title: React Native
description: Using @hanzo/ui components in React Native and Expo applications.
---
# React Native Guide (Beta)
@hanzo/ui provides React Native components built on top of React Native's core primitives and NativeWind for styling. This integration is currently in beta.
## Installation
<Steps>
### Install the package
```bash
npm install @hanzo/ui
# or
pnpm add @hanzo/ui
```
### Install NativeWind
```bash
npm install nativewind
npm install -D tailwindcss
```
### Configure Tailwind
```js title="tailwind.config.js"
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./App.{js,jsx,ts,tsx}",
"./src/**/*.{js,jsx,ts,tsx}",
"./node_modules/@hanzo/ui/**/*.{js,ts,jsx,tsx}",
],
presets: [require("nativewind/preset")],
theme: {
extend: {},
},
plugins: [],
}
```
</Steps>
## Usage
```tsx
import { useState } from "react"
import { Button, Card, Input } from "@hanzo/ui/native"
export default function App() {
const [count, setCount] = useState(0)
return (
<Card>
<Button onPress={() => setCount((c) => c + 1)}>
Clicked {count} times
</Button>
<Input value={String(count)} />
</Card>
)
}
```
## Expo Integration
```tsx
// app/(tabs)/index.tsx
import { Button, Card } from "@hanzo/ui/native"
export default function HomeScreen() {
return (
<Card>
<Button>Click me</Button>
</Card>
)
}
```
## Component Coverage
**Available:**
- Button, Card, Input, Label, Badge
- Checkbox, Switch, Separator
- Tabs, Avatar
**In Progress:**
- Dialog and Sheet (modal-based)
- Select and Popover
- Navigation components
**Not Available:**
- 3D components
- Browser-specific components (Safari mockup, etc.)
- Chart components
## Platform Differences
Some components behave differently on native platforms:
- **Dialog** uses React Native's `Modal` component
- **Sheet** uses bottom sheet gestures via `react-native-gesture-handler`
- **Animations** use `react-native-reanimated` instead of Framer Motion
## Status
This integration is in **beta**. Component coverage is limited compared to the web version. Please report issues on [GitHub](https://github.com/hanzoai/ui/issues).
## Next Steps
- [View all components](/docs/components)
- [React Guide](/docs/frameworks/react) (stable)
+100
View File
@@ -0,0 +1,100 @@
---
title: Svelte
description: Using @hanzo/ui components in Svelte and SvelteKit applications.
---
# Svelte Guide (Beta)
@hanzo/ui provides Svelte 5 components with full TypeScript support. This integration is currently in beta.
## Installation
<Steps>
### Install the package
```bash
npm install @hanzo/ui
# or
pnpm add @hanzo/ui
```
### Install Svelte and dependencies
```bash
npm install svelte
npm install -D tailwindcss postcss autoprefixer
```
### Configure Tailwind
```js title="tailwind.config.js"
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{html,js,svelte,ts}",
"./node_modules/@hanzo/ui/**/*.{js,ts,svelte}",
],
theme: {
extend: {},
},
plugins: [],
}
```
</Steps>
## Usage
```svelte
<script lang="ts">
import { Button, Card, Input } from '@hanzo/ui/svelte'
let count = $state(0)
</script>
<Card>
<Button onclick={() => count++}>
Clicked {count} times
</Button>
<Input bind:value={count} type="number" />
</Card>
```
## SvelteKit Integration
```svelte
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { Button, Card } from '@hanzo/ui/svelte'
</script>
<Card>
<h1>Welcome to SvelteKit</h1>
<Button>Click me</Button>
</Card>
```
## Component Coverage
**Available:**
- Button, Card, Input, Label, Badge
- Checkbox, Dialog, Select, Separator
- Tabs, Toggle, Tooltip
**In Progress:**
- Form components
- Data Table
- Advanced animations
## Status
This integration is in **beta**. Some components may have limited functionality compared to the React version. Please report issues on [GitHub](https://github.com/hanzoai/ui/issues).
## Next Steps
- [View all components](/docs/components)
- [React Guide](/docs/frameworks/react) (stable)
- [Vue Guide](/docs/frameworks/vue) (stable)
+8 -19
View File
@@ -1,5 +1,5 @@
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import tsParser from "@typescript-eslint/parser";
export default [
{
@@ -12,14 +12,16 @@ export default [
"dist/**",
".turbo/**",
"node_modules/**",
"coverage/**",
"*.config.js",
"*.config.mjs",
],
},
// Use only base configs without rules
// JavaScript files
{
files: ["**/*.{js,jsx,mjs,cjs}"],
...js.configs.recommended,
rules: {
// Turn off all rules
"no-unused-vars": "off",
"no-undef": "off",
"no-empty": "off",
@@ -30,26 +32,13 @@ export default [
"prefer-const": "off",
},
},
// TypeScript files
// TypeScript files - parser only, all rules off (rely on TypeScript compiler)
{
files: ["**/*.{ts,tsx,mts}"],
languageOptions: {
parser: tseslint.parser,
},
plugins: {
"@typescript-eslint": tseslint.plugin,
},
rules: {
// Turn off ALL rules - rely on TypeScript compiler
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-object-type": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/no-unsafe-function-type": "off",
"@typescript-eslint/no-empty-interface": "off",
parser: tsParser,
},
rules: {},
},
// CommonJS files
{
+33
View File
@@ -0,0 +1,33 @@
/**
* Hanzo Cloud search configuration for ui.hanzo.ai
*
* Uses the same infrastructure as docs.hanzo.ai:
* - Meilisearch for fulltext search
* - Qdrant for vector/semantic search
* - Hanzo Cloud API for RAG chat
*/
export const searchBackend =
(process.env.NEXT_PUBLIC_HANZO_SEARCH_BACKEND as 'cloud' | 'meilisearch' | undefined) ??
'cloud'
export const searchEndpoint =
process.env.NEXT_PUBLIC_HANZO_SEARCH_ENDPOINT ??
'https://cloud-api.hanzo.ai/api/search-docs'
export const searchIndex =
process.env.NEXT_PUBLIC_HANZO_SEARCH_INDEX ?? 'app-ui-hanzo-ai'
export const indexEndpoint =
process.env.HANZO_SEARCH_INDEX_ENDPOINT ??
'https://cloud-api.hanzo.ai/api/index-docs'
export const chatEndpoint =
process.env.NEXT_PUBLIC_HANZO_CHAT_ENDPOINT ??
'https://cloud-api.hanzo.ai/api/chat-docs'
export const publishableKey =
process.env.NEXT_PUBLIC_HANZO_SEARCH_KEY ?? 'pk-hanzo-ui-search-2026'
export const adminKey =
process.env.HANZO_SEARCH_ADMIN_KEY ?? ''
+2 -2
View File
@@ -5,8 +5,8 @@ const nextConfig = {
// Transpile packages that might have issues with pnpm symlinks
transpilePackages: ["chrono-node", "@hanzo/ui"],
// Enable static export for GitHub Pages deployment (but not for E2E tests)
output: process.env.GITHUB_ACTIONS && !process.env.E2E_TEST ? "export" : undefined,
// Enable static export for GitHub Pages / Cloudflare Pages deployment (but not for E2E tests)
output: (process.env.GITHUB_ACTIONS || process.env.CF_PAGES) && !process.env.E2E_TEST ? "export" : undefined,
// Use trailing slashes for GitHub Pages compatibility
trailingSlash: true,
+77 -73
View File
@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "next dev --port 3333",
"build": "pnpm registry:build && (pnpm registry:capture || echo 'Skipping screenshots in CI') && next build",
"build": "pnpm registry:build && pnpm registry:api && (pnpm registry:capture || echo 'Skipping screenshots in CI') && next build",
"start": "next start --port 3001",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
@@ -17,20 +17,27 @@
"format:write": "prettier --write \"**/*.{ts,tsx,mdx}\" --cache",
"format:check": "prettier --check \"**/*.{ts,tsx,mdx}\" --cache",
"registry:build": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts",
"registry:api": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry-api.mts",
"registry:capture": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/capture-registry.mts",
"validate:registries": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/validate-registries.mts",
"postinstall": "docs-mdx source.config.ts .source"
},
"dependencies": {
"@ai-sdk/openai-compatible": "^0.2.0",
"ai": "^4.3.0",
"@ctrl/tinycolor": "^4.2.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@faker-js/faker": "^10.0.0",
"@hanzo/logo": "^1.0.3",
"@faker-js/faker": "^10.3.0",
"@hanzo/docs-core": "16.5.3",
"@hanzo/docs-docgen": "3.0.7",
"@hanzo/docs-mdx": "14.3.0",
"@hanzo/docs-ui": "16.5.3",
"@hanzo/logo": "^1.0.5",
"@hanzo/ui": "workspace:^",
"@hookform/resolvers": "^5.2.2",
"@hookform/resolvers": "5.2.2",
"@monaco-editor/react": "^4.7.0",
"@radix-ui/react-accessible-icon": "^1.1.8",
"@radix-ui/react-accordion": "^1.2.12",
@@ -41,6 +48,7 @@
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-direction": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-icons": "^1.3.2",
@@ -62,18 +70,18 @@
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@rainbow-me/rainbowkit": "^2.2.9",
"@rc-component/color-picker": "^3.0.2",
"@tabler/icons-react": "^3.35.0",
"@tanstack/react-query": "^5.90.7",
"@rainbow-me/rainbowkit": "^2.2.10",
"@rc-component/color-picker": "^3.1.1",
"@tabler/icons-react": "^3.40.0",
"@tanstack/react-query": "^5.94.5",
"@tanstack/react-table": "^8.21.3",
"@tiptap/extension-placeholder": "^3.10.2",
"@tiptap/react": "^3.10.2",
"@tiptap/starter-kit": "^3.10.2",
"@tiptap/extension-placeholder": "^3.20.4",
"@tiptap/react": "^3.20.4",
"@tiptap/starter-kit": "^3.20.4",
"@types/react-syntax-highlighter": "^15.5.13",
"@vercel/analytics": "^1.5.0",
"@vercel/og": "^0.8.5",
"@xyflow/react": "^12.8.6",
"@vercel/analytics": "^2.0.1",
"@vercel/og": "^0.11.1",
"@xyflow/react": "^12.10.1",
"chrono-node": "^2.9.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -82,87 +90,83 @@
"date-fns": "^4.1.0",
"embla-carousel-autoplay": "8.6.0",
"embla-carousel-react": "8.6.0",
"framer-motion": "^12.23.22",
"@hanzo/docs-core": "16.2.6",
"@hanzo/docs-docgen": "3.0.4",
"@hanzo/docs-mdx": "14.3.0",
"@hanzo/docs-ui": "16.2.6",
"fumadocs-core": "^16.4.8",
"fumadocs-mdx": "^14.2.6",
"geist": "^1.5.1",
"fumadocs-core": "^16.7.4",
"fumadocs-mdx": "^14.2.11",
"geist": "^1.7.0",
"input-otp": "^1.4.2",
"jotai": "^2.15.0",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"lucide-react": "0.544.0",
"monaco-editor": "^0.53.0",
"motion": "^12.23.22",
"jotai": "^2.18.1",
"js-yaml": "^4.1.1",
"lodash": "^4.17.23",
"lucide-react": "^0.577.0",
"monaco-editor": "^0.55.1",
"motion": "^12.38.0",
"next-themes": "0.4.6",
"qrcode.react": "^4.2.0",
"react": "19.2.0",
"react": "19.2.4",
"react-colorful": "^5.6.1",
"react-day-picker": "^9.11.0",
"react-dom": "19.2.0",
"react-dropzone": "^14.3.8",
"react-hook-form": "^7.64.0",
"react-day-picker": "^9.14.0",
"react-dom": "19.2.4",
"react-dropzone": "^15.0.0",
"react-hook-form": "^7.71.2",
"react-image-crop": "^11.0.10",
"react-medium-image-zoom": "^5.4.0",
"react-medium-image-zoom": "^5.4.1",
"react-resizable-panels": "^3.0.6",
"react-syntax-highlighter": "^15.6.6",
"react-syntax-highlighter": "^16.1.1",
"react-wrap-balancer": "^1.1.1",
"recharts": "3.3.0",
"recharts": "3.8.0",
"sonner": "^2.0.7",
"swr": "^2.3.6",
"tailwind-merge": "^3.3.1",
"swr": "^2.4.1",
"tailwind-merge": "^3.5.0",
"tailwindcss-animate": "^1.0.7",
"ts-morph": "27.0.2",
"vaul": "1.1.2",
"viem": "^2.38.6",
"wagmi": "^2.19.2",
"zod": "^4.1.11",
"zustand": "^5.0.8"
"viem": "^2.47.6",
"wagmi": "^3.5.0",
"zod": "^4.3.6",
"zustand": "^5.0.12"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
"@playwright/test": "^1.55.1",
"@tailwindcss/postcss": "^4.1.17",
"@eslint/eslintrc": "^3.3.5",
"@ianvs/prettier-plugin-sort-imports": "^4.7.1",
"@playwright/test": "^1.58.2",
"@tailwindcss/postcss": "^4.2.2",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@testing-library/react": "^16.3.2",
"@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.17.20",
"@types/lodash": "^4.17.24",
"@types/mdx": "^2.0.13",
"@types/node": "^24.6.2",
"@types/react": "19.2.2",
"@types/react-dom": "19.2.2",
"@types/node": "^25.5.0",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@types/unist": "^3.0.3",
"@typescript-eslint/parser": "^8.46.3",
"@vitejs/plugin-react": "^5.0.4",
"@vitest/ui": "^4.0.7",
"autoprefixer": "^10.4.21",
"@typescript-eslint/parser": "^8.57.1",
"@vitejs/plugin-react": "^6.0.1",
"@vitest/ui": "^4.1.0",
"autoprefixer": "^10.4.27",
"del-cli": "^7.0.0",
"eslint": "^9.39.1",
"eslint-config-next": "16.0.1",
"happy-dom": "^20.0.10",
"next": "16.0.1",
"@eslint/js": "^9.28.0",
"eslint": "^10.1.0",
"eslint-config-next": "16.2.1",
"happy-dom": "^20.8.4",
"next": "^16.2.1",
"npm-run-all": "^4.1.5",
"playwright": "^1.55.1",
"playwright": "^1.58.2",
"png-to-ico": "^3.0.1",
"postcss": "^8.5.6",
"postcss-selector-parser": "^7.1.0",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"puppeteer": "^23.6.0",
"rehype-pretty-code": "^0.14.1",
"rimraf": "^6.0.1",
"sharp": "^0.34.4",
"shiki": "^3.15.0",
"tailwindcss": "^4.1.17",
"tsx": "^4.20.6",
"postcss": "^8.5.8",
"postcss-selector-parser": "^7.1.1",
"prettier": "^3.8.1",
"prettier-plugin-tailwindcss": "^0.7.2",
"puppeteer": "^24.40.0",
"rehype-pretty-code": "^0.14.3",
"rimraf": "^6.1.3",
"sharp": "^0.34.5",
"shiki": "^4.0.2",
"tailwindcss": "^4.2.2",
"tsx": "^4.21.0",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"unist-builder": "4.0.0",
"unist-util-visit": "^5.0.0",
"vitest": "^4.0.7"
"unist-util-visit": "^5.1.0",
"vitest": "^4.1.0"
}
}
+14 -14
View File
@@ -19,7 +19,12 @@
"destructive-foreground": "gray-50",
"border": "gray-200",
"input": "gray-200",
"ring": "gray-950"
"ring": "gray-950",
"chart-1": "12 76% 61%",
"chart-2": "173 58% 39%",
"chart-3": "197 37% 24%",
"chart-4": "43 74% 66%",
"chart-5": "27 87% 67%"
},
"dark": {
"background": "gray-950",
@@ -40,7 +45,12 @@
"destructive-foreground": "gray-50",
"border": "gray-800",
"input": "gray-800",
"ring": "gray-300"
"ring": "gray-300",
"chart-1": "220 70% 50%",
"chart-2": "160 60% 45%",
"chart-3": "30 80% 55%",
"chart-4": "280 65% 60%",
"chart-5": "340 75% 55%"
}
},
"cssVars": {
@@ -63,12 +73,7 @@
"destructive-foreground": "210 20% 98%",
"border": "220 13% 91%",
"input": "220 13% 91%",
"ring": "224 71.4% 4.1%",
"chart-1": "12 76% 61%",
"chart-2": "173 58% 39%",
"chart-3": "197 37% 24%",
"chart-4": "43 74% 66%",
"chart-5": "27 87% 67%"
"ring": "224 71.4% 4.1%"
},
"dark": {
"background": "224 71.4% 4.1%",
@@ -89,12 +94,7 @@
"destructive-foreground": "210 20% 98%",
"border": "215 27.9% 16.9%",
"input": "215 27.9% 16.9%",
"ring": "216 12.2% 83.9%",
"chart-1": "220 70% 50%",
"chart-2": "160 60% 45%",
"chart-3": "30 80% 55%",
"chart-4": "280 65% 60%",
"chart-5": "340 75% 55%"
"ring": "216 12.2% 83.9%"
}
},
"inlineColorsTemplate": "@tailwind base;\n @tailwind components;\n @tailwind utilities;\n ",
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -19,7 +19,12 @@
"destructive-foreground": "neutral-50",
"border": "neutral-200",
"input": "neutral-200",
"ring": "neutral-950"
"ring": "neutral-950",
"chart-1": "12 76% 61%",
"chart-2": "173 58% 39%",
"chart-3": "197 37% 24%",
"chart-4": "43 74% 66%",
"chart-5": "27 87% 67%"
},
"dark": {
"background": "neutral-950",
@@ -40,7 +45,12 @@
"destructive-foreground": "neutral-50",
"border": "neutral-800",
"input": "neutral-800",
"ring": "neutral-300"
"ring": "neutral-300",
"chart-1": "220 70% 50%",
"chart-2": "160 60% 45%",
"chart-3": "30 80% 55%",
"chart-4": "280 65% 60%",
"chart-5": "340 75% 55%"
}
},
"cssVars": {
@@ -63,12 +73,7 @@
"destructive-foreground": "0 0% 98%",
"border": "0 0% 89.8%",
"input": "0 0% 89.8%",
"ring": "0 0% 3.9%",
"chart-1": "12 76% 61%",
"chart-2": "173 58% 39%",
"chart-3": "197 37% 24%",
"chart-4": "43 74% 66%",
"chart-5": "27 87% 67%"
"ring": "0 0% 3.9%"
},
"dark": {
"background": "0 0% 3.9%",
@@ -89,12 +94,7 @@
"destructive-foreground": "0 0% 98%",
"border": "0 0% 14.9%",
"input": "0 0% 14.9%",
"ring": "0 0% 83.1%",
"chart-1": "220 70% 50%",
"chart-2": "160 60% 45%",
"chart-3": "30 80% 55%",
"chart-4": "280 65% 60%",
"chart-5": "340 75% 55%"
"ring": "0 0% 83.1%"
}
},
"inlineColorsTemplate": "@tailwind base;\n @tailwind components;\n @tailwind utilities;\n ",
+13 -8
View File
@@ -19,7 +19,12 @@
"destructive-foreground": "slate-50",
"border": "slate-200",
"input": "slate-200",
"ring": "slate-950"
"ring": "slate-950",
"chart-1": "12 76% 61%",
"chart-2": "173 58% 39%",
"chart-3": "197 37% 24%",
"chart-4": "43 74% 66%",
"chart-5": "27 87% 67%"
},
"dark": {
"background": "slate-950",
@@ -40,7 +45,12 @@
"destructive-foreground": "slate-50",
"border": "slate-800",
"input": "slate-800",
"ring": "slate-300"
"ring": "slate-300",
"chart-1": "220 70% 50%",
"chart-2": "160 60% 45%",
"chart-3": "30 80% 55%",
"chart-4": "280 65% 60%",
"chart-5": "340 75% 55%"
}
},
"cssVars": {
@@ -63,12 +73,7 @@
"destructive-foreground": "210 40% 98%",
"border": "214.3 31.8% 91.4%",
"input": "214.3 31.8% 91.4%",
"ring": "222.2 84% 4.9%",
"chart-1": "12 76% 61%",
"chart-2": "173 58% 39%",
"chart-3": "197 37% 24%",
"chart-4": "43 74% 66%",
"chart-5": "27 87% 67%"
"ring": "222.2 84% 4.9%"
},
"dark": {
"background": "222.2 84% 4.9%",
+14 -14
View File
@@ -19,7 +19,12 @@
"destructive-foreground": "stone-50",
"border": "stone-200",
"input": "stone-200",
"ring": "stone-950"
"ring": "stone-950",
"chart-1": "12 76% 61%",
"chart-2": "173 58% 39%",
"chart-3": "197 37% 24%",
"chart-4": "43 74% 66%",
"chart-5": "27 87% 67%"
},
"dark": {
"background": "stone-950",
@@ -40,7 +45,12 @@
"destructive-foreground": "stone-50",
"border": "stone-800",
"input": "stone-800",
"ring": "stone-300"
"ring": "stone-300",
"chart-1": "220 70% 50%",
"chart-2": "160 60% 45%",
"chart-3": "30 80% 55%",
"chart-4": "280 65% 60%",
"chart-5": "340 75% 55%"
}
},
"cssVars": {
@@ -63,12 +73,7 @@
"destructive-foreground": "60 9.1% 97.8%",
"border": "20 5.9% 90%",
"input": "20 5.9% 90%",
"ring": "20 14.3% 4.1%",
"chart-1": "12 76% 61%",
"chart-2": "173 58% 39%",
"chart-3": "197 37% 24%",
"chart-4": "43 74% 66%",
"chart-5": "27 87% 67%"
"ring": "20 14.3% 4.1%"
},
"dark": {
"background": "20 14.3% 4.1%",
@@ -89,12 +94,7 @@
"destructive-foreground": "60 9.1% 97.8%",
"border": "12 6.5% 15.1%",
"input": "12 6.5% 15.1%",
"ring": "24 5.7% 82.9%",
"chart-1": "220 70% 50%",
"chart-2": "160 60% 45%",
"chart-3": "30 80% 55%",
"chart-4": "280 65% 60%",
"chart-5": "340 75% 55%"
"ring": "24 5.7% 82.9%"
}
},
"inlineColorsTemplate": "@tailwind base;\n @tailwind components;\n @tailwind utilities;\n ",
+14 -14
View File
@@ -19,7 +19,12 @@
"destructive-foreground": "zinc-50",
"border": "zinc-200",
"input": "zinc-200",
"ring": "zinc-950"
"ring": "zinc-950",
"chart-1": "12 76% 61%",
"chart-2": "173 58% 39%",
"chart-3": "197 37% 24%",
"chart-4": "43 74% 66%",
"chart-5": "27 87% 67%"
},
"dark": {
"background": "zinc-950",
@@ -40,7 +45,12 @@
"destructive-foreground": "zinc-50",
"border": "zinc-800",
"input": "zinc-800",
"ring": "zinc-300"
"ring": "zinc-300",
"chart-1": "220 70% 50%",
"chart-2": "160 60% 45%",
"chart-3": "30 80% 55%",
"chart-4": "280 65% 60%",
"chart-5": "340 75% 55%"
}
},
"cssVars": {
@@ -63,12 +73,7 @@
"destructive-foreground": "0 0% 98%",
"border": "240 5.9% 90%",
"input": "240 5.9% 90%",
"ring": "240 10% 3.9%",
"chart-1": "12 76% 61%",
"chart-2": "173 58% 39%",
"chart-3": "197 37% 24%",
"chart-4": "43 74% 66%",
"chart-5": "27 87% 67%"
"ring": "240 10% 3.9%"
},
"dark": {
"background": "240 10% 3.9%",
@@ -89,12 +94,7 @@
"destructive-foreground": "0 0% 98%",
"border": "240 3.7% 15.9%",
"input": "240 3.7% 15.9%",
"ring": "240 4.9% 83.9%",
"chart-1": "220 70% 50%",
"chart-2": "160 60% 45%",
"chart-3": "30 80% 55%",
"chart-4": "280 65% 60%",
"chart-5": "340 75% 55%"
"ring": "240 4.9% 83.9%"
}
},
"inlineColorsTemplate": "@tailwind base;\n @tailwind components;\n @tailwind utilities;\n ",
+10
View File
@@ -184,6 +184,16 @@
],
"type": "components:ui"
},
{
"name": "direction",
"dependencies": [
"@radix-ui/react-direction"
],
"files": [
"ui/direction.tsx"
],
"type": "components:ui"
},
{
"name": "dialog",
"dependencies": [
@@ -7,7 +7,7 @@
"files": [
{
"name": "advanced-chart.tsx",
"content": "export * from \"../ui/advanced-chart\"\n"
"content": "export { default } from \"../ui/advanced-chart\"\nexport * from \"../ui/advanced-chart\"\n"
}
],
"type": "components:finance",
@@ -4,7 +4,7 @@
"files": [
{
"name": "advanced-navigation-bar.tsx",
"content": "export default function Component() {\n return (\n <div className=\"flex items-center justify-center p-8\">\n <p className=\"text-muted-foreground\">Component coming soon</p>\n </div>\n )\n}\n"
"content": "export * from \"../advanced-navigation-bar\"\n"
}
],
"type": "components:ui"
@@ -4,7 +4,7 @@
"files": [
{
"name": "ai-model-selector-navigation-bar.tsx",
"content": "export default function Component() {\n return (\n <div className=\"flex items-center justify-center p-8\">\n <p className=\"text-muted-foreground\">Component coming soon</p>\n </div>\n )\n}\n"
"content": "export * from \"../ai-model-selector-navigation-bar\"\n"
}
],
"type": "components:ui"
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
"files": [
{
"name": "app-switcher-navigation-bar.tsx",
"content": "export default function Component() {\n return (\n <div className=\"flex items-center justify-center p-8\">\n <p className=\"text-muted-foreground\">Component coming soon</p>\n </div>\n )\n}\n"
"content": "export * from \"../app-switcher-navigation-bar\"\n"
}
],
"type": "components:ui"
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
"files": [
{
"name": "breadcrumb-and-filters-navigation-bar.tsx",
"content": "export default function Component() {\n return (\n <div className=\"flex items-center justify-center p-8\">\n <p className=\"text-muted-foreground\">Component coming soon</p>\n </div>\n )\n}\n"
"content": "export * from \"../breadcrumb-and-filters-navigation-bar\"\n"
}
],
"type": "components:ui"

Some files were not shown because too many files have changed in this diff Show More