feat(web): extract dify ui package

This commit is contained in:
yyh 2026-04-02 18:25:16 +08:00
parent a3386da5d6
commit 485586f49a
No known key found for this signature in database
50 changed files with 1220 additions and 890 deletions

2
packages/dify-ui/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
dist/
node_modules/

View File

@ -0,0 +1,78 @@
{
"name": "@langgenius/dify-ui",
"private": true,
"version": "0.0.0-private",
"type": "module",
"files": [
"dist"
],
"sideEffects": [
"**/*.css"
],
"exports": {
"./context-menu": {
"types": "./dist/context-menu/index.d.ts",
"import": "./dist/context-menu/index.js"
},
"./dropdown-menu": {
"types": "./dist/dropdown-menu/index.d.ts",
"import": "./dist/dropdown-menu/index.js"
},
"./tailwind-preset": {
"types": "./dist/tailwind-preset.d.ts",
"import": "./dist/tailwind-preset.js"
},
"./styles.css": "./dist/styles.css",
"./markdown.css": "./dist/markdown.css",
"./themes/light.css": "./dist/themes/light.css",
"./themes/dark.css": "./dist/themes/dark.css",
"./themes/manual-light.css": "./dist/themes/manual-light.css",
"./themes/manual-dark.css": "./dist/themes/manual-dark.css",
"./themes/markdown-light.css": "./dist/themes/markdown-light.css",
"./themes/markdown-dark.css": "./dist/themes/markdown-dark.css",
"./tokens/tailwind-theme-var-define": {
"types": "./dist/tokens/tailwind-theme-var-define.d.ts",
"import": "./dist/tokens/tailwind-theme-var-define.js"
},
"./package.json": "./package.json"
},
"scripts": {
"build": "node ./scripts/build.mjs",
"prepack": "pnpm build",
"test": "vp test",
"test:watch": "vp test --watch",
"type-check": "tsc -p tsconfig.json --noEmit"
},
"peerDependencies": {
"react": "catalog:",
"react-dom": "catalog:"
},
"dependencies": {
"@base-ui/react": "catalog:",
"@dify/iconify-collections": "workspace:*",
"@egoist/tailwindcss-icons": "catalog:",
"@iconify-json/heroicons": "catalog:",
"@iconify-json/ri": "catalog:",
"@remixicon/react": "catalog:",
"@tailwindcss/typography": "catalog:",
"clsx": "catalog:",
"tailwind-merge": "catalog:"
},
"devDependencies": {
"@storybook/react": "catalog:",
"@testing-library/jest-dom": "catalog:",
"@testing-library/react": "catalog:",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "catalog:",
"happy-dom": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"tailwindcss": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plus": "catalog:",
"vitest": "catalog:"
}
}

View File

@ -0,0 +1,31 @@
import { cp, mkdir, rm } from 'node:fs/promises'
import { spawnSync } from 'node:child_process'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const distDir = resolve(packageRoot, 'dist')
await rm(distDir, { recursive: true, force: true })
const tsc = spawnSync('pnpm', ['exec', 'tsc', '-p', 'tsconfig.build.json'], {
cwd: packageRoot,
stdio: 'inherit',
})
if (tsc.status !== 0)
process.exit(tsc.status ?? 1)
await mkdir(distDir, { recursive: true })
await cp(resolve(packageRoot, 'src/styles.css'), resolve(packageRoot, 'dist/styles.css'))
await cp(resolve(packageRoot, 'src/markdown.css'), resolve(packageRoot, 'dist/markdown.css'))
await cp(resolve(packageRoot, 'src/styles'), resolve(packageRoot, 'dist/styles'), {
force: true,
recursive: true,
})
await cp(resolve(packageRoot, 'src/themes'), resolve(packageRoot, 'dist/themes'), {
force: true,
recursive: true,
})

View File

