mirror of
https://mirror.skon.top/github.com/langgenius/dify.git
synced 2026-04-21 07:50:26 +08:00
106 lines
6.6 KiB
Markdown
106 lines
6.6 KiB
Markdown
# @langgenius/dify-ui
|
|
|
|
Shared UI primitives, design tokens, Tailwind preset, and the `cn()` utility consumed by Dify's `web/` app.
|
|
|
|
The primitives are thin, opinionated wrappers around [Base UI] headless components, styled with `cva` + `cn` and Dify design tokens.
|
|
|
|
> `private: true` — this package is consumed by `web/` via the pnpm workspace and is not published to npm. Treat the API as internal to Dify, but stable within the workspace.
|
|
|
|
## Installation
|
|
|
|
Already wired as a workspace dependency in `web/package.json`. Nothing to install.
|
|
|
|
For a new workspace consumer, add:
|
|
|
|
```jsonc
|
|
{
|
|
"dependencies": {
|
|
"@langgenius/dify-ui": "workspace:*"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Imports
|
|
|
|
Always import from a **subpath export** — there is no barrel:
|
|
|
|
```ts
|
|
import { Button } from '@langgenius/dify-ui/button'
|
|
import { cn } from '@langgenius/dify-ui/cn'
|
|
import { Dialog, DialogContent, DialogTrigger } from '@langgenius/dify-ui/dialog'
|
|
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
|
import '@langgenius/dify-ui/styles.css' // once, in the app root
|
|
```
|
|
|
|
Importing from `@langgenius/dify-ui` (no subpath) is intentionally not supported — it keeps tree-shaking trivial and makes Storybook / test coverage attribution per-primitive.
|
|
|
|
## Primitives
|
|
|
|
| Category | Subpath | Notes |
|
|
| -------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------- |
|
|
| Overlay | `./alert-dialog`, `./context-menu`, `./dialog`, `./dropdown-menu`, `./popover`, `./select`, `./toast`, `./tooltip` | Portalled. See [Overlay & portal contract] below. |
|
|
| Form | `./number-field`, `./slider`, `./switch` | Controlled / uncontrolled per Base UI defaults. |
|
|
| Layout | `./scroll-area` | Custom-styled scrollbar over the host viewport. |
|
|
| Media | `./avatar`, `./button` | Button exposes `cva` variants. |
|
|
|
|
Utilities:
|
|
|
|
- `./cn` — `clsx` + `tailwind-merge` wrapper. Use this for conditional class composition.
|
|
- `./tailwind-preset` — Tailwind v4 preset with Dify tokens. Apps extend it from their own `tailwind.config.ts`.
|
|
- `./styles.css` — the one CSS entry that ships the design tokens, theme variables, and base reset. Import it once from the app root.
|
|
|
|
## Overlay & portal contract
|
|
|
|
All overlay primitives (`dialog`, `alert-dialog`, `popover`, `dropdown-menu`, `context-menu`, `select`, `tooltip`, `toast`) render their content inside a [Base UI Portal] attached to `document.body`. This is the Base UI default — see the upstream [Portals][Base UI Portal] docs for the underlying behavior. Consumers **do not** need to wrap anything in a portal manually.
|
|
|
|
### Root isolation requirement
|
|
|
|
The host app **must** establish an isolated stacking context at its root so the portalled overlay layer is not clipped or re-ordered by ancestor `transform` / `filter` / `contain` styles. In the Dify web app this is done in `web/app/layout.tsx`:
|
|
|
|
```tsx
|
|
<body>
|
|
<div className="isolate h-full">{children}</div>
|
|
</body>
|
|
```
|
|
|
|
Equivalent: any root element with `isolation: isolate` in CSS. Without it, overlays can be visually clipped on Safari when a descendant creates a new stacking context.
|
|
|
|
### z-index layering
|
|
|
|
Every overlay primitive uses a single, shared z-index. Do **not** override it at call sites.
|
|
|
|
| Layer | z-index | Where |
|
|
| ----------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------- |
|
|
| Overlays (Dialog, AlertDialog, Popover, DropdownMenu, ContextMenu, Select, Tooltip) | `z-1002` | Positioner / Backdrop |
|
|
| Toast viewport | `z-1003` | One layer above overlays so notifications are never hidden under a dialog. |
|
|
|
|
Rationale: during Dify's migration from legacy `portal-to-follow-elem` / `base/modal` / `base/dialog` overlays to this package, new and old overlays coexist in the DOM. `z-1002` sits above any common legacy layer, eliminating per-call-site z-index hacks. Among themselves, new primitives share the same z-index and **rely on DOM order** for stacking — the portal mounted later wins.
|
|
|
|
See `[web/docs/overlay-migration.md](../../web/docs/overlay-migration.md)` for the Dify-web migration history and the remaining legacy allowlist. Once the legacy overlays are gone, the values in this table can drop back to `z-50` / `z-51`.
|
|
|
|
### Rules
|
|
|
|
- Never add `z-1003` / `z-9999` / etc. overrides on primitives from this package. If something is getting clipped, the **parent** overlay (typically a legacy one) is the problem and should be migrated.
|
|
- Never portal an overlay manually on top of our primitives — use `DialogTrigger`, `PopoverTrigger`, etc. Base UI handles focus management, scroll-locking, and dismissal.
|
|
- When a primitive needs additional presentation chrome (e.g. a custom backdrop), add it **inside** the exported component, not at call sites.
|
|
|
|
## Development
|
|
|
|
- `pnpm -C packages/dify-ui test` — Vitest unit tests for primitives.
|
|
- `pnpm -C packages/dify-ui storybook` — Storybook on the default port. Each primitive has `index.stories.tsx`.
|
|
- `pnpm -C packages/dify-ui type-check` — `tsc --noEmit` for this package only.
|
|
|
|
See `[AGENTS.md](./AGENTS.md)` for:
|
|
|
|
- Component authoring rules (one-component-per-folder, `cva` + `cn`, relative imports inside the package, subpath imports from consumers).
|
|
- Figma `--radius/`* token → Tailwind `rounded-*` class mapping.
|
|
|
|
## Not part of this package
|
|
|
|
- Application state (`jotai`, `zustand`), data fetching (`ky`, `@tanstack/react-query`, `@orpc/*`), i18n (`next-i18next` / `react-i18next`), and routing (`next`) all live in `web/`. This package has zero dependencies on them and must stay that way so it can eventually be consumed by other apps or extracted.
|
|
- Business components (chat, workflow, dataset views, etc.). Those belong in `web/app/components/...`.
|
|
|
|
[Base UI Portal]: https://base-ui.com/react/overview/quick-start#portals
|
|
[Base UI]: https://base-ui.com/react
|
|
[Overlay & portal contract]: #overlay--portal-contract
|