From 485586f49a281f4c1bf4e371ba15b18eaea32a63 Mon Sep 17 00:00:00 2001 From: yyh Date: Thu, 2 Apr 2026 18:25:16 +0800 Subject: [PATCH] feat(web): extract dify ui package --- packages/dify-ui/.gitignore | 2 + packages/dify-ui/package.json | 78 ++ packages/dify-ui/scripts/build.mjs | 31 + .../context-menu/__tests__/index.spec.tsx | 0 .../src}/context-menu/index.stories.tsx | 18 +- .../dify-ui/src}/context-menu/index.tsx | 43 +- .../dropdown-menu/__tests__/index.spec.tsx | 25 +- .../src}/dropdown-menu/index.stories.tsx | 41 +- .../dify-ui/src}/dropdown-menu/index.tsx | 39 +- packages/dify-ui/src/internal/cn.ts | 7 + packages/dify-ui/src/internal/menu-shared.ts | 7 + packages/dify-ui/src/internal/placement.ts | 25 + packages/dify-ui/src/markdown.css | 2 + packages/dify-ui/src/styles.css | 7 + packages/dify-ui/src/styles/tokens.css | 713 ++++++++++++++++ .../dify-ui/src/tailwind-preset.ts | 9 +- {web => packages/dify-ui/src}/themes/dark.css | 0 .../dify-ui/src}/themes/light.css | 0 .../dify-ui/src}/themes/manual-dark.css | 0 .../dify-ui/src}/themes/manual-light.css | 0 .../dify-ui/src}/themes/markdown-dark.css | 0 .../dify-ui/src}/themes/markdown-light.css | 0 .../src/tokens}/tailwind-theme-var-define.ts | 0 packages/dify-ui/src/typography.d.ts | 3 + {web => packages/dify-ui/src}/typography.js | 0 packages/dify-ui/tailwind.config.ts | 8 + packages/dify-ui/tsconfig.build.json | 21 + packages/dify-ui/tsconfig.json | 38 + packages/dify-ui/vite.config.ts | 11 + packages/dify-ui/vitest.setup.ts | 39 + pnpm-lock.yaml | 79 ++ web/.storybook/main.ts | 5 +- .../base/portal-to-follow-elem/index.tsx | 2 +- .../__tests__/compliance.spec.tsx | 2 +- .../__tests__/support.spec.tsx | 4 +- .../header/account-dropdown/compliance.tsx | 2 +- .../header/account-dropdown/index.tsx | 2 +- .../header/account-dropdown/support.tsx | 2 +- .../presets-parameter.tsx | 7 +- .../__tests__/operation-dropdown.spec.tsx | 2 +- .../operation-dropdown.tsx | 10 +- .../components/workflow/edge-contextmenu.tsx | 10 +- .../workflow/selection-contextmenu.tsx | 16 +- web/app/styles/globals.css | 773 +----------------- web/app/styles/markdown.css | 3 +- web/docs/overlay-migration.md | 4 +- web/docs/test.md | 2 +- web/eslint.constants.mjs | 2 +- web/package.json | 12 + web/tailwind.config.ts | 4 +- 50 files changed, 1220 insertions(+), 890 deletions(-) create mode 100644 packages/dify-ui/.gitignore create mode 100644 packages/dify-ui/package.json create mode 100644 packages/dify-ui/scripts/build.mjs rename {web/app/components/base/ui => packages/dify-ui/src}/context-menu/__tests__/index.spec.tsx (100%) rename {web/app/components/base/ui => packages/dify-ui/src}/context-menu/index.stories.tsx (92%) rename {web/app/components/base/ui => packages/dify-ui/src}/context-menu/index.tsx (85%) rename {web/app/components/base/ui => packages/dify-ui/src}/dropdown-menu/__tests__/index.spec.tsx (98%) rename {web/app/components/base/ui => packages/dify-ui/src}/dropdown-menu/index.stories.tsx (87%) rename {web/app/components/base/ui => packages/dify-ui/src}/dropdown-menu/index.tsx (84%) create mode 100644 packages/dify-ui/src/internal/cn.ts create mode 100644 packages/dify-ui/src/internal/menu-shared.ts create mode 100644 packages/dify-ui/src/internal/placement.ts create mode 100644 packages/dify-ui/src/markdown.css create mode 100644 packages/dify-ui/src/styles.css create mode 100644 packages/dify-ui/src/styles/tokens.css rename web/tailwind-common-config.ts => packages/dify-ui/src/tailwind-preset.ts (96%) rename {web => packages/dify-ui/src}/themes/dark.css (100%) rename {web => packages/dify-ui/src}/themes/light.css (100%) rename {web => packages/dify-ui/src}/themes/manual-dark.css (100%) rename {web => packages/dify-ui/src}/themes/manual-light.css (100%) rename {web => packages/dify-ui/src}/themes/markdown-dark.css (100%) rename {web => packages/dify-ui/src}/themes/markdown-light.css (100%) rename {web/themes => packages/dify-ui/src/tokens}/tailwind-theme-var-define.ts (100%) create mode 100644 packages/dify-ui/src/typography.d.ts rename {web => packages/dify-ui/src}/typography.js (100%) create mode 100644 packages/dify-ui/tailwind.config.ts create mode 100644 packages/dify-ui/tsconfig.build.json create mode 100644 packages/dify-ui/tsconfig.json create mode 100644 packages/dify-ui/vite.config.ts create mode 100644 packages/dify-ui/vitest.setup.ts diff --git a/packages/dify-ui/.gitignore b/packages/dify-ui/.gitignore new file mode 100644 index 00000000000..1eae0cf6700 --- /dev/null +++ b/packages/dify-ui/.gitignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ diff --git a/packages/dify-ui/package.json b/packages/dify-ui/package.json new file mode 100644 index 00000000000..9b2571d7d36 --- /dev/null +++ b/packages/dify-ui/package.json @@ -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:" + } +} diff --git a/packages/dify-ui/scripts/build.mjs b/packages/dify-ui/scripts/build.mjs new file mode 100644 index 00000000000..6698e754af3 --- /dev/null +++ b/packages/dify-ui/scripts/build.mjs @@ -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, +}) diff --git a/web/app/components/base/ui/context-menu/__tests__/index.spec.tsx b/packages/dify-ui/src/context-menu/__tests__/index.spec.tsx similarity index 100% rename from web/app/components/base/ui/context-menu/__tests__/index.spec.tsx rename to packages/dify-ui/src/context-menu/__tests__/index.spec.tsx diff --git a/web/app/components/base/ui/context-menu/index.stories.tsx b/packages/dify-ui/src/context-menu/index.stories.tsx similarity index 92% rename from web/app/components/base/ui/context-menu/index.stories.tsx rename to packages/dify-ui/src/context-menu/index.stories.tsx index 7c57a81c65a..2932a10ee37 100644 --- a/web/app/components/base/ui/context-menu/index.stories.tsx +++ b/packages/dify-ui/src/context-menu/index.stories.tsx @@ -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 { ContextMenu, @@ -17,7 +23,7 @@ import { ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, -} from '.' +} from './index' const TriggerArea = ({ label = 'Right-click inside this area' }: { label?: string }) => ( - + Rename - + Duplicate - + Share @@ -206,7 +212,7 @@ export const Complex: Story = { - + Delete diff --git a/web/app/components/base/ui/context-menu/index.tsx b/packages/dify-ui/src/context-menu/index.tsx similarity index 85% rename from web/app/components/base/ui/context-menu/index.tsx rename to packages/dify-ui/src/context-menu/index.tsx index 5a0f580ca4c..19e0f5ed95c 100644 --- a/web/app/components/base/ui/context-menu/index.tsx +++ b/packages/dify-ui/src/context-menu/index.tsx @@ -1,8 +1,10 @@ '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 { RiArrowRightSLine, RiCheckLine } from '@remixicon/react' import * as React from 'react' +import { cn } from '../internal/cn.js' import { menuBackdropClassName, menuGroupLabelClassName, @@ -11,9 +13,8 @@ import { menuPopupBaseClassName, menuRowClassName, menuSeparatorClassName, -} from '@/app/components/base/ui/menu-shared' -import { parsePlacement } from '@/app/components/base/ui/placement' -import { cn } from '@/utils/classnames' +} from '../internal/menu-shared.js' +import { parsePlacement } from '../internal/placement.js' export const ContextMenu = BaseContextMenu.Root export const ContextMenuTrigger = BaseContextMenu.Trigger @@ -44,11 +45,11 @@ type ContextMenuPopupRenderProps = Required - {children ?? } + {children ?? } ) } @@ -204,7 +205,7 @@ export function ContextMenuCheckboxItemIndicator({ className={cn(menuIndicatorClassName, className)} {...props} > - + ) } @@ -218,7 +219,7 @@ export function ContextMenuRadioItemIndicator({ className={cn(menuIndicatorClassName, className)} {...props} > - + ) } @@ -239,20 +240,20 @@ export function ContextMenuSubTrigger({ {...props} > {children} - + ) } type ContextMenuSubContentProps = { children: React.ReactNode - placement?: Placement - sideOffset?: number - alignOffset?: number - className?: string - popupClassName?: string - positionerProps?: ContextMenuContentProps['positionerProps'] - popupProps?: ContextMenuContentProps['popupProps'] + placement?: Placement | undefined + sideOffset?: number | undefined + alignOffset?: number | undefined + className?: string | undefined + popupClassName?: string | undefined + positionerProps?: ContextMenuContentProps['positionerProps'] | undefined + popupProps?: ContextMenuContentProps['popupProps'] | undefined } export function ContextMenuSubContent({ @@ -300,3 +301,5 @@ export function ContextMenuSeparator({ /> ) } + +export type { Placement } diff --git a/web/app/components/base/ui/dropdown-menu/__tests__/index.spec.tsx b/packages/dify-ui/src/dropdown-menu/__tests__/index.spec.tsx similarity index 98% rename from web/app/components/base/ui/dropdown-menu/__tests__/index.spec.tsx rename to packages/dify-ui/src/dropdown-menu/__tests__/index.spec.tsx index b6772e5ad0b..7231a51fd12 100644 --- a/web/app/components/base/ui/dropdown-menu/__tests__/index.spec.tsx +++ b/packages/dify-ui/src/dropdown-menu/__tests__/index.spec.tsx @@ -1,7 +1,6 @@ import type { ComponentPropsWithoutRef, ReactNode } from 'react' import { fireEvent, render, screen, within } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' -import Link from '@/next/link' import { DropdownMenu, DropdownMenuContent, @@ -14,20 +13,20 @@ import { DropdownMenuTrigger, } from '../index' -vi.mock('@/next/link', () => ({ - default: ({ - href, - children, - ...props - }: { - href: string - children?: ReactNode - } & Omit, 'href'>) => ( +function MockLink({ + href, + children, + ...props +}: { + href: string + children?: ReactNode +} & Omit, 'href'>) { + return ( {children} - ), -})) + ) +} describe('dropdown-menu wrapper', () => { describe('DropdownMenuContent', () => { @@ -301,7 +300,7 @@ describe('dropdown-menu wrapper', () => { Open } + render={} aria-label="account link" > Account settings diff --git a/web/app/components/base/ui/dropdown-menu/index.stories.tsx b/packages/dify-ui/src/dropdown-menu/index.stories.tsx similarity index 87% rename from web/app/components/base/ui/dropdown-menu/index.stories.tsx rename to packages/dify-ui/src/dropdown-menu/index.stories.tsx index 0e2f21dd542..8058ab11038 100644 --- a/web/app/components/base/ui/dropdown-menu/index.stories.tsx +++ b/packages/dify-ui/src/dropdown-menu/index.stories.tsx @@ -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 { DropdownMenu, @@ -17,7 +28,7 @@ import { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, -} from '.' +} from './index' const TriggerButton = ({ label = 'Open Menu' }: { label?: string }) => ( - + Edit - + Duplicate - + Archive - + Delete @@ -262,35 +273,35 @@ const ComplexDemo = () => { Edit - + Rename - + Duplicate - + Move to Workspace - + Share - + Email - + Slack - + Copy Link @@ -315,13 +326,13 @@ const ComplexDemo = () => { - + Show Archived - + Delete diff --git a/web/app/components/base/ui/dropdown-menu/index.tsx b/packages/dify-ui/src/dropdown-menu/index.tsx similarity index 84% rename from web/app/components/base/ui/dropdown-menu/index.tsx rename to packages/dify-ui/src/dropdown-menu/index.tsx index 13c2dab626d..8bfadb9935b 100644 --- a/web/app/components/base/ui/dropdown-menu/index.tsx +++ b/packages/dify-ui/src/dropdown-menu/index.tsx @@ -1,8 +1,10 @@ '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 { RiArrowRightSLine, RiCheckLine } from '@remixicon/react' import * as React from 'react' +import { cn } from '../internal/cn.js' import { menuGroupLabelClassName, menuIndicatorClassName, @@ -10,9 +12,8 @@ import { menuPopupBaseClassName, menuRowClassName, menuSeparatorClassName, -} from '@/app/components/base/ui/menu-shared' -import { parsePlacement } from '@/app/components/base/ui/placement' -import { cn } from '@/utils/classnames' +} from '../internal/menu-shared.js' +import { parsePlacement } from '../internal/placement.js' export const DropdownMenu = Menu.Root export const DropdownMenuPortal = Menu.Portal @@ -42,7 +43,7 @@ export function DropdownMenuRadioItemIndicator({ className={cn(menuIndicatorClassName, className)} {...props} > - + ) } @@ -68,7 +69,7 @@ export function DropdownMenuCheckboxItemIndicator({ className={cn(menuIndicatorClassName, className)} {...props} > - + ) } @@ -106,10 +107,10 @@ type DropdownMenuPopupRenderProps = Required {children} - + ) } type DropdownMenuSubContentProps = { children: React.ReactNode - placement?: Placement - sideOffset?: number - alignOffset?: number - className?: string - popupClassName?: string - positionerProps?: DropdownMenuContentProps['positionerProps'] - popupProps?: DropdownMenuContentProps['popupProps'] + placement?: Placement | undefined + sideOffset?: number | undefined + alignOffset?: number | undefined + className?: string | undefined + popupClassName?: string | undefined + positionerProps?: DropdownMenuContentProps['positionerProps'] | undefined + popupProps?: DropdownMenuContentProps['popupProps'] | undefined } export function DropdownMenuSubContent({ @@ -272,3 +273,5 @@ export function DropdownMenuSeparator({ /> ) } + +export type { Placement } diff --git a/packages/dify-ui/src/internal/cn.ts b/packages/dify-ui/src/internal/cn.ts new file mode 100644 index 00000000000..abba253f04c --- /dev/null +++ b/packages/dify-ui/src/internal/cn.ts @@ -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)) +} diff --git a/packages/dify-ui/src/internal/menu-shared.ts b/packages/dify-ui/src/internal/menu-shared.ts new file mode 100644 index 00000000000..b0c379dae2d --- /dev/null +++ b/packages/dify-ui/src/internal/menu-shared.ts @@ -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' diff --git a/packages/dify-ui/src/internal/placement.ts b/packages/dify-ui/src/internal/placement.ts new file mode 100644 index 00000000000..fbcb6f00e30 --- /dev/null +++ b/packages/dify-ui/src/internal/placement.ts @@ -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', + } +} diff --git a/packages/dify-ui/src/markdown.css b/packages/dify-ui/src/markdown.css new file mode 100644 index 00000000000..317c13e280b --- /dev/null +++ b/packages/dify-ui/src/markdown.css @@ -0,0 +1,2 @@ +@import './themes/markdown-light.css'; +@import './themes/markdown-dark.css'; diff --git a/packages/dify-ui/src/styles.css b/packages/dify-ui/src/styles.css new file mode 100644 index 00000000000..063bef1afe3 --- /dev/null +++ b/packages/dify-ui/src/styles.css @@ -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}'; diff --git a/packages/dify-ui/src/styles/tokens.css b/packages/dify-ui/src/styles/tokens.css new file mode 100644 index 00000000000..c38fc81c0c9 --- /dev/null +++ b/packages/dify-ui/src/styles/tokens.css @@ -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; +} + diff --git a/web/tailwind-common-config.ts b/packages/dify-ui/src/tailwind-preset.ts similarity index 96% rename from web/tailwind-common-config.ts rename to packages/dify-ui/src/tailwind-preset.ts index db50f2457b8..b008051ebe1 100644 --- a/web/tailwind-common-config.ts +++ b/packages/dify-ui/src/tailwind-preset.ts @@ -1,8 +1,10 @@ import { icons as customPublicIcons } from '@dify/iconify-collections/custom-public' 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 tailwindThemeVarDefine from './themes/tailwind-theme-var-define' +import tailwindThemeVarDefine from './tokens/tailwind-theme-var-define.js' import typography from './typography.js' const config = { @@ -151,7 +153,8 @@ const config = { tailwindTypography, iconsPlugin({ collections: { - ...getIconCollections(['heroicons', 'ri']), + heroicons, + ri: remixIcons, 'custom-public': customPublicIcons, 'custom-vender': customVenderIcons, }, diff --git a/web/themes/dark.css b/packages/dify-ui/src/themes/dark.css similarity index 100% rename from web/themes/dark.css rename to packages/dify-ui/src/themes/dark.css diff --git a/web/themes/light.css b/packages/dify-ui/src/themes/light.css similarity index 100% rename from web/themes/light.css rename to packages/dify-ui/src/themes/light.css diff --git a/web/themes/manual-dark.css b/packages/dify-ui/src/themes/manual-dark.css similarity index 100% rename from web/themes/manual-dark.css rename to packages/dify-ui/src/themes/manual-dark.css diff --git a/web/themes/manual-light.css b/packages/dify-ui/src/themes/manual-light.css similarity index 100% rename from web/themes/manual-light.css rename to packages/dify-ui/src/themes/manual-light.css diff --git a/web/themes/markdown-dark.css b/packages/dify-ui/src/themes/markdown-dark.css similarity index 100% rename from web/themes/markdown-dark.css rename to packages/dify-ui/src/themes/markdown-dark.css diff --git a/web/themes/markdown-light.css b/packages/dify-ui/src/themes/markdown-light.css similarity index 100% rename from web/themes/markdown-light.css rename to packages/dify-ui/src/themes/markdown-light.css diff --git a/web/themes/tailwind-theme-var-define.ts b/packages/dify-ui/src/tokens/tailwind-theme-var-define.ts similarity index 100% rename from web/themes/tailwind-theme-var-define.ts rename to packages/dify-ui/src/tokens/tailwind-theme-var-define.ts diff --git a/packages/dify-ui/src/typography.d.ts b/packages/dify-ui/src/typography.d.ts new file mode 100644 index 00000000000..db1b269684c --- /dev/null +++ b/packages/dify-ui/src/typography.d.ts @@ -0,0 +1,3 @@ +declare const typography: (helpers: { theme: (path: string) => unknown }) => Record + +export default typography diff --git a/web/typography.js b/packages/dify-ui/src/typography.js similarity index 100% rename from web/typography.js rename to packages/dify-ui/src/typography.js diff --git a/packages/dify-ui/tailwind.config.ts b/packages/dify-ui/tailwind.config.ts new file mode 100644 index 00000000000..8185e86928f --- /dev/null +++ b/packages/dify-ui/tailwind.config.ts @@ -0,0 +1,8 @@ +import difyUiTailwindPreset from './src/tailwind-preset' + +const config = { + content: [], + ...difyUiTailwindPreset, +} + +export default config diff --git a/packages/dify-ui/tsconfig.build.json b/packages/dify-ui/tsconfig.build.json new file mode 100644 index 00000000000..50f51559737 --- /dev/null +++ b/packages/dify-ui/tsconfig.build.json @@ -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__/**" + ] +} diff --git a/packages/dify-ui/tsconfig.json b/packages/dify-ui/tsconfig.json new file mode 100644 index 00000000000..67c2d163e23 --- /dev/null +++ b/packages/dify-ui/tsconfig.json @@ -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" + ] +} diff --git a/packages/dify-ui/vite.config.ts b/packages/dify-ui/vite.config.ts new file mode 100644 index 00000000000..d6976e74b33 --- /dev/null +++ b/packages/dify-ui/vite.config.ts @@ -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'], + }, +}) diff --git a/packages/dify-ui/vitest.setup.ts b/packages/dify-ui/vitest.setup.ts new file mode 100644 index 00000000000..8ae19714ed9 --- /dev/null +++ b/packages/dify-ui/vitest.setup.ts @@ -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 = [] + constructor(_callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) {} + observe(_target: Element) {} + unobserve(_target: Element) {} + disconnect() {} + takeRecords(): IntersectionObserverEntry[] { + return [] + } + } +} + +afterEach(() => { + cleanup() +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a44b621b14..dd902d5c9f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -640,6 +640,82 @@ importers: 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) + 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: devDependencies: iconify-import-svg: @@ -702,6 +778,9 @@ importers: '@heroicons/react': specifier: 'catalog:' version: 2.2.0(react@19.2.4) + '@langgenius/dify-ui': + specifier: workspace:* + version: link:../packages/dify-ui '@lexical/code': 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) diff --git a/web/.storybook/main.ts b/web/.storybook/main.ts index 918860c7864..f7447d797e1 100644 --- a/web/.storybook/main.ts +++ b/web/.storybook/main.ts @@ -1,7 +1,10 @@ import type { StorybookConfig } from '@storybook/nextjs-vite' 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: [ // Not working with Storybook Vite framework // '@storybook/addon-onboarding', diff --git a/web/app/components/base/portal-to-follow-elem/index.tsx b/web/app/components/base/portal-to-follow-elem/index.tsx index 7d4f6baa9bd..357b1ca1c73 100644 --- a/web/app/components/base/portal-to-follow-elem/index.tsx +++ b/web/app/components/base/portal-to-follow-elem/index.tsx @@ -6,7 +6,7 @@ * * Migration guide: * - 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` * - Dialog/Modal → `@/app/components/base/ui/dialog` * - Select → `@/app/components/base/ui/select` diff --git a/web/app/components/header/account-dropdown/__tests__/compliance.spec.tsx b/web/app/components/header/account-dropdown/__tests__/compliance.spec.tsx index 01f3460e7d4..dfd35cf8672 100644 --- a/web/app/components/header/account-dropdown/__tests__/compliance.spec.tsx +++ b/web/app/components/header/account-dropdown/__tests__/compliance.spec.tsx @@ -1,7 +1,7 @@ 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 { 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 { Plan } from '@/app/components/billing/type' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' diff --git a/web/app/components/header/account-dropdown/__tests__/support.spec.tsx b/web/app/components/header/account-dropdown/__tests__/support.spec.tsx index a826038ad04..38be78abf5f 100644 --- a/web/app/components/header/account-dropdown/__tests__/support.spec.tsx +++ b/web/app/components/header/account-dropdown/__tests__/support.spec.tsx @@ -1,7 +1,7 @@ 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 { useAppContext } from '@/context/app-context' import { baseProviderContextValue, useProviderContext } from '@/context/provider-context' diff --git a/web/app/components/header/account-dropdown/compliance.tsx b/web/app/components/header/account-dropdown/compliance.tsx index 41c1e81910c..c149da26e08 100644 --- a/web/app/components/header/account-dropdown/compliance.tsx +++ b/web/app/components/header/account-dropdown/compliance.tsx @@ -1,8 +1,8 @@ import type { ReactNode } from 'react' +import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@langgenius/dify-ui/dropdown-menu' import { useMutation } from '@tanstack/react-query' import { useCallback } from 'react' 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 { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' import { Plan } from '@/app/components/billing/type' diff --git a/web/app/components/header/account-dropdown/index.tsx b/web/app/components/header/account-dropdown/index.tsx index f5b0352a401..5a800a43100 100644 --- a/web/app/components/header/account-dropdown/index.tsx +++ b/web/app/components/header/account-dropdown/index.tsx @@ -1,13 +1,13 @@ 'use client' 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 { useTranslation } from 'react-i18next' import { resetUser } from '@/app/components/base/amplitude/utils' import { Avatar } from '@/app/components/base/avatar' import PremiumBadge from '@/app/components/base/premium-badge' 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 { IS_CLOUD_EDITION } from '@/config' import { useAppContext } from '@/context/app-context' diff --git a/web/app/components/header/account-dropdown/support.tsx b/web/app/components/header/account-dropdown/support.tsx index 97d87ac7e12..9a7023c415a 100644 --- a/web/app/components/header/account-dropdown/support.tsx +++ b/web/app/components/header/account-dropdown/support.tsx @@ -1,5 +1,5 @@ +import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuLinkItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@langgenius/dify-ui/dropdown-menu' 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 { Plan } from '@/app/components/billing/type' import { SUPPORT_EMAIL_ADDRESS, ZENDESK_WIDGET_KEY } from '@/config' diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/presets-parameter.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/presets-parameter.tsx index f96e7d02a54..e1503244e35 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/presets-parameter.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/presets-parameter.tsx @@ -1,15 +1,10 @@ import type { ReactNode } from 'react' +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@langgenius/dify-ui/dropdown-menu' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { Brush01 } from '@/app/components/base/icons/src/vender/solid/editor' import { Scales02 } from '@/app/components/base/icons/src/vender/solid/FinanceAndECommerce' 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' const toneI18nKeyMap = { diff --git a/web/app/components/plugins/plugin-detail-panel/__tests__/operation-dropdown.spec.tsx b/web/app/components/plugins/plugin-detail-panel/__tests__/operation-dropdown.spec.tsx index b8d84763df6..f2d25ea231b 100644 --- a/web/app/components/plugins/plugin-detail-panel/__tests__/operation-dropdown.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/__tests__/operation-dropdown.spec.tsx @@ -14,7 +14,7 @@ vi.mock('@/utils/classnames', () => ({ 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 }) => (
{children}
), diff --git a/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx b/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx index b056f977935..0dd5cc8f0d8 100644 --- a/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx +++ b/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx @@ -1,15 +1,9 @@ 'use client' +import type { Placement } from '@langgenius/dify-ui/dropdown-menu' 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 { 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 { cn } from '@/utils/classnames' import { PluginSource } from '../types' diff --git a/web/app/components/workflow/edge-contextmenu.tsx b/web/app/components/workflow/edge-contextmenu.tsx index cf9e70ef817..28c514bbf65 100644 --- a/web/app/components/workflow/edge-contextmenu.tsx +++ b/web/app/components/workflow/edge-contextmenu.tsx @@ -1,14 +1,14 @@ +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, +} from '@langgenius/dify-ui/context-menu' import { memo, useMemo, } from 'react' import { useTranslation } from 'react-i18next' import { useEdges } from 'reactflow' -import { - ContextMenu, - ContextMenuContent, - ContextMenuItem, -} from '@/app/components/base/ui/context-menu' import { useEdgesInteractions, usePanelInteractions } from './hooks' import ShortcutsName from './shortcuts-name' import { useStore } from './store' diff --git a/web/app/components/workflow/selection-contextmenu.tsx b/web/app/components/workflow/selection-contextmenu.tsx index 8c6304ba951..dec808ea627 100644 --- a/web/app/components/workflow/selection-contextmenu.tsx +++ b/web/app/components/workflow/selection-contextmenu.tsx @@ -1,4 +1,12 @@ import type { Node } from './types' +import { + ContextMenu, + ContextMenuContent, + ContextMenuGroup, + ContextMenuGroupLabel, + ContextMenuItem, + ContextMenuSeparator, +} from '@langgenius/dify-ui/context-menu' import { produce } from 'immer' import { memo, @@ -8,14 +16,6 @@ import { } from 'react' import { useTranslation } from 'react-i18next' 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 { useSelectionInteractions } from './hooks/use-selection-interactions' import { useWorkflowHistory, WorkflowHistoryEvent } from './hooks/use-workflow-history' diff --git a/web/app/styles/globals.css b/web/app/styles/globals.css index 0d9c950dec3..3462e3434af 100644 --- a/web/app/styles/globals.css +++ b/web/app/styles/globals.css @@ -1,9 +1,5 @@ @import './preflight.css' layer(base); - -@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 '@langgenius/dify-ui/styles.css'; @import './monaco-sticky-fix.css' layer(base); @import '../components/base/action-button/index.css'; @@ -17,727 +13,6 @@ @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 { html { color-scheme: light; @@ -794,35 +69,6 @@ --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; padding: 0; @@ -838,12 +84,6 @@ body { color: rgb(var(--foreground-rgb)); user-select: none; - /* background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); */ } a { @@ -852,13 +92,6 @@ outline: none; } - /* @media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } -} */ - - /* CSS Utils */ .h1 { padding-bottom: 1.5rem; line-height: 1.5; @@ -880,7 +113,7 @@ @layer components { .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 { @@ -891,13 +124,11 @@ text-fill-color: transparent; } - /* overwrite paging active dark model style */ [class*='style_paginatio'] li .text-primary-600 { color: rgb(28 100 242); background-color: rgb(235 245 255); } - /* support safari 14 and below */ .inset-0 { left: 0; right: 0; diff --git a/web/app/styles/markdown.css b/web/app/styles/markdown.css index 1ada6e49bde..b300b85b3f1 100644 --- a/web/app/styles/markdown.css +++ b/web/app/styles/markdown.css @@ -1,5 +1,4 @@ -@import '../../themes/markdown-light.css'; -@import '../../themes/markdown-dark.css'; +@import '@langgenius/dify-ui/markdown.css'; @reference "./globals.css"; .markdown-body { diff --git a/web/docs/overlay-migration.md b/web/docs/overlay-migration.md index 0609b1e3258..45594e668b2 100644 --- a/web/docs/overlay-migration.md +++ b/web/docs/overlay-migration.md @@ -16,8 +16,8 @@ This document tracks the migration away from legacy overlay APIs. - `@/app/components/base/toast` (including `context`) - Replacement primitives: - `@/app/components/base/ui/tooltip` - - `@/app/components/base/ui/dropdown-menu` - - `@/app/components/base/ui/context-menu` + - `@langgenius/dify-ui/dropdown-menu` + - `@langgenius/dify-ui/context-menu` - `@/app/components/base/ui/popover` - `@/app/components/base/ui/dialog` - `@/app/components/base/ui/alert-dialog` diff --git a/web/docs/test.md b/web/docs/test.md index bc1546a991a..b562968d746 100644 --- a/web/docs/test.md +++ b/web/docs/test.md @@ -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 - **Test Environment**: happy-dom - **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 diff --git a/web/eslint.constants.mjs b/web/eslint.constants.mjs index d4490425421..828faff5f81 100644 --- a/web/eslint.constants.mjs +++ b/web/eslint.constants.mjs @@ -71,7 +71,7 @@ export const OVERLAY_RESTRICTED_IMPORT_PATTERNS = [ '**/base/dropdown', '**/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: [ diff --git a/web/package.json b/web/package.json index d72d8b16487..591144ce921 100644 --- a/web/package.json +++ b/web/package.json @@ -25,6 +25,7 @@ "analyze": "next experimental-analyze", "analyze-component": "node ./scripts/analyze-component.js", "build": "next build", + "build:dify-ui": "pnpm --filter @langgenius/dify-ui build", "build:vinext": "vinext build", "dev": "next dev", "dev:inspect": "next dev --inspect", @@ -40,6 +41,16 @@ "lint:quiet": "vp run lint --quiet", "lint:tss": "tsslint --project tsconfig.json", "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", "start": "node ./scripts/copy-and-start.mjs", "start:vinext": "vinext start", @@ -61,6 +72,7 @@ "@formatjs/intl-localematcher": "catalog:", "@headlessui/react": "catalog:", "@heroicons/react": "catalog:", + "@langgenius/dify-ui": "workspace:*", "@lexical/code": "catalog:", "@lexical/link": "catalog:", "@lexical/list": "catalog:", diff --git a/web/tailwind.config.ts b/web/tailwind.config.ts index 32b889e7077..513f5ec3d7e 100644 --- a/web/tailwind.config.ts +++ b/web/tailwind.config.ts @@ -1,5 +1,5 @@ import type { Config } from 'tailwindcss' -import commonConfig from './tailwind-common-config' +import difyUiTailwindPreset from '@langgenius/dify-ui/tailwind-preset' const config: Config = { content: [ @@ -10,7 +10,7 @@ const config: Config = { './node_modules/@streamdown/math/dist/*.js', '!./**/*.{spec,test}.{js,ts,jsx,tsx}', ], - ...commonConfig, + ...difyUiTailwindPreset, } export default config