@ -1,4 +1,10 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite' import type { Meta, StoryObj } from '@storybook/react'
import {
RiDeleteBinLine,
RiFileCopyLine,
RiPencilLine,
RiShareLine,
} from '@remixicon/react'
import { useState } from 'react' import { useState } from 'react'
import { import {
ContextMenu, ContextMenu,
@ -17,7 +23,7 @@ import {
ContextMenuSubContent, ContextMenuSubContent,
ContextMenuSubTrigger, ContextMenuSubTrigger,
ContextMenuTrigger, ContextMenuTrigger,
} from '.' } from './index'
const TriggerArea = ({ label = 'Right-click inside this area' }: { label?: string }) => ( const TriggerArea = ({ label = 'Right-click inside this area' }: { label?: string }) => (
<ContextMenuTrigger <ContextMenuTrigger
@ -185,17 +191,17 @@ export const Complex: Story = {
<TriggerArea label="Right-click to inspect all menu capabilities" /> <TriggerArea label="Right-click to inspect all menu capabilities" />
<ContextMenuContent> <ContextMenuContent>
<ContextMenuItem> <ContextMenuItem>
<span aria-hidden className="i-ri-pencil-line size-4 shrink-0 text-text-tertiary" /> <RiPencilLine aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Rename Rename
</ContextMenuItem> </ContextMenuItem>
<ContextMenuItem> <ContextMenuItem>
<span aria-hidden className="i-ri-file-copy-line size-4 shrink-0 text-text-tertiary" /> <RiFileCopyLine aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Duplicate Duplicate
</ContextMenuItem> </ContextMenuItem>
<ContextMenuSeparator /> <ContextMenuSeparator />
<ContextMenuSub> <ContextMenuSub>
<ContextMenuSubTrigger> <ContextMenuSubTrigger>
<span aria-hidden className="i-ri-share-line size-4 shrink-0 text-text-tertiary" /> <RiShareLine aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Share Share
</ContextMenuSubTrigger> </ContextMenuSubTrigger>
<ContextMenuSubContent> <ContextMenuSubContent>
@ -206,7 +212,7 @@ export const Complex: Story = {
</ContextMenuSub> </ContextMenuSub>
<ContextMenuSeparator /> <ContextMenuSeparator />
<ContextMenuItem destructive> <ContextMenuItem destructive>
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" /> <RiDeleteBinLine aria-hidden className="size-4 shrink-0" />
Delete Delete
</ContextMenuItem> </ContextMenuItem>
</ContextMenuContent> </ContextMenuContent>

View File

@ -1,8 +1,10 @@
'use client' 'use client'
import type { Placement } from '@/app/components/base/ui/placement' import type { Placement } from '../internal/placement.js'
import { ContextMenu as BaseContextMenu } from '@base-ui/react/context-menu' import { ContextMenu as BaseContextMenu } from '@base-ui/react/context-menu'
import { RiArrowRightSLine, RiCheckLine } from '@remixicon/react'
import * as React from 'react' import * as React from 'react'
import { cn } from '../internal/cn.js'
import { import {
menuBackdropClassName, menuBackdropClassName,
menuGroupLabelClassName, menuGroupLabelClassName,
@ -11,9 +13,8 @@ import {
menuPopupBaseClassName, menuPopupBaseClassName,
menuRowClassName, menuRowClassName,
menuSeparatorClassName, menuSeparatorClassName,
} from '@/app/components/base/ui/menu-shared' } from '../internal/menu-shared.js'
import { parsePlacement } from '@/app/components/base/ui/placement' import { parsePlacement } from '../internal/placement.js'
import { cn } from '@/utils/classnames'
export const ContextMenu = BaseContextMenu.Root export const ContextMenu = BaseContextMenu.Root
export const ContextMenuTrigger = BaseContextMenu.Trigger export const ContextMenuTrigger = BaseContextMenu.Trigger
@ -44,11 +45,11 @@ type ContextMenuPopupRenderProps = Required<Pick<ContextMenuContentProps, 'child
placement: Placement placement: Placement
sideOffset: number sideOffset: number
alignOffset: number alignOffset: number
className?: string className?: string | undefined
popupClassName?: string popupClassName?: string | undefined
positionerProps?: ContextMenuContentProps['positionerProps'] positionerProps?: ContextMenuContentProps['positionerProps'] | undefined
popupProps?: ContextMenuContentProps['popupProps'] popupProps?: ContextMenuContentProps['popupProps'] | undefined
withBackdrop?: boolean withBackdrop?: boolean | undefined
} }
function renderContextMenuPopup({ function renderContextMenuPopup({
@ -190,7 +191,7 @@ export function ContextMenuItemIndicator({
className={cn(menuIndicatorClassName, className)} className={cn(menuIndicatorClassName, className)}
{...props} {...props}
> >
{children ?? <span aria-hidden className="i-ri-check-line h-4 w-4" />} {children ?? <RiCheckLine aria-hidden className="h-4 w-4" />}
</span> </span>
) )
} }
@ -204,7 +205,7 @@ export function ContextMenuCheckboxItemIndicator({
className={cn(menuIndicatorClassName, className)} className={cn(menuIndicatorClassName, className)}
{...props} {...props}
> >
<span aria-hidden className="i-ri-check-line h-4 w-4" /> <RiCheckLine aria-hidden className="h-4 w-4" />
</BaseContextMenu.CheckboxItemIndicator> </BaseContextMenu.CheckboxItemIndicator>
) )
} }
@ -218,7 +219,7 @@ export function ContextMenuRadioItemIndicator({
className={cn(menuIndicatorClassName, className)} className={cn(menuIndicatorClassName, className)}
{...props} {...props}
> >
<span aria-hidden className="i-ri-check-line h-4 w-4" /> <RiCheckLine aria-hidden className="h-4 w-4" />
</BaseContextMenu.RadioItemIndicator> </BaseContextMenu.RadioItemIndicator>
) )
} }
@ -239,20 +240,20 @@ export function ContextMenuSubTrigger({
{...props} {...props}
> >
{children} {children}
<span aria-hidden className="i-ri-arrow-right-s-line ml-auto size-4 shrink-0 text-text-tertiary" /> <RiArrowRightSLine aria-hidden className="ml-auto size-4 shrink-0 text-text-tertiary" />
</BaseContextMenu.SubmenuTrigger> </BaseContextMenu.SubmenuTrigger>
) )
} }
type ContextMenuSubContentProps = { type ContextMenuSubContentProps = {
children: React.ReactNode children: React.ReactNode
placement?: Placement placement?: Placement | undefined
sideOffset?: number sideOffset?: number | undefined
alignOffset?: number alignOffset?: number | undefined
className?: string className?: string | undefined
popupClassName?: string popupClassName?: string | undefined
positionerProps?: ContextMenuContentProps['positionerProps'] positionerProps?: ContextMenuContentProps['positionerProps'] | undefined
popupProps?: ContextMenuContentProps['popupProps'] popupProps?: ContextMenuContentProps['popupProps'] | undefined
} }
export function ContextMenuSubContent({ export function ContextMenuSubContent({
@ -300,3 +301,5 @@ export function ContextMenuSeparator({
/> />
) )
} }
export type { Placement }

View File

@ -1,7 +1,6 @@
import type { ComponentPropsWithoutRef, ReactNode } from 'react' import type { ComponentPropsWithoutRef, ReactNode } from 'react'
import { fireEvent, render, screen, within } from '@testing-library/react' import { fireEvent, render, screen, within } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest' import { describe, expect, it, vi } from 'vitest'
import Link from '@/next/link'
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@ -14,20 +13,20 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from '../index' } from '../index'
vi.mock('@/next/link', () => ({ function MockLink({
default: ({ href,
href, children,
children, ...props
...props }: {
}: { href: string
href: string children?: ReactNode
children?: ReactNode } & Omit<ComponentPropsWithoutRef<'a'>, 'href'>) {
} & Omit<ComponentPropsWithoutRef<'a'>, 'href'>) => ( return (
<a href={href} {...props}> <a href={href} {...props}>
{children} {children}
</a> </a>
), )
})) }
describe('dropdown-menu wrapper', () => { describe('dropdown-menu wrapper', () => {
describe('DropdownMenuContent', () => { describe('DropdownMenuContent', () => {
@ -301,7 +300,7 @@ describe('dropdown-menu wrapper', () => {
<DropdownMenuTrigger aria-label="menu trigger">Open</DropdownMenuTrigger> <DropdownMenuTrigger aria-label="menu trigger">Open</DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuLinkItem <DropdownMenuLinkItem
render={<Link href="/account" />} render={<MockLink href="/account" />}
aria-label="account link" aria-label="account link"
> >
Account settings Account settings

View File

@ -1,4 +1,15 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite' import type { Meta, StoryObj } from '@storybook/react'
import {
RiArchiveLine,
RiChat1Line,
RiDeleteBinLine,
RiFileCopyLine,
RiLink,
RiLockLine,
RiMailLine,
RiPencilLine,
RiShareLine,
} from '@remixicon/react'
import { useState } from 'react' import { useState } from 'react'
import { import {
DropdownMenu, DropdownMenu,
@ -17,7 +28,7 @@ import {
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '.' } from './index'
const TriggerButton = ({ label = 'Open Menu' }: { label?: string }) => ( const TriggerButton = ({ label = 'Open Menu' }: { label?: string }) => (
<DropdownMenuTrigger <DropdownMenuTrigger
@ -214,20 +225,20 @@ export const WithIcons: Story = {
<TriggerButton /> <TriggerButton />
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuItem> <DropdownMenuItem>
<span aria-hidden className="i-ri-pencil-line size-4 shrink-0 text-text-tertiary" /> <RiPencilLine aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem> <DropdownMenuItem>
<span aria-hidden className="i-ri-file-copy-line size-4 shrink-0 text-text-tertiary" /> <RiFileCopyLine aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Duplicate Duplicate
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem> <DropdownMenuItem>
<span aria-hidden className="i-ri-archive-line size-4 shrink-0 text-text-tertiary" /> <RiArchiveLine aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Archive Archive
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem destructive> <DropdownMenuItem destructive>
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" /> <RiDeleteBinLine aria-hidden className="size-4 shrink-0" />
Delete Delete
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
@ -262,35 +273,35 @@ const ComplexDemo = () => {
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuGroupLabel>Edit</DropdownMenuGroupLabel> <DropdownMenuGroupLabel>Edit</DropdownMenuGroupLabel>
<DropdownMenuItem> <DropdownMenuItem>
<span aria-hidden className="i-ri-pencil-line size-4 shrink-0 text-text-tertiary" /> <RiPencilLine aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Rename Rename
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem> <DropdownMenuItem>
<span aria-hidden className="i-ri-file-copy-line size-4 shrink-0 text-text-tertiary" /> <RiFileCopyLine aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Duplicate Duplicate
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem disabled> <DropdownMenuItem disabled>
<span aria-hidden className="i-ri-lock-line size-4 shrink-0 text-text-tertiary" /> <RiLockLine aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Move to Workspace Move to Workspace
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuSub> <DropdownMenuSub>
<DropdownMenuSubTrigger> <DropdownMenuSubTrigger>
<span aria-hidden className="i-ri-share-line size-4 shrink-0 text-text-tertiary" /> <RiShareLine aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Share Share
</DropdownMenuSubTrigger> </DropdownMenuSubTrigger>
<DropdownMenuSubContent> <DropdownMenuSubContent>
<DropdownMenuItem> <DropdownMenuItem>
<span aria-hidden className="i-ri-mail-line size-4 shrink-0 text-text-tertiary" /> <RiMailLine aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Email Email
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem> <DropdownMenuItem>
<span aria-hidden className="i-ri-chat-1-line size-4 shrink-0 text-text-tertiary" /> <RiChat1Line aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Slack Slack
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem> <DropdownMenuItem>
<span aria-hidden className="i-ri-link size-4 shrink-0 text-text-tertiary" /> <RiLink aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Copy Link Copy Link
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuSubContent> </DropdownMenuSubContent>
@ -315,13 +326,13 @@ const ComplexDemo = () => {
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuCheckboxItem checked={showArchived} onCheckedChange={setShowArchived}> <DropdownMenuCheckboxItem checked={showArchived} onCheckedChange={setShowArchived}>
<span aria-hidden className="i-ri-archive-line size-4 shrink-0 text-text-tertiary" /> <RiArchiveLine aria-hidden className="size-4 shrink-0 text-text-tertiary" />
Show Archived Show Archived
<DropdownMenuCheckboxItemIndicator /> <DropdownMenuCheckboxItemIndicator />
</DropdownMenuCheckboxItem> </DropdownMenuCheckboxItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem destructive> <DropdownMenuItem destructive>
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" /> <RiDeleteBinLine aria-hidden className="size-4 shrink-0" />
Delete Delete
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View File

@ -1,8 +1,10 @@
'use client' 'use client'
import type { Placement } from '@/app/components/base/ui/placement' import type { Placement } from '../internal/placement.js'
import { Menu } from '@base-ui/react/menu' import { Menu } from '@base-ui/react/menu'
import { RiArrowRightSLine, RiCheckLine } from '@remixicon/react'
import * as React from 'react' import * as React from 'react'
import { cn } from '../internal/cn.js'
import { import {
menuGroupLabelClassName, menuGroupLabelClassName,
menuIndicatorClassName, menuIndicatorClassName,
@ -10,9 +12,8 @@ import {
menuPopupBaseClassName, menuPopupBaseClassName,
menuRowClassName, menuRowClassName,
menuSeparatorClassName, menuSeparatorClassName,
} from '@/app/components/base/ui/menu-shared' } from '../internal/menu-shared.js'
import { parsePlacement } from '@/app/components/base/ui/placement' import { parsePlacement } from '../internal/placement.js'
import { cn } from '@/utils/classnames'
export const DropdownMenu = Menu.Root export const DropdownMenu = Menu.Root
export const DropdownMenuPortal = Menu.Portal export const DropdownMenuPortal = Menu.Portal
@ -42,7 +43,7 @@ export function DropdownMenuRadioItemIndicator({
className={cn(menuIndicatorClassName, className)} className={cn(menuIndicatorClassName, className)}
{...props} {...props}
> >
<span aria-hidden className="i-ri-check-line h-4 w-4" /> <RiCheckLine aria-hidden className="h-4 w-4" />
</Menu.RadioItemIndicator> </Menu.RadioItemIndicator>
) )
} }
@ -68,7 +69,7 @@ export function DropdownMenuCheckboxItemIndicator({
className={cn(menuIndicatorClassName, className)} className={cn(menuIndicatorClassName, className)}
{...props} {...props}
> >
<span aria-hidden className="i-ri-check-line h-4 w-4" /> <RiCheckLine aria-hidden className="h-4 w-4" />
</Menu.CheckboxItemIndicator> </Menu.CheckboxItemIndicator>
) )
} }
@ -106,10 +107,10 @@ type DropdownMenuPopupRenderProps = Required<Pick<DropdownMenuContentProps, 'chi
placement: Placement placement: Placement
sideOffset: number sideOffset: number
alignOffset: number alignOffset: number
className?: string className?: string | undefined
popupClassName?: string popupClassName?: string | undefined
positionerProps?: DropdownMenuContentProps['positionerProps'] positionerProps?: DropdownMenuContentProps['positionerProps'] | undefined
popupProps?: DropdownMenuContentProps['popupProps'] popupProps?: DropdownMenuContentProps['popupProps'] | undefined
} }
function renderDropdownMenuPopup({ function renderDropdownMenuPopup({
@ -187,20 +188,20 @@ export function DropdownMenuSubTrigger({
{...props} {...props}
> >
{children} {children}
<span aria-hidden className="i-ri-arrow-right-s-line ml-auto size-4 shrink-0 text-text-tertiary" /> <RiArrowRightSLine aria-hidden className="ml-auto size-4 shrink-0 text-text-tertiary" />
</Menu.SubmenuTrigger> </Menu.SubmenuTrigger>
) )
} }
type DropdownMenuSubContentProps = { type DropdownMenuSubContentProps = {
children: React.ReactNode children: React.ReactNode
placement?: Placement placement?: Placement | undefined
sideOffset?: number sideOffset?: number | undefined
alignOffset?: number alignOffset?: number | undefined
className?: string className?: string | undefined
popupClassName?: string popupClassName?: string | undefined
positionerProps?: DropdownMenuContentProps['positionerProps'] positionerProps?: DropdownMenuContentProps['positionerProps'] | undefined
popupProps?: DropdownMenuContentProps['popupProps'] popupProps?: DropdownMenuContentProps['popupProps'] | undefined
} }
export function DropdownMenuSubContent({ export function DropdownMenuSubContent({
@ -272,3 +273,5 @@ export function DropdownMenuSeparator({
/> />
) )
} }
export type { Placement }

View File

@ -0,0 +1,7 @@
import type { ClassValue } from 'clsx'
import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@ -0,0 +1,7 @@
export const menuRowClassName = 'mx-1 flex h-8 cursor-pointer select-none items-center gap-1 rounded-lg px-2 outline-hidden data-highlighted:bg-state-base-hover data-disabled:cursor-not-allowed data-disabled:opacity-30'
export const menuIndicatorClassName = 'ml-auto flex shrink-0 items-center text-text-accent'
export const menuGroupLabelClassName = 'px-3 pb-0.5 pt-1 text-text-tertiary system-xs-medium-uppercase'
export const menuSeparatorClassName = 'my-1 h-px bg-divider-subtle'
export const menuPopupBaseClassName = 'max-h-(--available-height) overflow-y-auto overflow-x-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur py-1 text-sm text-text-secondary shadow-lg outline-hidden focus:outline-hidden focus-visible:outline-hidden backdrop-blur-[5px]'
export const menuPopupAnimationClassName = 'origin-(--transform-origin) transition-[transform,scale,opacity] data-ending-style:scale-95 data-starting-style:scale-95 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none'
export const menuBackdropClassName = 'fixed inset-0 z-1002 bg-transparent transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none'

View File

@ -0,0 +1,25 @@
type Side = 'top' | 'bottom' | 'left' | 'right'
type Align = 'start' | 'center' | 'end'
export type Placement
= 'top'
| 'top-start'
| 'top-end'
| 'right'
| 'right-start'
| 'right-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
export function parsePlacement(placement: Placement): { side: Side, align: Align } {
const [side, align] = placement.split('-') as [Side, Align | undefined]
return {
side,
align: align ?? 'center',
}
}

View File

@ -0,0 +1,2 @@
@import './themes/markdown-light.css';
@import './themes/markdown-dark.css';

View File

@ -0,0 +1,7 @@
@import './themes/light.css' layer(base);
@import './themes/dark.css' layer(base);
@import './themes/manual-light.css' layer(base);
@import './themes/manual-dark.css' layer(base);
@import './styles/tokens.css';
@source './**/*.{js,mjs}';

View File

@ -0,0 +1,713 @@
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
@utility system-kbd {
/* font define start */
font-size: 12px;
font-weight: 500;
line-height: 16px;
/* border radius end */
}
@utility system-2xs-regular-uppercase {
font-size: 10px;
font-weight: 400;
text-transform: uppercase;
line-height: 12px;
/* border radius end */
}
@utility system-2xs-regular {
font-size: 10px;
font-weight: 400;
line-height: 12px;
/* border radius end */
}
@utility system-2xs-medium {
font-size: 10px;
font-weight: 500;
line-height: 12px;
/* border radius end */
}
@utility system-2xs-medium-uppercase {
font-size: 10px;
font-weight: 500;
text-transform: uppercase;
line-height: 12px;
/* border radius end */
}
@utility system-2xs-semibold-uppercase {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
line-height: 12px;
/* border radius end */
}
@utility system-xs-regular {
font-size: 12px;
font-weight: 400;
line-height: 16px;
/* border radius end */
}
@utility system-xs-regular-uppercase {
font-size: 12px;
font-weight: 400;
text-transform: uppercase;
line-height: 16px;
/* border radius end */
}
@utility system-xs-medium {
font-size: 12px;
font-weight: 500;
line-height: 16px;
/* border radius end */
}
@utility system-xs-medium-uppercase {
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
line-height: 16px;
/* border radius end */
}
@utility system-xs-semibold {
font-size: 12px;
font-weight: 600;
line-height: 16px;
/* border radius end */
}
@utility system-xs-semibold-uppercase {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
line-height: 16px;
/* border radius end */
}
@utility system-sm-regular {
font-size: 13px;
font-weight: 400;
line-height: 16px;
/* border radius end */
}
@utility system-sm-medium {
font-size: 13px;
font-weight: 500;
line-height: 16px;
/* border radius end */
}
@utility system-sm-medium-uppercase {
font-size: 13px;
font-weight: 500;
text-transform: uppercase;
line-height: 16px;
/* border radius end */
}
@utility system-sm-semibold {
font-size: 13px;
font-weight: 600;
line-height: 16px;
/* border radius end */
}
@utility system-sm-semibold-uppercase {
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
line-height: 16px;
/* border radius end */
}
@utility system-md-regular {
font-size: 14px;
font-weight: 400;
line-height: 20px;
/* border radius end */
}
@utility system-md-medium {
font-size: 14px;
font-weight: 500;
line-height: 20px;
/* border radius end */
}
@utility system-md-semibold {
font-size: 14px;
font-weight: 600;
line-height: 20px;
/* border radius end */
}
@utility system-md-semibold-uppercase {
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
line-height: 20px;
/* border radius end */
}
@utility system-xl-regular {
font-size: 16px;
font-weight: 400;
line-height: 24px;
/* border radius end */
}
@utility system-xl-medium {
font-size: 16px;
font-weight: 500;
line-height: 24px;
/* border radius end */
}
@utility system-xl-semibold {
font-size: 16px;
font-weight: 600;
line-height: 24px;
/* border radius end */
}
@utility code-xs-regular {
font-size: 12px;
font-weight: 400;
line-height: 1.5;
/* border radius end */
}
@utility code-xs-semibold {
font-size: 12px;
font-weight: 600;
line-height: 1.5;
/* border radius end */
}
@utility code-sm-regular {
font-size: 13px;
font-weight: 400;
line-height: 1.5;
/* border radius end */
}
@utility code-sm-semibold {
font-size: 13px;
font-weight: 600;
line-height: 1.5;
/* border radius end */
}
@utility code-md-regular {
font-size: 14px;
font-weight: 400;
line-height: 1.5;
/* border radius end */
}
@utility code-md-semibold {
font-size: 14px;
font-weight: 600;
line-height: 1.5;
/* border radius end */
}
@utility body-xs-light {
font-size: 12px;
font-weight: 300;
line-height: 16px;
/* border radius end */
}
@utility body-xs-regular {
font-size: 12px;
font-weight: 400;
line-height: 16px;
/* border radius end */
}
@utility body-xs-medium {
font-size: 12px;
font-weight: 500;
line-height: 16px;
/* border radius end */
}
@utility body-sm-light {
font-size: 13px;
font-weight: 300;
line-height: 16px;
/* border radius end */
}
@utility body-sm-regular {
font-size: 13px;
font-weight: 400;
line-height: 16px;
/* border radius end */
}
@utility body-sm-medium {
font-size: 13px;
font-weight: 500;
line-height: 16px;
/* border radius end */
}
@utility body-md-light {
font-size: 14px;
font-weight: 300;
line-height: 20px;
/* border radius end */
}
@utility body-md-regular {
font-size: 14px;
font-weight: 400;
line-height: 20px;
/* border radius end */
}
@utility body-md-medium {
font-size: 14px;
font-weight: 500;
line-height: 20px;
/* border radius end */
}
@utility body-lg-light {
font-size: 15px;
font-weight: 300;
line-height: 20px;
/* border radius end */
}
@utility body-lg-regular {
font-size: 15px;
font-weight: 400;
line-height: 20px;
/* border radius end */
}
@utility body-lg-medium {
font-size: 15px;
font-weight: 500;
line-height: 20px;
/* border radius end */
}
@utility body-xl-regular {
font-size: 16px;
font-weight: 400;
line-height: 24px;
/* border radius end */
}
@utility body-xl-medium {
font-size: 16px;
font-weight: 500;
line-height: 24px;
/* border radius end */
}
@utility body-xl-light {
font-size: 16px;
font-weight: 300;
line-height: 24px;
/* border radius end */
}
@utility body-2xl-light {
font-size: 18px;
font-weight: 300;
line-height: 1.5;
/* border radius end */
}
@utility body-2xl-regular {
font-size: 18px;
font-weight: 400;
line-height: 1.5;
/* border radius end */
}
@utility body-2xl-medium {
font-size: 18px;
font-weight: 500;
line-height: 1.5;
/* border radius end */
}
@utility title-xs-semi-bold {
font-size: 12px;
font-weight: 600;
line-height: 16px;
/* border radius end */
}
@utility title-xs-bold {
font-size: 12px;
font-weight: 700;
line-height: 16px;
/* border radius end */
}
@utility title-sm-semi-bold {
font-size: 13px;
font-weight: 600;
line-height: 16px;
/* border radius end */
}
@utility title-sm-bold {
font-size: 13px;
font-weight: 700;
line-height: 16px;
/* border radius end */
}
@utility title-md-semi-bold {
font-size: 14px;
font-weight: 600;
line-height: 20px;
/* border radius end */
}
@utility title-md-bold {
font-size: 14px;
font-weight: 700;
line-height: 20px;
/* border radius end */
}
@utility title-lg-semi-bold {
font-size: 15px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-lg-bold {
font-size: 15px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-xl-semi-bold {
font-size: 16px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-xl-bold {
font-size: 16px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-2xl-semi-bold {
font-size: 18px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-2xl-bold {
font-size: 18px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-3xl-semi-bold {
font-size: 20px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-3xl-bold {
font-size: 20px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-4xl-semi-bold {
font-size: 24px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-4xl-bold {
font-size: 24px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-5xl-semi-bold {
font-size: 30px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-5xl-bold {
font-size: 30px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-6xl-semi-bold {
font-size: 36px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-6xl-bold {
font-size: 36px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-7xl-semi-bold {
font-size: 48px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-7xl-bold {
font-size: 48px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-8xl-semi-bold {
font-size: 60px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-8xl-bold {
font-size: 60px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility radius-2xs {
/* font define end */
/* border radius start */
border-radius: 2px;
/* border radius end */
}
@utility radius-xs {
border-radius: 4px;
/* border radius end */
}
@utility radius-sm {
border-radius: 6px;
/* border radius end */
}
@utility radius-md {
border-radius: 8px;
/* border radius end */
}
@utility radius-lg {
border-radius: 10px;
/* border radius end */
}
@utility radius-xl {
border-radius: 12px;
/* border radius end */
}
@utility radius-2xl {
border-radius: 16px;
/* border radius end */
}
@utility radius-3xl {
border-radius: 20px;
/* border radius end */
}
@utility radius-4xl {
border-radius: 24px;
/* border radius end */
}
@utility radius-5xl {
border-radius: 24px;
/* border radius end */
}
@utility radius-6xl {
border-radius: 28px;
/* border radius end */
}
@utility radius-7xl {
border-radius: 32px;
/* border radius end */
}
@utility radius-8xl {
border-radius: 40px;
/* border radius end */
}
@utility radius-9xl {
border-radius: 48px;
/* border radius end */
}
@utility radius-full {
border-radius: 64px;
/* border radius end */
}
@utility no-scrollbar {
/* Hide scrollbar for Chrome, Safari and Opera */
&::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
-ms-overflow-style: none;
scrollbar-width: none;
}
@utility no-spinner {
/* Hide arrows from number input */
&::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
-moz-appearance: textfield;
}

View File

@ -1,8 +1,10 @@
import { icons as customPublicIcons } from '@dify/iconify-collections/custom-public' import { icons as customPublicIcons } from '@dify/iconify-collections/custom-public'
import { icons as customVenderIcons } from '@dify/iconify-collections/custom-vender' import { icons as customVenderIcons } from '@dify/iconify-collections/custom-vender'
import { getIconCollections, iconsPlugin } from '@egoist/tailwindcss-icons' import { icons as heroicons } from '@iconify-json/heroicons'
import { icons as remixIcons } from '@iconify-json/ri'
import { iconsPlugin } from '@egoist/tailwindcss-icons'
import tailwindTypography from '@tailwindcss/typography' import tailwindTypography from '@tailwindcss/typography'
import tailwindThemeVarDefine from './themes/tailwind-theme-var-define' import tailwindThemeVarDefine from './tokens/tailwind-theme-var-define.js'
import typography from './typography.js' import typography from './typography.js'
const config = { const config = {
@ -151,7 +153,8 @@ const config = {
tailwindTypography, tailwindTypography,
iconsPlugin({ iconsPlugin({
collections: { collections: {
...getIconCollections(['heroicons', 'ri']), heroicons,
ri: remixIcons,
'custom-public': customPublicIcons, 'custom-public': customPublicIcons,
'custom-vender': customVenderIcons, 'custom-vender': customVenderIcons,
}, },

3
packages/dify-ui/src/typography.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare const typography: (helpers: { theme: (path: string) => unknown }) => Record<string, unknown>
export default typography

View File

@ -0,0 +1,8 @@
import difyUiTailwindPreset from './src/tailwind-preset'
const config = {
content: [],
...difyUiTailwindPreset,
}
export default config

View File

@ -0,0 +1,21 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"allowJs": true,
"noEmit": false,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.js"
],
"exclude": [
"src/**/*.stories.tsx",
"src/**/__tests__/**"
]
}

View File

@ -0,0 +1,38 @@
{
"compilerOptions": {
"target": "es2022",
"jsx": "react-jsx",
"lib": [
"dom",
"dom.iterable",
"es2022"
],
"module": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"resolveJsonModule": true,
"allowJs": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noEmit": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"types": [
"node",
"vitest/globals",
"@testing-library/jest-dom"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.js",
"scripts/**/*.mjs",
"vite.config.ts",
"vitest.setup.ts"
]
}

View File

@ -0,0 +1,11 @@
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite-plus'
export default defineConfig({
plugins: [react()],
test: {
environment: 'happy-dom',
globals: true,
setupFiles: ['./vitest.setup.ts'],
},
})

View File

@ -0,0 +1,39 @@
import { cleanup } from '@testing-library/react'
import '@testing-library/jest-dom/vitest'
import { afterEach } from 'vitest'
if (typeof globalThis.ResizeObserver === 'undefined') {
globalThis.ResizeObserver = class {
observe() {
return undefined
}
unobserve() {
return undefined
}
disconnect() {
return undefined
}
}
}
if (typeof globalThis.IntersectionObserver === 'undefined') {
globalThis.IntersectionObserver = class {
readonly root: Element | Document | null = null
readonly rootMargin = ''
readonly scrollMargin = ''
readonly thresholds: ReadonlyArray<number> = []
constructor(_callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) {}
observe(_target: Element) {}
unobserve(_target: Element) {}
disconnect() {}
takeRecords(): IntersectionObserverEntry[] {
return []
}
}
}
afterEach(() => {
cleanup()
})

79
pnpm-lock.yaml generated
View File

@ -640,6 +640,82 @@ importers:
specifier: 'catalog:' specifier: 'catalog:'
version: 0.1.14(@types/node@25.5.0)(esbuild@0.27.2)(happy-dom@20.8.9)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.2)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) version: 0.1.14(@types/node@25.5.0)(esbuild@0.27.2)(happy-dom@20.8.9)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.2)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3)
packages/dify-ui:
dependencies:
'@base-ui/react':
specifier: 'catalog:'
version: 1.3.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@dify/iconify-collections':
specifier: workspace:*
version: link:../iconify-collections
'@egoist/tailwindcss-icons':
specifier: 'catalog:'
version: 1.9.2(tailwindcss@4.2.2)
'@iconify-json/heroicons':
specifier: 'catalog:'
version: 1.2.3
'@iconify-json/ri':
specifier: 'catalog:'
version: 1.2.10
'@remixicon/react':
specifier: 'catalog:'
version: 4.9.0(react@19.2.4)
'@tailwindcss/typography':
specifier: 'catalog:'
version: 0.5.19(tailwindcss@4.2.2)
clsx:
specifier: 'catalog:'
version: 2.1.1
tailwind-merge:
specifier: 'catalog:'
version: 3.5.0
devDependencies:
'@storybook/react':
specifier: 'catalog:'
version: 10.3.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(storybook@10.3.3(@testing-library/dom@10.4.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)
'@testing-library/jest-dom':
specifier: 'catalog:'
version: 6.9.1
'@testing-library/react':
specifier: 'catalog:'
version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@types/node':
specifier: 'catalog:'
version: 25.5.0
'@types/react':
specifier: 'catalog:'
version: 19.2.14
'@types/react-dom':
specifier: 'catalog:'
version: 19.2.3(@types/react@19.2.14)
'@vitejs/plugin-react':
specifier: 'catalog:'
version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.14(@types/node@25.5.0)(esbuild@0.27.2)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))
happy-dom:
specifier: 'catalog:'
version: 20.8.9
react:
specifier: 'catalog:'
version: 19.2.4
react-dom:
specifier: 'catalog:'
version: 19.2.4(react@19.2.4)
tailwindcss:
specifier: 'catalog:'
version: 4.2.2
typescript:
specifier: 'catalog:'
version: 5.9.3
vite:
specifier: npm:@voidzero-dev/vite-plus-core@0.1.14
version: '@voidzero-dev/vite-plus-core@0.1.14(@types/node@25.5.0)(esbuild@0.27.2)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)'
vite-plus:
specifier: 'catalog:'
version: 0.1.14(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.14(@types/node@25.5.0)(esbuild@0.27.2)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.9)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
vitest:
specifier: npm:@voidzero-dev/vite-plus-test@0.1.14
version: '@voidzero-dev/vite-plus-test@0.1.14(@types/node@25.5.0)(@voidzero-dev/vite-plus-core@0.1.14(@types/node@25.5.0)(esbuild@0.27.2)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.2)(happy-dom@20.8.9)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)'
packages/iconify-collections: packages/iconify-collections:
devDependencies: devDependencies:
iconify-import-svg: iconify-import-svg:
@ -702,6 +778,9 @@ importers:
'@heroicons/react': '@heroicons/react':
specifier: 'catalog:' specifier: 'catalog:'
version: 2.2.0(react@19.2.4) version: 2.2.0(react@19.2.4)
'@langgenius/dify-ui':
specifier: workspace:*
version: link:../packages/dify-ui
'@lexical/code': '@lexical/code':
specifier: npm:lexical-code-no-prism@0.41.0 specifier: npm:lexical-code-no-prism@0.41.0
version: lexical-code-no-prism@0.41.0(@lexical/utils@0.42.0)(lexical@0.42.0) version: lexical-code-no-prism@0.41.0(@lexical/utils@0.42.0)(lexical@0.42.0)

View File

@ -1,7 +1,10 @@
import type { StorybookConfig } from '@storybook/nextjs-vite' import type { StorybookConfig } from '@storybook/nextjs-vite'
const config: StorybookConfig = { const config: StorybookConfig = {
stories: ['../app/components/**/*.stories.@(js|jsx|mjs|ts|tsx)'], stories: [
'../app/components/**/*.stories.@(js|jsx|mjs|ts|tsx)',
'../../packages/dify-ui/src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
],
addons: [ addons: [
// Not working with Storybook Vite framework // Not working with Storybook Vite framework
// '@storybook/addon-onboarding', // '@storybook/addon-onboarding',

View File

@ -6,7 +6,7 @@
* *
* Migration guide: * Migration guide:
* - Tooltip `@/app/components/base/ui/tooltip` * - Tooltip `@/app/components/base/ui/tooltip`
* - Menu/Dropdown `@/app/components/base/ui/dropdown-menu` * - Menu/Dropdown `@langgenius/dify-ui/dropdown-menu`
* - Popover `@/app/components/base/ui/popover` * - Popover `@/app/components/base/ui/popover`
* - Dialog/Modal `@/app/components/base/ui/dialog` * - Dialog/Modal `@/app/components/base/ui/dialog`
* - Select `@/app/components/base/ui/select` * - Select `@/app/components/base/ui/select`

View File

@ -1,7 +1,7 @@
import type { ModalContextState } from '@/context/modal-context' import type { ModalContextState } from '@/context/modal-context'
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@langgenius/dify-ui/dropdown-menu'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/app/components/base/ui/dropdown-menu'
import { toast } from '@/app/components/base/ui/toast' import { toast } from '@/app/components/base/ui/toast'
import { Plan } from '@/app/components/billing/type' import { Plan } from '@/app/components/billing/type'
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'

View File

@ -1,7 +1,7 @@
import type { AppContextValue } from '@/context/app-context' import type { AppContextValue } from '@/context/app-context'
import { fireEvent, render, screen } from '@testing-library/react' import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@langgenius/dify-ui/dropdown-menu'
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/app/components/base/ui/dropdown-menu' import { fireEvent, render, screen } from '@testing-library/react'
import { Plan } from '@/app/components/billing/type' import { Plan } from '@/app/components/billing/type'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'
import { baseProviderContextValue, useProviderContext } from '@/context/provider-context' import { baseProviderContextValue, useProviderContext } from '@/context/provider-context'

View File

@ -1,8 +1,8 @@
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@langgenius/dify-ui/dropdown-menu'
import { useMutation } from '@tanstack/react-query' import { useMutation } from '@tanstack/react-query'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@/app/components/base/ui/dropdown-menu'
import { toast } from '@/app/components/base/ui/toast' import { toast } from '@/app/components/base/ui/toast'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { Plan } from '@/app/components/billing/type' import { Plan } from '@/app/components/billing/type'

View File

@ -1,13 +1,13 @@
'use client' 'use client'
import type { MouseEventHandler, ReactNode } from 'react' import type { MouseEventHandler, ReactNode } from 'react'
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLinkItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@langgenius/dify-ui/dropdown-menu'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { resetUser } from '@/app/components/base/amplitude/utils' import { resetUser } from '@/app/components/base/amplitude/utils'
import { Avatar } from '@/app/components/base/avatar' import { Avatar } from '@/app/components/base/avatar'
import PremiumBadge from '@/app/components/base/premium-badge' import PremiumBadge from '@/app/components/base/premium-badge'
import ThemeSwitcher from '@/app/components/base/theme-switcher' import ThemeSwitcher from '@/app/components/base/theme-switcher'
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLinkItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/app/components/base/ui/dropdown-menu'
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
import { IS_CLOUD_EDITION } from '@/config' import { IS_CLOUD_EDITION } from '@/config'
import { useAppContext } from '@/context/app-context' import { useAppContext } from '@/context/app-context'

View File

@ -1,5 +1,5 @@
import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuLinkItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@langgenius/dify-ui/dropdown-menu'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuLinkItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@/app/components/base/ui/dropdown-menu'
import { toggleZendeskWindow } from '@/app/components/base/zendesk/utils' import { toggleZendeskWindow } from '@/app/components/base/zendesk/utils'
import { Plan } from '@/app/components/billing/type' import { Plan } from '@/app/components/billing/type'
import { SUPPORT_EMAIL_ADDRESS, ZENDESK_WIDGET_KEY } from '@/config' import { SUPPORT_EMAIL_ADDRESS, ZENDESK_WIDGET_KEY } from '@/config'

View File

@ -1,15 +1,10 @@
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@langgenius/dify-ui/dropdown-menu'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { Brush01 } from '@/app/components/base/icons/src/vender/solid/editor' import { Brush01 } from '@/app/components/base/icons/src/vender/solid/editor'
import { Scales02 } from '@/app/components/base/icons/src/vender/solid/FinanceAndECommerce' import { Scales02 } from '@/app/components/base/icons/src/vender/solid/FinanceAndECommerce'
import { Target04 } from '@/app/components/base/icons/src/vender/solid/general' import { Target04 } from '@/app/components/base/icons/src/vender/solid/general'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/app/components/base/ui/dropdown-menu'
import { TONE_LIST } from '@/config' import { TONE_LIST } from '@/config'
const toneI18nKeyMap = { const toneI18nKeyMap = {

View File

@ -14,7 +14,7 @@ vi.mock('@/utils/classnames', () => ({
cn: (...args: (string | undefined | false | null)[]) => args.filter(Boolean).join(' '), cn: (...args: (string | undefined | false | null)[]) => args.filter(Boolean).join(' '),
})) }))
vi.mock('@/app/components/base/ui/dropdown-menu', () => ({ vi.mock('@langgenius/dify-ui/dropdown-menu', () => ({
DropdownMenu: ({ children, open }: { children: ReactNode, open: boolean }) => ( DropdownMenu: ({ children, open }: { children: ReactNode, open: boolean }) => (
<div data-testid="dropdown-menu" data-open={open}>{children}</div> <div data-testid="dropdown-menu" data-open={open}>{children}</div>
), ),

View File

@ -1,15 +1,9 @@
'use client' 'use client'
import type { Placement } from '@langgenius/dify-ui/dropdown-menu'
import type { FC } from 'react' import type { FC } from 'react'
import type { Placement } from '@/app/components/base/ui/placement' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@langgenius/dify-ui/dropdown-menu'
import * as React from 'react' import * as React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/app/components/base/ui/dropdown-menu'
import { useGlobalPublicStore } from '@/context/global-public-context' import { useGlobalPublicStore } from '@/context/global-public-context'
import { cn } from '@/utils/classnames' import { cn } from '@/utils/classnames'
import { PluginSource } from '../types' import { PluginSource } from '../types'

View File

@ -1,14 +1,14 @@
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
} from '@langgenius/dify-ui/context-menu'
import { import {
memo, memo,
useMemo, useMemo,
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useEdges } from 'reactflow' import { useEdges } from 'reactflow'
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
} from '@/app/components/base/ui/context-menu'
import { useEdgesInteractions, usePanelInteractions } from './hooks' import { useEdgesInteractions, usePanelInteractions } from './hooks'
import ShortcutsName from './shortcuts-name' import ShortcutsName from './shortcuts-name'
import { useStore } from './store' import { useStore } from './store'

View File

@ -1,4 +1,12 @@
import type { Node } from './types' import type { Node } from './types'
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuGroupLabel,
ContextMenuItem,
ContextMenuSeparator,
} from '@langgenius/dify-ui/context-menu'
import { produce } from 'immer' import { produce } from 'immer'
import { import {
memo, memo,
@ -8,14 +16,6 @@ import {
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useStore as useReactFlowStore, useStoreApi } from 'reactflow' import { useStore as useReactFlowStore, useStoreApi } from 'reactflow'
import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuGroupLabel,
ContextMenuItem,
ContextMenuSeparator,
} from '@/app/components/base/ui/context-menu'
import { useNodesInteractions, useNodesReadOnly, useNodesSyncDraft } from './hooks' import { useNodesInteractions, useNodesReadOnly, useNodesSyncDraft } from './hooks'
import { useSelectionInteractions } from './hooks/use-selection-interactions' import { useSelectionInteractions } from './hooks/use-selection-interactions'
import { useWorkflowHistory, WorkflowHistoryEvent } from './hooks/use-workflow-history' import { useWorkflowHistory, WorkflowHistoryEvent } from './hooks/use-workflow-history'

View File

@ -1,9 +1,5 @@
@import './preflight.css' layer(base); @import './preflight.css' layer(base);
@import '@langgenius/dify-ui/styles.css';
@import '../../themes/light.css' layer(base);
@import '../../themes/dark.css' layer(base);
@import '../../themes/manual-light.css' layer(base);
@import '../../themes/manual-dark.css' layer(base);
@import './monaco-sticky-fix.css' layer(base); @import './monaco-sticky-fix.css' layer(base);
@import '../components/base/action-button/index.css'; @import '../components/base/action-button/index.css';
@ -17,727 +13,6 @@
@config '../../tailwind.config.ts'; @config '../../tailwind.config.ts';
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
@utility system-kbd {
/* font define start */
font-size: 12px;
font-weight: 500;
line-height: 16px;
/* border radius end */
}
@utility system-2xs-regular-uppercase {
font-size: 10px;
font-weight: 400;
text-transform: uppercase;
line-height: 12px;
/* border radius end */
}
@utility system-2xs-regular {
font-size: 10px;
font-weight: 400;
line-height: 12px;
/* border radius end */
}
@utility system-2xs-medium {
font-size: 10px;
font-weight: 500;
line-height: 12px;
/* border radius end */
}
@utility system-2xs-medium-uppercase {
font-size: 10px;
font-weight: 500;
text-transform: uppercase;
line-height: 12px;
/* border radius end */
}
@utility system-2xs-semibold-uppercase {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
line-height: 12px;
/* border radius end */
}
@utility system-xs-regular {
font-size: 12px;
font-weight: 400;
line-height: 16px;
/* border radius end */
}
@utility system-xs-regular-uppercase {
font-size: 12px;
font-weight: 400;
text-transform: uppercase;
line-height: 16px;
/* border radius end */
}
@utility system-xs-medium {
font-size: 12px;
font-weight: 500;
line-height: 16px;
/* border radius end */
}
@utility system-xs-medium-uppercase {
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
line-height: 16px;
/* border radius end */
}
@utility system-xs-semibold {
font-size: 12px;
font-weight: 600;
line-height: 16px;
/* border radius end */
}
@utility system-xs-semibold-uppercase {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
line-height: 16px;
/* border radius end */
}
@utility system-sm-regular {
font-size: 13px;
font-weight: 400;
line-height: 16px;
/* border radius end */
}
@utility system-sm-medium {
font-size: 13px;
font-weight: 500;
line-height: 16px;
/* border radius end */
}
@utility system-sm-medium-uppercase {
font-size: 13px;
font-weight: 500;
text-transform: uppercase;
line-height: 16px;
/* border radius end */
}
@utility system-sm-semibold {
font-size: 13px;
font-weight: 600;
line-height: 16px;
/* border radius end */
}
@utility system-sm-semibold-uppercase {
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
line-height: 16px;
/* border radius end */
}
@utility system-md-regular {
font-size: 14px;
font-weight: 400;
line-height: 20px;
/* border radius end */
}
@utility system-md-medium {
font-size: 14px;
font-weight: 500;
line-height: 20px;
/* border radius end */
}
@utility system-md-semibold {
font-size: 14px;
font-weight: 600;
line-height: 20px;
/* border radius end */
}
@utility system-md-semibold-uppercase {
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
line-height: 20px;
/* border radius end */
}
@utility system-xl-regular {
font-size: 16px;
font-weight: 400;
line-height: 24px;
/* border radius end */
}
@utility system-xl-medium {
font-size: 16px;
font-weight: 500;
line-height: 24px;
/* border radius end */
}
@utility system-xl-semibold {
font-size: 16px;
font-weight: 600;
line-height: 24px;
/* border radius end */
}
@utility code-xs-regular {
font-size: 12px;
font-weight: 400;
line-height: 1.5;
/* border radius end */
}
@utility code-xs-semibold {
font-size: 12px;
font-weight: 600;
line-height: 1.5;
/* border radius end */
}
@utility code-sm-regular {
font-size: 13px;
font-weight: 400;
line-height: 1.5;
/* border radius end */
}
@utility code-sm-semibold {
font-size: 13px;
font-weight: 600;
line-height: 1.5;
/* border radius end */
}
@utility code-md-regular {
font-size: 14px;
font-weight: 400;
line-height: 1.5;
/* border radius end */
}
@utility code-md-semibold {
font-size: 14px;
font-weight: 600;
line-height: 1.5;
/* border radius end */
}
@utility body-xs-light {
font-size: 12px;
font-weight: 300;
line-height: 16px;
/* border radius end */
}
@utility body-xs-regular {
font-size: 12px;
font-weight: 400;
line-height: 16px;
/* border radius end */
}
@utility body-xs-medium {
font-size: 12px;
font-weight: 500;
line-height: 16px;
/* border radius end */
}
@utility body-sm-light {
font-size: 13px;
font-weight: 300;
line-height: 16px;
/* border radius end */
}
@utility body-sm-regular {
font-size: 13px;
font-weight: 400;
line-height: 16px;
/* border radius end */
}
@utility body-sm-medium {
font-size: 13px;
font-weight: 500;
line-height: 16px;
/* border radius end */
}
@utility body-md-light {
font-size: 14px;
font-weight: 300;
line-height: 20px;
/* border radius end */
}
@utility body-md-regular {
font-size: 14px;
font-weight: 400;
line-height: 20px;
/* border radius end */
}
@utility body-md-medium {
font-size: 14px;
font-weight: 500;
line-height: 20px;
/* border radius end */
}
@utility body-lg-light {
font-size: 15px;
font-weight: 300;
line-height: 20px;
/* border radius end */
}
@utility body-lg-regular {
font-size: 15px;
font-weight: 400;
line-height: 20px;
/* border radius end */
}
@utility body-lg-medium {
font-size: 15px;
font-weight: 500;
line-height: 20px;
/* border radius end */
}
@utility body-xl-regular {
font-size: 16px;
font-weight: 400;
line-height: 24px;
/* border radius end */
}
@utility body-xl-medium {
font-size: 16px;
font-weight: 500;
line-height: 24px;
/* border radius end */
}
@utility body-xl-light {
font-size: 16px;
font-weight: 300;
line-height: 24px;
/* border radius end */
}
@utility body-2xl-light {
font-size: 18px;
font-weight: 300;
line-height: 1.5;
/* border radius end */
}
@utility body-2xl-regular {
font-size: 18px;
font-weight: 400;
line-height: 1.5;
/* border radius end */
}
@utility body-2xl-medium {
font-size: 18px;
font-weight: 500;
line-height: 1.5;
/* border radius end */
}
@utility title-xs-semi-bold {
font-size: 12px;
font-weight: 600;
line-height: 16px;
/* border radius end */
}
@utility title-xs-bold {
font-size: 12px;
font-weight: 700;
line-height: 16px;
/* border radius end */
}
@utility title-sm-semi-bold {
font-size: 13px;
font-weight: 600;
line-height: 16px;
/* border radius end */
}
@utility title-sm-bold {
font-size: 13px;
font-weight: 700;
line-height: 16px;
/* border radius end */
}
@utility title-md-semi-bold {
font-size: 14px;
font-weight: 600;
line-height: 20px;
/* border radius end */
}
@utility title-md-bold {
font-size: 14px;
font-weight: 700;
line-height: 20px;
/* border radius end */
}
@utility title-lg-semi-bold {
font-size: 15px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-lg-bold {
font-size: 15px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-xl-semi-bold {
font-size: 16px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-xl-bold {
font-size: 16px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-2xl-semi-bold {
font-size: 18px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-2xl-bold {
font-size: 18px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-3xl-semi-bold {
font-size: 20px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-3xl-bold {
font-size: 20px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-4xl-semi-bold {
font-size: 24px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-4xl-bold {
font-size: 24px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-5xl-semi-bold {
font-size: 30px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-5xl-bold {
font-size: 30px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-6xl-semi-bold {
font-size: 36px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-6xl-bold {
font-size: 36px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-7xl-semi-bold {
font-size: 48px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-7xl-bold {
font-size: 48px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility title-8xl-semi-bold {
font-size: 60px;
font-weight: 600;
line-height: 1.2;
/* border radius end */
}
@utility title-8xl-bold {
font-size: 60px;
font-weight: 700;
line-height: 1.2;
/* border radius end */
}
@utility radius-2xs {
/* font define end */
/* border radius start */
border-radius: 2px;
/* border radius end */
}
@utility radius-xs {
border-radius: 4px;
/* border radius end */
}
@utility radius-sm {
border-radius: 6px;
/* border radius end */
}
@utility radius-md {
border-radius: 8px;
/* border radius end */
}
@utility radius-lg {
border-radius: 10px;
/* border radius end */
}
@utility radius-xl {
border-radius: 12px;
/* border radius end */
}
@utility radius-2xl {
border-radius: 16px;
/* border radius end */
}
@utility radius-3xl {
border-radius: 20px;
/* border radius end */
}
@utility radius-4xl {
border-radius: 24px;
/* border radius end */
}
@utility radius-5xl {
border-radius: 24px;
/* border radius end */
}
@utility radius-6xl {
border-radius: 28px;
/* border radius end */
}
@utility radius-7xl {
border-radius: 32px;
/* border radius end */
}
@utility radius-8xl {
border-radius: 40px;
/* border radius end */
}
@utility radius-9xl {
border-radius: 48px;
/* border radius end */
}
@utility radius-full {
border-radius: 64px;
/* border radius end */
}
@utility no-scrollbar {
/* Hide scrollbar for Chrome, Safari and Opera */
&::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
-ms-overflow-style: none;
scrollbar-width: none;
}
@utility no-spinner {
/* Hide arrows from number input */
&::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
-moz-appearance: textfield;
}
@layer components { @layer components {
html { html {
color-scheme: light; color-scheme: light;
@ -794,35 +69,6 @@
--card-border-rgb: 131, 134, 135; --card-border-rgb: 131, 134, 135;
} }
/* @media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
--primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
--secondary-glow: linear-gradient(to bottom right,
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0.3));
--tile-start-rgb: 2, 13, 46;
--tile-end-rgb: 2, 5, 19;
--tile-border: conic-gradient(#ffffff80,
#ffffff40,
#ffffff30,
#ffffff20,
#ffffff10,
#ffffff10,
#ffffff80);
--callout-rgb: 20, 20, 20;
--callout-border-rgb: 108, 108, 108;
--card-rgb: 100, 100, 100;
--card-border-rgb: 200, 200, 200;
}
} */
* { * {
box-sizing: border-box; box-sizing: border-box;
padding: 0; padding: 0;
@ -838,12 +84,6 @@
body { body {
color: rgb(var(--foreground-rgb)); color: rgb(var(--foreground-rgb));
user-select: none; user-select: none;
/* background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb)); */
} }
a { a {
@ -852,13 +92,6 @@
outline: none; outline: none;
} }
/* @media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
} */
/* CSS Utils */
.h1 { .h1 {
padding-bottom: 1.5rem; padding-bottom: 1.5rem;
line-height: 1.5; line-height: 1.5;
@ -880,7 +113,7 @@
@layer components { @layer components {
.link { .link {
@apply text-blue-600 cursor-pointer hover:opacity-80 transition-opacity duration-200 ease-in-out; @apply cursor-pointer text-blue-600 transition-opacity duration-200 ease-in-out hover:opacity-80;
} }
.text-gradient { .text-gradient {
@ -891,13 +124,11 @@
text-fill-color: transparent; text-fill-color: transparent;
} }
/* overwrite paging active dark model style */
[class*='style_paginatio'] li .text-primary-600 { [class*='style_paginatio'] li .text-primary-600 {
color: rgb(28 100 242); color: rgb(28 100 242);
background-color: rgb(235 245 255); background-color: rgb(235 245 255);
} }
/* support safari 14 and below */
.inset-0 { .inset-0 {
left: 0; left: 0;
right: 0; right: 0;

View File

@ -1,5 +1,4 @@
@import '../../themes/markdown-light.css'; @import '@langgenius/dify-ui/markdown.css';
@import '../../themes/markdown-dark.css';
@reference "./globals.css"; @reference "./globals.css";
.markdown-body { .markdown-body {

View File

@ -16,8 +16,8 @@ This document tracks the migration away from legacy overlay APIs.
- `@/app/components/base/toast` (including `context`) - `@/app/components/base/toast` (including `context`)
- Replacement primitives: - Replacement primitives:
- `@/app/components/base/ui/tooltip` - `@/app/components/base/ui/tooltip`
- `@/app/components/base/ui/dropdown-menu` - `@langgenius/dify-ui/dropdown-menu`
- `@/app/components/base/ui/context-menu` - `@langgenius/dify-ui/context-menu`
- `@/app/components/base/ui/popover` - `@/app/components/base/ui/popover`
- `@/app/components/base/ui/dialog` - `@/app/components/base/ui/dialog`
- `@/app/components/base/ui/alert-dialog` - `@/app/components/base/ui/alert-dialog`

View File

@ -10,7 +10,7 @@ When I ask you to write/refactor/fix tests, follow these rules by default.
- **Testing Tools**: Vitest 4.0.16 + React Testing Library 16.0 - **Testing Tools**: Vitest 4.0.16 + React Testing Library 16.0
- **Test Environment**: happy-dom - **Test Environment**: happy-dom
- **File Naming**: `ComponentName.spec.tsx` inside a same-level `__tests__/` directory - **File Naming**: `ComponentName.spec.tsx` inside a same-level `__tests__/` directory
- **Placement Rule**: Component, hook, and utility tests must live in a sibling `__tests__/` folder at the same level as the source under test. For example, `foo/index.tsx` maps to `foo/__tests__/index.spec.tsx`, and `foo/bar.ts` maps to `foo/__tests__/bar.spec.ts`. - **Placement Rule**: Component, hook, and utility tests must live in a sibling `__tests__/` folder at the same level as the source under test. For example, `foo/index.tsx` maps to `foo/__tests__/index.spec.tsx`, and `foo/bar.ts` maps to `foo/__tests__/bar.spec.ts`. This rule also applies to workspace packages under `packages/`.
## Running Tests ## Running Tests

View File

@ -71,7 +71,7 @@ export const OVERLAY_RESTRICTED_IMPORT_PATTERNS = [
'**/base/dropdown', '**/base/dropdown',
'**/base/dropdown/index', '**/base/dropdown/index',
], ],
message: 'Deprecated: use @/app/components/base/ui/dropdown-menu instead. See issue #32767.', message: 'Deprecated: use @langgenius/dify-ui/dropdown-menu instead. See issue #32767.',
}, },
{ {
group: [ group: [

View File

@ -25,6 +25,7 @@
"analyze": "next experimental-analyze", "analyze": "next experimental-analyze",
"analyze-component": "node ./scripts/analyze-component.js", "analyze-component": "node ./scripts/analyze-component.js",
"build": "next build", "build": "next build",
"build:dify-ui": "pnpm --filter @langgenius/dify-ui build",
"build:vinext": "vinext build", "build:vinext": "vinext build",
"dev": "next dev", "dev": "next dev",
"dev:inspect": "next dev --inspect", "dev:inspect": "next dev --inspect",
@ -40,6 +41,16 @@
"lint:quiet": "vp run lint --quiet", "lint:quiet": "vp run lint --quiet",
"lint:tss": "tsslint --project tsconfig.json", "lint:tss": "tsslint --project tsconfig.json",
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"prebuild": "pnpm run build:dify-ui",
"prebuild:vinext": "pnpm run build:dify-ui",
"predev": "pnpm run build:dify-ui",
"predev:vinext": "pnpm run build:dify-ui",
"prestorybook": "pnpm run build:dify-ui",
"prestorybook:build": "pnpm run build:dify-ui",
"pretest": "pnpm run build:dify-ui",
"pretest:watch": "pnpm run build:dify-ui",
"pretype-check": "pnpm run build:dify-ui",
"pretype-check:tsgo": "pnpm run build:dify-ui",
"refactor-component": "node ./scripts/refactor-component.js", "refactor-component": "node ./scripts/refactor-component.js",
"start": "node ./scripts/copy-and-start.mjs", "start": "node ./scripts/copy-and-start.mjs",
"start:vinext": "vinext start", "start:vinext": "vinext start",
@ -61,6 +72,7 @@
"@formatjs/intl-localematcher": "catalog:", "@formatjs/intl-localematcher": "catalog:",
"@headlessui/react": "catalog:", "@headlessui/react": "catalog:",
"@heroicons/react": "catalog:", "@heroicons/react": "catalog:",
"@langgenius/dify-ui": "workspace:*",
"@lexical/code": "catalog:", "@lexical/code": "catalog:",
"@lexical/link": "catalog:", "@lexical/link": "catalog:",
"@lexical/list": "catalog:", "@lexical/list": "catalog:",

View File

@ -1,5 +1,5 @@
import type { Config } from 'tailwindcss' import type { Config } from 'tailwindcss'
import commonConfig from './tailwind-common-config' import difyUiTailwindPreset from '@langgenius/dify-ui/tailwind-preset'
const config: Config = { const config: Config = {
content: [ content: [
@ -10,7 +10,7 @@ const config: Config = {
'./node_modules/@streamdown/math/dist/*.js', './node_modules/@streamdown/math/dist/*.js',
'!./**/*.{spec,test}.{js,ts,jsx,tsx}', '!./**/*.{spec,test}.{js,ts,jsx,tsx}',
], ],
...commonConfig, ...difyUiTailwindPreset,
} }
export default config export default config