mirror of
https://github.com/langgenius/dify.git
synced 2026-10-29 18:07:39 +08:00
chore(tailwind-css): migrate to css first (#35754)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
autofix-ci[bot]
copilot-swe-agent[bot]
parent
70eb98d6c5
commit
b2dacf0718
@@ -6,7 +6,6 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { I18nClientProvider as I18N } from '../app/components/provider/i18n'
|
||||
import commonEnUS from '../i18n/en-US/common.json'
|
||||
|
||||
import '../app/styles/globals.css'
|
||||
import '../app/styles/markdown.css'
|
||||
import './storybook.css'
|
||||
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
@import '../app/styles/tailwind-core.css';
|
||||
|
||||
@source '../app';
|
||||
@source '../context';
|
||||
@source '../hooks';
|
||||
@source '.';
|
||||
@source '../../packages/dify-ui/src';
|
||||
@source '../node_modules/streamdown/dist';
|
||||
@source '../node_modules/@streamdown/math/dist';
|
||||
@source not '../**/*.{spec,test}.{js,ts,jsx,tsx}';
|
||||
@source not '../../packages/dify-ui/src/**/*.{spec,test}.{ts,tsx}';
|
||||
@source not '../../packages/dify-ui/src/**/*.stories.{ts,tsx}';
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: unset;
|
||||
|
||||
@@ -255,7 +255,6 @@ const TestThemeProvider = ({ children }: { children: React.ReactNode }) => (
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
enableColorScheme={false}
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
|
||||
+47
-31
@@ -15,9 +15,9 @@ import type {
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import Radio from '@/app/components/base/radio'
|
||||
import RadioE from '@/app/components/base/radio/ui'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
|
||||
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector'
|
||||
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
|
||||
@@ -28,6 +28,23 @@ import { FormTypeEnum } from '../declarations'
|
||||
import { useLanguage } from '../hooks'
|
||||
import Input from './Input'
|
||||
|
||||
const radioGridColumnsClassNames: Record<number, string> = {
|
||||
1: 'grid-cols-1',
|
||||
2: 'grid-cols-2',
|
||||
3: 'grid-cols-3',
|
||||
4: 'grid-cols-4',
|
||||
5: 'grid-cols-5',
|
||||
6: 'grid-cols-6',
|
||||
7: 'grid-cols-7',
|
||||
8: 'grid-cols-8',
|
||||
9: 'grid-cols-9',
|
||||
10: 'grid-cols-10',
|
||||
11: 'grid-cols-11',
|
||||
12: 'grid-cols-12',
|
||||
}
|
||||
|
||||
type ModelSelectorValue = Record<string, unknown>
|
||||
|
||||
type FormProps<
|
||||
CustomFormSchema extends Omit<CredentialFormSchema, 'type'> & { type: string } = never,
|
||||
> = {
|
||||
@@ -100,7 +117,7 @@ function Form<
|
||||
fieldMoreInfo,
|
||||
}
|
||||
|
||||
const handleFormChange = (key: string, val: string | boolean) => {
|
||||
const handleFormChange = (key: string, val: FormValue[string]) => {
|
||||
if (isEditMode && (key === '__model_type' || key === '__model_name'))
|
||||
return
|
||||
|
||||
@@ -115,7 +132,7 @@ function Form<
|
||||
onChange({ ...value, [key]: val, ...shouldClearVariable })
|
||||
}
|
||||
|
||||
const handleModelChanged = useCallback((key: string, model: any) => {
|
||||
const handleModelChanged = useCallback((key: string, model: ModelSelectorValue) => {
|
||||
const newValue = {
|
||||
...value[key],
|
||||
...model,
|
||||
@@ -125,17 +142,16 @@ function Form<
|
||||
}, [onChange, value])
|
||||
|
||||
const renderField = (formSchema: CredentialFormSchema | CustomFormSchema) => {
|
||||
const tooltip = formSchema.tooltip
|
||||
const tooltipContent = (tooltip && (
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[200px]">
|
||||
{tooltip[language] || tooltip.en_US}
|
||||
</div>
|
||||
)}
|
||||
triggerClassName="ml-1 w-4 h-4"
|
||||
asChild={false}
|
||||
/>
|
||||
const infotip = formSchema.tooltip
|
||||
const infotipText = infotip?.[language] || infotip?.en_US
|
||||
const infotipContent = (infotipText && (
|
||||
<Infotip
|
||||
aria-label={infotipText}
|
||||
className="ml-1"
|
||||
popupClassName="w-[200px] max-w-[200px]"
|
||||
>
|
||||
{infotipText}
|
||||
</Infotip>
|
||||
))
|
||||
if (override) {
|
||||
const [overrideTypes, overrideRender] = override
|
||||
@@ -166,7 +182,7 @@ function Form<
|
||||
{required && (
|
||||
<span className="ml-1 text-red-500">*</span>
|
||||
)}
|
||||
{tooltipContent}
|
||||
{infotipContent}
|
||||
</div>
|
||||
<Input
|
||||
className={cn(inputClassName, `${disabled && 'cursor-not-allowed opacity-60'}`)}
|
||||
@@ -201,6 +217,7 @@ function Form<
|
||||
return null
|
||||
|
||||
const disabled = isEditMode && (variable === '__model_type' || variable === '__model_name')
|
||||
const gridColumnsClassName = radioGridColumnsClassNames[options.length] ?? 'grid-cols-1'
|
||||
|
||||
return (
|
||||
<div key={variable} className={cn(itemClassName, 'py-3')}>
|
||||
@@ -209,10 +226,9 @@ function Form<
|
||||
{required && (
|
||||
<span className="ml-1 text-red-500">*</span>
|
||||
)}
|
||||
{tooltipContent}
|
||||
{infotipContent}
|
||||
</div>
|
||||
{/* eslint-disable-next-line tailwindcss/no-unknown-classes */}
|
||||
<div className={cn('grid gap-3', `grid-cols-${options?.length}`)}>
|
||||
<div className={cn('grid gap-3', gridColumnsClassName)}>
|
||||
{options.filter((option) => {
|
||||
if (option.show_on.length)
|
||||
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
|
||||
@@ -272,7 +288,7 @@ function Form<
|
||||
{required && (
|
||||
<span className="ml-1 text-red-500">*</span>
|
||||
)}
|
||||
{tooltipContent}
|
||||
{infotipContent}
|
||||
</div>
|
||||
<Select
|
||||
disabled={readonly}
|
||||
@@ -320,7 +336,7 @@ function Form<
|
||||
{required && (
|
||||
<span className="ml-1 text-red-500">*</span>
|
||||
)}
|
||||
{tooltipContent}
|
||||
{infotipContent}
|
||||
</div>
|
||||
<Radio.Group
|
||||
className="flex items-center"
|
||||
@@ -350,7 +366,7 @@ function Form<
|
||||
{required && (
|
||||
<span className="ml-1 text-red-500">*</span>
|
||||
)}
|
||||
{tooltipContent}
|
||||
{infotipContent}
|
||||
</div>
|
||||
<ModelParameterModal
|
||||
popupClassName="w-[387px]!"
|
||||
@@ -382,7 +398,7 @@ function Form<
|
||||
{required && (
|
||||
<span className="ml-1 text-red-500">*</span>
|
||||
)}
|
||||
{tooltipContent}
|
||||
{infotipContent}
|
||||
</div>
|
||||
<ToolSelector
|
||||
scope={scope}
|
||||
@@ -392,8 +408,8 @@ function Form<
|
||||
disabled={readonly}
|
||||
value={value[variable]}
|
||||
// selectedTools={value[variable] ? [value[variable]] : []}
|
||||
onSelect={item => handleFormChange(variable, item as any)}
|
||||
onDelete={() => handleFormChange(variable, null as any)}
|
||||
onSelect={item => handleFormChange(variable, item)}
|
||||
onDelete={() => handleFormChange(variable, null)}
|
||||
/>
|
||||
{fieldMoreInfo?.(formSchema)}
|
||||
{validating && changeKey === variable && <ValidatingTip />}
|
||||
@@ -405,7 +421,7 @@ function Form<
|
||||
const {
|
||||
variable,
|
||||
label,
|
||||
tooltip,
|
||||
tooltip: infotip,
|
||||
required,
|
||||
scope,
|
||||
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
|
||||
@@ -420,9 +436,9 @@ function Form<
|
||||
scope={scope}
|
||||
label={label[language] || label.en_US}
|
||||
required={required}
|
||||
tooltip={tooltip?.[language] || tooltip?.en_US}
|
||||
tooltip={infotip?.[language] || infotip?.en_US}
|
||||
value={value[variable] || []}
|
||||
onChange={item => handleFormChange(variable, item as any)}
|
||||
onChange={item => handleFormChange(variable, item)}
|
||||
supportCollapse
|
||||
/>
|
||||
{fieldMoreInfo?.(formSchema)}
|
||||
@@ -446,13 +462,13 @@ function Form<
|
||||
{required && (
|
||||
<span className="ml-1 text-red-500">*</span>
|
||||
)}
|
||||
{tooltipContent}
|
||||
{infotipContent}
|
||||
</div>
|
||||
<AppSelector
|
||||
disabled={readonly}
|
||||
scope={scope}
|
||||
value={value[variable]}
|
||||
onSelect={item => handleFormChange(variable, { ...item, type: FormTypeEnum.appSelector } as any)}
|
||||
onSelect={item => handleFormChange(variable, { ...item, type: FormTypeEnum.appSelector })}
|
||||
/>
|
||||
{fieldMoreInfo?.(formSchema)}
|
||||
{validating && changeKey === variable && <ValidatingTip />}
|
||||
@@ -475,7 +491,7 @@ function Form<
|
||||
{required && (
|
||||
<span className="ml-1 text-red-500">*</span>
|
||||
)}
|
||||
{tooltipContent}
|
||||
{infotipContent}
|
||||
</div>
|
||||
<VarReferencePicker
|
||||
zIndex={1001}
|
||||
@@ -483,7 +499,7 @@ function Form<
|
||||
isShowNodeName
|
||||
nodeId={nodeId || ''}
|
||||
value={value[variable] || []}
|
||||
onChange={item => handleFormChange(variable, item as any)}
|
||||
onChange={item => handleFormChange(variable, item)}
|
||||
filterVar={(varPayload) => {
|
||||
if (!scope)
|
||||
return true
|
||||
|
||||
+4
@@ -1235,6 +1235,10 @@ describe('Form', () => {
|
||||
expect(screen.getByText('Region'))!.toBeInTheDocument()
|
||||
expect(screen.getByText('Model'))!.toBeInTheDocument()
|
||||
expect(screen.getByText('Agree'))!.toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Enter your API key here'))!.toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Select region'))!.toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Choose model'))!.toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Agree tooltip'))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render required asterisk for radio, select, checkbox, and other field types', () => {
|
||||
|
||||
@@ -651,7 +651,6 @@ describe('Workflow edge event wiring', () => {
|
||||
await waitFor(() => {
|
||||
expect(toastInfoMock).toHaveBeenCalledWith(
|
||||
'workflow.versionHistory.action.restoreInProgress:{"userName":"Alice","versionName":"Version One"}',
|
||||
{ timeout: 3000 },
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -283,7 +283,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
|
||||
ns: 'workflow',
|
||||
userName: data.initiatorName,
|
||||
versionName: data.versionName || data.versionId,
|
||||
}), { timeout: 3000 })
|
||||
}))
|
||||
})
|
||||
}, [t])
|
||||
|
||||
|
||||
@@ -226,6 +226,11 @@ describe('node actions menu details', () => {
|
||||
const user = userEvent.setup()
|
||||
renderDropdownContent()
|
||||
|
||||
const deleteMenuItem = screen.getByText('common.operation.delete').closest('[role="menuitem"]')
|
||||
expect(deleteMenuItem).toHaveAttribute('data-variant', 'default')
|
||||
expect(deleteMenuItem).toHaveClass('text-text-secondary')
|
||||
expect(deleteMenuItem).toHaveClass('data-highlighted:text-text-destructive')
|
||||
|
||||
await user.click(screen.getByText('workflow.panel.runThisStep'))
|
||||
await user.click(screen.getByText('workflow.common.copy'))
|
||||
await user.click(screen.getByText('workflow.common.duplicate'))
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ChangeBlockMenuTrigger } from './change-block-menu-trigger'
|
||||
import {
|
||||
NODE_ACTIONS_MENU_DELETE_ITEM_CLASS_NAME,
|
||||
NODE_ACTIONS_MENU_ITEM_WITH_SHORTCUT_CLASS_NAME,
|
||||
NodeActionsMenuAbout,
|
||||
NodeActionsMenuItemContent,
|
||||
@@ -64,8 +65,7 @@ export function NodeActionsContextMenuContent(props: NodeActionsMenuProps) {
|
||||
{hasDeleteGroup && (
|
||||
<ContextMenuGroup>
|
||||
<ContextMenuItem
|
||||
variant="destructive"
|
||||
className={NODE_ACTIONS_MENU_ITEM_WITH_SHORTCUT_CLASS_NAME}
|
||||
className={NODE_ACTIONS_MENU_DELETE_ITEM_CLASS_NAME}
|
||||
onClick={model.handleDelete}
|
||||
>
|
||||
<NodeActionsMenuItemContent shortcut="workflow.delete">
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ChangeBlockMenuTrigger } from './change-block-menu-trigger'
|
||||
import {
|
||||
NODE_ACTIONS_MENU_DELETE_ITEM_CLASS_NAME,
|
||||
NODE_ACTIONS_MENU_ITEM_WITH_SHORTCUT_CLASS_NAME,
|
||||
NodeActionsMenuAbout,
|
||||
NodeActionsMenuItemContent,
|
||||
@@ -64,8 +65,7 @@ export function NodeActionsDropdownContent(props: NodeActionsMenuProps) {
|
||||
{hasDeleteGroup && (
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
className={NODE_ACTIONS_MENU_ITEM_WITH_SHORTCUT_CLASS_NAME}
|
||||
className={NODE_ACTIONS_MENU_DELETE_ITEM_CLASS_NAME}
|
||||
onClick={model.handleDelete}
|
||||
>
|
||||
<NodeActionsMenuItemContent shortcut="workflow.delete">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ShortcutKbd } from '@/app/components/workflow/shortcuts/shortcut-kbd'
|
||||
|
||||
export const NODE_ACTIONS_MENU_WIDTH_CLASS_NAME = 'w-[240px] rounded-lg'
|
||||
export const NODE_ACTIONS_MENU_ITEM_WITH_SHORTCUT_CLASS_NAME = 'w-auto justify-between gap-4'
|
||||
export const NODE_ACTIONS_MENU_DELETE_ITEM_CLASS_NAME = `${NODE_ACTIONS_MENU_ITEM_WITH_SHORTCUT_CLASS_NAME} text-text-secondary data-highlighted:bg-state-destructive-hover data-highlighted:text-text-destructive`
|
||||
|
||||
export function NodeActionsMenuItemContent({
|
||||
children,
|
||||
|
||||
@@ -17,9 +17,6 @@ import Item from './class-item'
|
||||
|
||||
const i18nPrefix = 'nodes.questionClassifiers'
|
||||
|
||||
// Layout constants
|
||||
const HANDLE_SIDE_WIDTH = 3 // Width offset for drag handle spacing
|
||||
|
||||
type Props = {
|
||||
nodeId: string
|
||||
list: Topic[]
|
||||
@@ -107,7 +104,7 @@ const ClassList: FC<Props> = ({
|
||||
{!collapsed && (
|
||||
<div
|
||||
ref={listContainerRef}
|
||||
className={cn('overflow-y-visible', `pl-${HANDLE_SIDE_WIDTH}`)}
|
||||
className="overflow-y-visible pl-3"
|
||||
>
|
||||
<ReactSortable
|
||||
list={list.map(item => ({ ...item }))}
|
||||
@@ -130,8 +127,7 @@ const ClassList: FC<Props> = ({
|
||||
<div
|
||||
key={item.id}
|
||||
className={cn(
|
||||
'group relative rounded-[10px] bg-components-panel-bg',
|
||||
`-ml-${HANDLE_SIDE_WIDTH} min-h-[40px] px-0 py-0`,
|
||||
'group relative -ml-3 min-h-[40px] rounded-[10px] bg-components-panel-bg px-0 py-0',
|
||||
)}
|
||||
style={{
|
||||
// Performance hint for browser
|
||||
|
||||
@@ -64,7 +64,6 @@ const LocaleLayout = async ({
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
enableColorScheme={false}
|
||||
>
|
||||
<NuqsAdapter>
|
||||
<TanstackQueryInitializer>
|
||||
|
||||
+11
-78
@@ -1,79 +1,12 @@
|
||||
@import './preflight.css' layer(base);
|
||||
@import './tailwind-core.css';
|
||||
|
||||
@import '@langgenius/dify-ui/styles.css';
|
||||
@import '../../themes/manual-light.css' layer(base);
|
||||
@import '../../themes/manual-dark.css' layer(base);
|
||||
@import './monaco-sticky-fix.css' layer(base);
|
||||
|
||||
@import '../components/base/action-button/index.css';
|
||||
@import '../components/base/badge/index.css';
|
||||
@import '../components/base/premium-badge/index.css';
|
||||
@import '../components/base/segmented-control/index.css';
|
||||
|
||||
@import 'tailwindcss';
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
html {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
html[data-changing-theme] * {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
[class*='code-'] {
|
||||
@apply font-mono;
|
||||
}
|
||||
|
||||
.text-gradient {
|
||||
background: linear-gradient(91.58deg, #2250f2 -29.55%, #0ebcf3 75.22%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
}
|
||||
|
||||
[data-theme='dark'] [data-hide-on-theme='dark'],
|
||||
[data-theme='light'] [data-hide-on-theme='light'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Shiki code block line numbers */
|
||||
.shiki-line-numbers code {
|
||||
counter-reset: line;
|
||||
}
|
||||
.shiki-line-numbers .line::before {
|
||||
counter-increment: line;
|
||||
content: counter(line);
|
||||
display: inline-block;
|
||||
width: 1rem;
|
||||
margin-right: 0.75rem;
|
||||
text-align: right;
|
||||
color: var(--color-text-quaternary);
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
@source '../../app';
|
||||
@source '../../context';
|
||||
@source '../../hooks';
|
||||
@source '../../../packages/dify-ui/src';
|
||||
@source '../../node_modules/streamdown/dist';
|
||||
@source '../../node_modules/@streamdown/math/dist';
|
||||
@source not '../../**/*.{spec,test}.{js,ts,jsx,tsx}';
|
||||
@source not '../../**/*.stories.{js,ts,jsx,tsx}';
|
||||
@source not '../../../packages/dify-ui/src/**/*.{spec,test}.{ts,tsx}';
|
||||
@source not '../../../packages/dify-ui/src/**/*.stories.{ts,tsx}';
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
/* Ensures Monaco sticky header and other sticky headers remain visible in dark mode */
|
||||
html[data-theme="dark"] .monaco-editor .sticky-widget {
|
||||
background-color: var(--color-components-sticky-header-bg) !important;
|
||||
border-bottom: 1px solid var(--color-components-sticky-header-border) !important;
|
||||
background-color: var(--color-monaco-sticky-header-bg) !important;
|
||||
border-bottom: 1px solid var(--color-monaco-sticky-header-border) !important;
|
||||
box-shadow: var(--vscode-editorStickyScroll-shadow) 0 4px 2px -2px !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .monaco-editor .sticky-line-content:hover {
|
||||
background-color: var(--color-components-sticky-header-bg-hover) !important;
|
||||
background-color: var(--color-monaco-sticky-header-bg-hover) !important;
|
||||
}
|
||||
|
||||
/* Monaco editor specific sticky scroll styles in dark mode */
|
||||
html[data-theme="dark"] .monaco-editor .sticky-line-root {
|
||||
background-color: var(--color-components-sticky-header-bg) !important;
|
||||
}
|
||||
background-color: var(--color-monaco-sticky-header-bg) !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Pre-bound icons plugin used by `web/`. Combines Iconify's heroicons and
|
||||
* remix icon packs with the project's custom public and vendor sprites.
|
||||
* Wired into Tailwind v4 via `@plugin './plugins/icons.ts';` in tailwind-core.css.
|
||||
*/
|
||||
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'
|
||||
|
||||
export default iconsPlugin({
|
||||
collections: {
|
||||
...getIconCollections(['heroicons', 'ri']),
|
||||
'custom-public': customPublicIcons,
|
||||
'custom-vender': customVenderIcons,
|
||||
},
|
||||
extraProperties: {
|
||||
width: '1rem',
|
||||
height: '1rem',
|
||||
display: 'block',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* `@tailwindcss/typography` configured with Dify's prose color tokens.
|
||||
* Injects `theme.typography` for v4 CSS-first via plugin `config` merge.
|
||||
*/
|
||||
import typographyPlugin from '@tailwindcss/typography'
|
||||
import typographyConfig from './typography-config.js'
|
||||
|
||||
const created = typographyPlugin()
|
||||
|
||||
export default {
|
||||
handler: created.handler,
|
||||
config: {
|
||||
theme: {
|
||||
typography: typographyConfig,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Tailwind CSS v4 shared core for `web/`. CSS-first: no tailwind.config.ts.
|
||||
*
|
||||
* Layers, JS plugins, project-only design tokens, and app-level CSS are
|
||||
* declared here. Environment-specific entry files declare source scanning.
|
||||
*/
|
||||
|
||||
@layer theme, base, components, utilities;
|
||||
|
||||
/* Tailwind core — selective layer imports skip preflight; we ship our own
|
||||
* (see ./preflight.css) so cross-app baseline stays controlled. */
|
||||
@import 'tailwindcss/theme.css' layer(theme);
|
||||
@import 'tailwindcss/utilities.css' layer(utilities);
|
||||
|
||||
/* Local preflight (replaces v3 `corePlugins.preflight: false`). */
|
||||
@import './preflight.css' layer(base);
|
||||
|
||||
/* Design system: palette overrides, semantic tokens, light/dark themes,
|
||||
* project utilities and components. */
|
||||
@import '@langgenius/dify-ui/styles.css';
|
||||
|
||||
/* Project-only runtime CSS variables (gradients and special masks). */
|
||||
@import '../../themes/manual-light.css' layer(base);
|
||||
@import '../../themes/manual-dark.css' layer(base);
|
||||
@import './monaco-sticky-fix.css' layer(base);
|
||||
|
||||
/* Component CSS using @apply / @utility. */
|
||||
@import '../components/base/action-button/index.css';
|
||||
@import '../components/base/badge/index.css';
|
||||
@import '../components/base/premium-badge/index.css';
|
||||
@import '../components/base/segmented-control/index.css';
|
||||
|
||||
/* ---------- JS plugins ------------------------------------------------ */
|
||||
@plugin './plugins/icons.ts';
|
||||
@plugin './plugins/typography.ts';
|
||||
|
||||
/* ---------- Project-only theme tokens --------------------------------- */
|
||||
@theme {
|
||||
/* Additional breakpoints layered on top of v4 defaults. */
|
||||
--breakpoint-mobile: 100px;
|
||||
--breakpoint-tablet: 640px;
|
||||
--breakpoint-pc: 769px;
|
||||
--breakpoint-2k: 2560px;
|
||||
|
||||
/* Custom animations. */
|
||||
--animate-spin-slow: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
/* Background-image / gradient utilities. Values resolve at runtime from
|
||||
* manual-light.css / manual-dark.css. Using @theme inline so the variable
|
||||
* indirection survives without re-declaring values at :root. */
|
||||
@theme inline {
|
||||
/* Flat color (used as `bg-background-gradient-bg-fill-chat-bubble-bg-3`). */
|
||||
--color-background-gradient-bg-fill-chat-bubble-bg-3: var(--color-background-gradient-bg-fill-chat-bubble-bg-3);
|
||||
|
||||
/* Gradients — registered under v4's --background-image-* namespace. */
|
||||
--background-image-chatbot-bg: var(--color-chatbot-bg);
|
||||
--background-image-chat-bubble-bg: var(--color-chat-bubble-bg);
|
||||
--background-image-chat-input-mask: var(--color-chat-input-mask);
|
||||
--background-image-workflow-process-bg: var(--color-workflow-process-bg);
|
||||
--background-image-workflow-process-paused-bg: var(--color-workflow-process-paused-bg);
|
||||
--background-image-workflow-run-failed-bg: var(--color-workflow-run-failed-bg);
|
||||
--background-image-workflow-batch-failed-bg: var(--color-workflow-batch-failed-bg);
|
||||
--background-image-mask-top2bottom-gray-50-to-transparent: var(--mask-top2bottom-gray-50-to-transparent);
|
||||
--background-image-marketplace-divider-bg: var(--color-marketplace-divider-bg);
|
||||
--background-image-marketplace-plugin-empty: var(--color-marketplace-plugin-empty);
|
||||
--background-image-toast-success-bg: var(--color-toast-success-bg);
|
||||
--background-image-toast-warning-bg: var(--color-toast-warning-bg);
|
||||
--background-image-toast-error-bg: var(--color-toast-error-bg);
|
||||
--background-image-toast-info-bg: var(--color-toast-info-bg);
|
||||
--background-image-app-detail-bg: var(--color-app-detail-bg);
|
||||
--background-image-app-detail-overlay-bg: var(--color-app-detail-overlay-bg);
|
||||
--background-image-dataset-chunk-process-success-bg: var(--color-dataset-chunk-process-success-bg);
|
||||
--background-image-dataset-chunk-process-error-bg: var(--color-dataset-chunk-process-error-bg);
|
||||
--background-image-dataset-chunk-detail-card-hover-bg: var(--color-dataset-chunk-detail-card-hover-bg);
|
||||
--background-image-dataset-child-chunk-expand-btn-bg: var(--color-dataset-child-chunk-expand-btn-bg);
|
||||
--background-image-dataset-option-card-blue-gradient: var(--color-dataset-option-card-blue-gradient);
|
||||
--background-image-dataset-option-card-purple-gradient: var(--color-dataset-option-card-purple-gradient);
|
||||
--background-image-dataset-option-card-orange-gradient: var(--color-dataset-option-card-orange-gradient);
|
||||
--background-image-dataset-chunk-list-mask-bg: var(--color-dataset-chunk-list-mask-bg);
|
||||
--background-image-line-divider-bg: var(--color-line-divider-bg);
|
||||
--background-image-dataset-warning-message-bg: var(--color-dataset-warning-message-bg);
|
||||
--background-image-price-premium-badge-background: var(--color-premium-badge-background);
|
||||
--background-image-premium-yearly-tip-text-background: var(--color-premium-yearly-tip-text-background);
|
||||
--background-image-price-premium-text-background: var(--color-premium-text-background);
|
||||
--background-image-price-enterprise-background: var(--color-price-enterprise-background);
|
||||
--background-image-grid-mask-background: var(--color-grid-mask-background);
|
||||
--background-image-node-data-source-bg: var(--color-node-data-source-bg);
|
||||
--background-image-tag-selector-mask-bg: var(--color-tag-selector-mask-bg);
|
||||
--background-image-tag-selector-mask-hover-bg: var(--color-tag-selector-mask-hover-bg);
|
||||
--background-image-pipeline-template-card-hover-bg: var(--color-pipeline-template-card-hover-bg);
|
||||
--background-image-pipeline-add-documents-title-bg: var(--color-pipeline-add-documents-title-bg);
|
||||
--background-image-billing-plan-title-bg: var(--color-billing-plan-title-bg);
|
||||
--background-image-billing-plan-card-premium-bg: var(--color-billing-plan-card-premium-bg);
|
||||
--background-image-billing-plan-card-enterprise-bg: var(--color-billing-plan-card-enterprise-bg);
|
||||
--background-image-knowledge-pipeline-creation-footer-bg: var(--color-knowledge-pipeline-creation-footer-bg);
|
||||
--background-image-progress-bar-indeterminate-stripe: var(--color-progress-bar-indeterminate-stripe);
|
||||
--background-image-chat-answer-human-input-form-divider-bg: var(--color-chat-answer-human-input-form-divider-bg);
|
||||
}
|
||||
|
||||
/* ---------- Backwards-compat: gray-200 default border color ----------- *
|
||||
* v4 changed the default border color to `currentColor`. Preserve the v3
|
||||
* baseline used throughout the codebase. */
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentcolor);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- App-level component CSS ----------------------------------- */
|
||||
@layer components {
|
||||
[class*='code-'] {
|
||||
@apply font-mono;
|
||||
}
|
||||
|
||||
.text-gradient {
|
||||
background: linear-gradient(91.58deg, #2250f2 -29.55%, #0ebcf3 75.22%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* Shiki code block line numbers */
|
||||
.shiki-line-numbers code {
|
||||
counter-reset: line;
|
||||
}
|
||||
.shiki-line-numbers .line::before {
|
||||
counter-increment: line;
|
||||
content: counter(line);
|
||||
display: inline-block;
|
||||
width: 1rem;
|
||||
margin-right: 0.75rem;
|
||||
text-align: right;
|
||||
color: var(--color-text-quaternary);
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
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 difyUIPreset from '@langgenius/dify-ui/tailwind-preset'
|
||||
import tailwindTypography from '@tailwindcss/typography'
|
||||
import typography from './typography.js'
|
||||
|
||||
const config = {
|
||||
presets: [difyUIPreset],
|
||||
theme: {
|
||||
typography,
|
||||
extend: {
|
||||
screens: {
|
||||
'mobile': '100px',
|
||||
'tablet': '640px',
|
||||
'pc': '769px',
|
||||
'2k': '2560px',
|
||||
},
|
||||
backgroundColor: {
|
||||
'background-gradient-bg-fill-chat-bubble-bg-3': 'var(--color-background-gradient-bg-fill-chat-bubble-bg-3)',
|
||||
},
|
||||
backgroundImage: {
|
||||
'chatbot-bg': 'var(--color-chatbot-bg)',
|
||||
'chat-bubble-bg': 'var(--color-chat-bubble-bg)',
|
||||
'chat-input-mask': 'var(--color-chat-input-mask)',
|
||||
'workflow-process-bg': 'var(--color-workflow-process-bg)',
|
||||
'workflow-process-paused-bg': 'var(--color-workflow-process-paused-bg)',
|
||||
'workflow-run-failed-bg': 'var(--color-workflow-run-failed-bg)',
|
||||
'workflow-batch-failed-bg': 'var(--color-workflow-batch-failed-bg)',
|
||||
'mask-top2bottom-gray-50-to-transparent': 'var(--mask-top2bottom-gray-50-to-transparent)',
|
||||
'marketplace-divider-bg': 'var(--color-marketplace-divider-bg)',
|
||||
'marketplace-plugin-empty': 'var(--color-marketplace-plugin-empty)',
|
||||
'toast-success-bg': 'var(--color-toast-success-bg)',
|
||||
'toast-warning-bg': 'var(--color-toast-warning-bg)',
|
||||
'toast-error-bg': 'var(--color-toast-error-bg)',
|
||||
'toast-info-bg': 'var(--color-toast-info-bg)',
|
||||
'app-detail-bg': 'var(--color-app-detail-bg)',
|
||||
'app-detail-overlay-bg': 'var(--color-app-detail-overlay-bg)',
|
||||
'dataset-chunk-process-success-bg': 'var(--color-dataset-chunk-process-success-bg)',
|
||||
'dataset-chunk-process-error-bg': 'var(--color-dataset-chunk-process-error-bg)',
|
||||
'dataset-chunk-detail-card-hover-bg': 'var(--color-dataset-chunk-detail-card-hover-bg)',
|
||||
'dataset-child-chunk-expand-btn-bg': 'var(--color-dataset-child-chunk-expand-btn-bg)',
|
||||
'dataset-option-card-blue-gradient': 'var(--color-dataset-option-card-blue-gradient)',
|
||||
'dataset-option-card-purple-gradient': 'var(--color-dataset-option-card-purple-gradient)',
|
||||
'dataset-option-card-orange-gradient': 'var(--color-dataset-option-card-orange-gradient)',
|
||||
'dataset-chunk-list-mask-bg': 'var(--color-dataset-chunk-list-mask-bg)',
|
||||
'line-divider-bg': 'var(--color-line-divider-bg)',
|
||||
'dataset-warning-message-bg': 'var(--color-dataset-warning-message-bg)',
|
||||
'price-premium-badge-background': 'var(--color-premium-badge-background)',
|
||||
'premium-yearly-tip-text-background': 'var(--color-premium-yearly-tip-text-background)',
|
||||
'price-premium-text-background': 'var(--color-premium-text-background)',
|
||||
'price-enterprise-background': 'var(--color-price-enterprise-background)',
|
||||
'grid-mask-background': 'var(--color-grid-mask-background)',
|
||||
'node-data-source-bg': 'var(--color-node-data-source-bg)',
|
||||
'tag-selector-mask-bg': 'var(--color-tag-selector-mask-bg)',
|
||||
'tag-selector-mask-hover-bg': 'var(--color-tag-selector-mask-hover-bg)',
|
||||
'pipeline-template-card-hover-bg': 'var(--color-pipeline-template-card-hover-bg)',
|
||||
'pipeline-add-documents-title-bg': 'var(--color-pipeline-add-documents-title-bg)',
|
||||
'billing-plan-title-bg': 'var(--color-billing-plan-title-bg)',
|
||||
'billing-plan-card-premium-bg': 'var(--color-billing-plan-card-premium-bg)',
|
||||
'billing-plan-card-enterprise-bg': 'var(--color-billing-plan-card-enterprise-bg)',
|
||||
'knowledge-pipeline-creation-footer-bg': 'var(--color-knowledge-pipeline-creation-footer-bg)',
|
||||
'progress-bar-indeterminate-stripe': 'var(--color-progress-bar-indeterminate-stripe)',
|
||||
'chat-answer-human-input-form-divider-bg': 'var(--color-chat-answer-human-input-form-divider-bg)',
|
||||
},
|
||||
animation: {
|
||||
'spin-slow': 'spin 2s linear infinite',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
tailwindTypography,
|
||||
iconsPlugin({
|
||||
collections: {
|
||||
...getIconCollections(['heroicons', 'ri']),
|
||||
'custom-public': customPublicIcons,
|
||||
'custom-vender': customVenderIcons,
|
||||
},
|
||||
extraProperties: {
|
||||
width: '1rem',
|
||||
height: '1rem',
|
||||
display: 'block',
|
||||
},
|
||||
}),
|
||||
],
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { Config } from 'tailwindcss'
|
||||
import commonConfig from './tailwind-common-config'
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
'./app/**/*.{js,ts,jsx,tsx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx}',
|
||||
'./context/**/*.{js,ts,jsx,tsx}',
|
||||
'../packages/dify-ui/src/**/*.{ts,tsx}',
|
||||
'./node_modules/streamdown/dist/*.js',
|
||||
'./node_modules/@streamdown/math/dist/*.js',
|
||||
'!./**/*.{spec,test}.{js,ts,jsx,tsx}',
|
||||
'!../packages/dify-ui/src/**/*.{spec,test}.{ts,tsx}',
|
||||
],
|
||||
...commonConfig,
|
||||
}
|
||||
|
||||
export default config
|
||||
@@ -1,4 +1,11 @@
|
||||
html[data-theme="dark"] {
|
||||
--color-monaco-sticky-header-bg: var(--color-components-panel-bg);
|
||||
--color-monaco-sticky-header-bg-hover: var(--color-components-panel-on-panel-item-bg-hover);
|
||||
--color-monaco-sticky-header-border: var(--color-components-panel-border);
|
||||
--vscode-editorStickyScroll-background: var(--color-monaco-sticky-header-bg);
|
||||
--vscode-editorStickyScrollHover-background: var(--color-monaco-sticky-header-bg-hover);
|
||||
--vscode-editorStickyScroll-border: var(--color-monaco-sticky-header-border);
|
||||
--vscode-editorStickyScroll-shadow: rgba(0, 0, 0, 0.6);
|
||||
--color-chatbot-bg: linear-gradient(180deg,
|
||||
rgba(34, 34, 37, 0.9) 0%,
|
||||
rgba(29, 29, 32, 0.9) 90.48%);
|
||||
|
||||
Reference in New Issue
Block a user