feat(web): ui
This commit is contained in:
@@ -2,12 +2,12 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2025-12-23 16:22:51
|
* @Date: 2025-12-23 16:22:51
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-25 15:53:57
|
* @Last Modified time: 2026-03-25 16:13:37
|
||||||
*/
|
*/
|
||||||
import { useEffect, useState, useRef, type FC } from 'react';
|
import { useEffect, useState, useRef, type FC } from 'react';
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
import { $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_HIGH, KEY_ENTER_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ESCAPE_COMMAND } from 'lexical';
|
import { $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_HIGH, KEY_ENTER_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ESCAPE_COMMAND } from 'lexical';
|
||||||
import { Space } from 'antd';
|
import { Space, Flex } from 'antd';
|
||||||
|
|
||||||
import { INSERT_VARIABLE_COMMAND, CLOSE_AUTOCOMPLETE_COMMAND } from '../commands';
|
import { INSERT_VARIABLE_COMMAND, CLOSE_AUTOCOMPLETE_COMMAND } from '../commands';
|
||||||
import type { NodeProperties } from '../../../types'
|
import type { NodeProperties } from '../../../types'
|
||||||
@@ -275,64 +275,59 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
|
|||||||
ref={popupRef}
|
ref={popupRef}
|
||||||
data-autocomplete-popup="true"
|
data-autocomplete-popup="true"
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
|
className="rb:fixed rb:z-1000 rb:py-1 rb:bg-white rb:rounded-xl rb:min-w-70 rb:max-h-50 rb:overflow-y-auto rb:transform-[translateY(-100%)] rb:shadow-[0px_2px_12px_0px_rgba(23,23,25,0.12)]"
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
|
||||||
top: popupPosition.top,
|
top: popupPosition.top,
|
||||||
left: popupPosition.left,
|
left: popupPosition.left,
|
||||||
zIndex: 1000,
|
|
||||||
background: 'white',
|
|
||||||
border: '1px solid #d9d9d9',
|
|
||||||
borderRadius: '6px',
|
|
||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
|
|
||||||
minWidth: '280px',
|
|
||||||
maxHeight: '200px',
|
|
||||||
overflowY: 'auto',
|
|
||||||
transform: 'translateY(-100%)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Object.entries(groupedSuggestions).map(([nodeId, nodeOptions], groupIndex) => {
|
<Flex vertical gap={12}>
|
||||||
const nodeName = nodeOptions[0]?.nodeData?.name || nodeId;
|
{Object.entries(groupedSuggestions).map(([nodeId, nodeOptions]) => {
|
||||||
return (
|
const nodeName = nodeOptions[0]?.nodeData?.name || nodeId;
|
||||||
<div key={nodeId}>
|
const nodeIcon = nodeOptions[0]?.nodeData?.icon;
|
||||||
{/* Divider between groups */}
|
return (
|
||||||
{groupIndex > 0 && <div style={{ height: '1px', background: '#f0f0f0', margin: '4px 0' }} />}
|
<div key={nodeId}>
|
||||||
{/* Group header with node name */}
|
<Flex align="center" gap={4} className="rb:px-3! rb:text-[12px] rb:py-1.25! rb:font-medium rb:text-[#5B6167]">
|
||||||
<div style={{ padding: '4px 12px', fontSize: '12px', color: '#999', fontWeight: 'bold' }}>
|
{nodeIcon && <img
|
||||||
{nodeName}
|
src={nodeIcon}
|
||||||
|
className="rb:size-3"
|
||||||
|
alt=""
|
||||||
|
/>}
|
||||||
|
{nodeName}
|
||||||
|
</Flex>
|
||||||
|
{nodeOptions.map((option) => {
|
||||||
|
const globalIndex = Object.values(groupedSuggestions).flat().indexOf(option);
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={option.key}
|
||||||
|
data-selected={selectedIndex === globalIndex}
|
||||||
|
className="rb:pl-6! rb:pr-3! rb:py-2! "
|
||||||
|
align="center"
|
||||||
|
justify="space-between"
|
||||||
|
style={{
|
||||||
|
cursor: option.disabled ? 'not-allowed' : 'pointer',
|
||||||
|
background: selectedIndex === globalIndex ? '#f0f8ff' : 'white',
|
||||||
|
opacity: option.disabled ? 0.5 : 1,
|
||||||
|
}}
|
||||||
|
onClick={() => !option.disabled && insertMention(option)}
|
||||||
|
onMouseEnter={() => setSelectedIndex(globalIndex)}
|
||||||
|
>
|
||||||
|
<Space size={4}>
|
||||||
|
<span className="rb:text-[#155EEF]">{option.isContext ? '📄' : `{x}`}</span>
|
||||||
|
<span>{option.label}</span>
|
||||||
|
</Space>
|
||||||
|
{option.dataType && (
|
||||||
|
<span className="rb:text-[#5B6167]">
|
||||||
|
{option.dataType}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
{nodeOptions.map((option) => {
|
);
|
||||||
const globalIndex = Object.values(groupedSuggestions).flat().indexOf(option);
|
})}
|
||||||
return (
|
</Flex>
|
||||||
<div
|
|
||||||
key={option.key}
|
|
||||||
data-selected={selectedIndex === globalIndex}
|
|
||||||
style={{
|
|
||||||
padding: '8px 12px',
|
|
||||||
cursor: option.disabled ? 'not-allowed' : 'pointer',
|
|
||||||
background: selectedIndex === globalIndex ? '#f0f8ff' : 'white',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
opacity: option.disabled ? 0.5 : 1,
|
|
||||||
}}
|
|
||||||
onClick={() => !option.disabled && insertMention(option)}
|
|
||||||
onMouseEnter={() => setSelectedIndex(globalIndex)}
|
|
||||||
>
|
|
||||||
<Space size={4}>
|
|
||||||
<span className="rb:text-[#155EEF]">{option.isContext ? '📄' : `{x}`}</span>
|
|
||||||
<span>{option.label}</span>
|
|
||||||
</Space>
|
|
||||||
{option.dataType && (
|
|
||||||
<span className="rb:text-[#5B6167]">
|
|
||||||
{option.dataType}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 15:40:13
|
* @Date: 2026-02-03 15:40:13
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-25 15:52:24
|
* @Last Modified time: 2026-03-25 16:03:59
|
||||||
*/
|
*/
|
||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
@@ -108,7 +108,14 @@ const VariableSelect: FC<VariableSelectProps> = ({
|
|||||||
* Format grouped options for Select component
|
* Format grouped options for Select component
|
||||||
*/
|
*/
|
||||||
const groupedOptions = Object.entries(groupedSuggestions).map(([_nodeId, suggestions]) => ({
|
const groupedOptions = Object.entries(groupedSuggestions).map(([_nodeId, suggestions]) => ({
|
||||||
label: suggestions[0].nodeData.name,
|
label: <Flex align="center" gap={4}>
|
||||||
|
{suggestions[0].nodeData.icon && <img
|
||||||
|
src={suggestions[0].nodeData.icon}
|
||||||
|
className="rb:size-3"
|
||||||
|
alt=""
|
||||||
|
/>}
|
||||||
|
{suggestions[0].nodeData.name}
|
||||||
|
</Flex>,
|
||||||
options: suggestions.map(s => ({
|
options: suggestions.map(s => ({
|
||||||
label: <Flex align="center" justify="space-between" gap={4}>
|
label: <Flex align="center" justify="space-between" gap={4}>
|
||||||
<Space size={8}>
|
<Space size={8}>
|
||||||
|
|||||||
Reference in New Issue
Block a user