Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
9458b897
Unverified
Commit
9458b897
authored
Aug 31, 2023
by
Joel
Committed by
GitHub
Aug 31, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: siderbar operation support portal (#1061)
parent
d75e8aea
Changes
13
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
1971 additions
and
1587 deletions
+1971
-1587
index.tsx
web/app/components/base/confirm/index.tsx
+1
-1
index.tsx
web/app/components/base/portal-to-follow-elem/index.tsx
+156
-73
index.tsx
web/app/components/base/select-support-portal/index.tsx
+0
-73
index.tsx
web/app/components/explore/item-operation/index.tsx
+37
-16
style.module.css
web/app/components/explore/item-operation/style.module.css
+2
-4
index.tsx
web/app/components/explore/sidebar/app-nav-item/index.tsx
+9
-11
style.module.css
.../components/explore/sidebar/app-nav-item/style.module.css
+0
-8
index.tsx
web/app/components/explore/sidebar/index.tsx
+1
-1
index.tsx
web/app/components/share/chat/sidebar/list/index.tsx
+11
-44
item.tsx
web/app/components/share/chat/sidebar/list/item.tsx
+77
-0
style.module.css
web/app/components/share/chat/sidebar/list/style.module.css
+0
-7
package.json
web/package.json
+1
-0
yarn.lock
web/yarn.lock
+1676
-1349
No files found.
web/app/components/base/confirm/index.tsx
View file @
9458b897
...
...
@@ -34,7 +34,7 @@ export default function Confirm({
const
cancelTxt
=
cancelText
||
`
${
t
(
'common.operation.cancel'
)}
`
return
(
<
Transition
appear
show=
{
isShow
}
as=
{
Fragment
}
>
<
Dialog
as=
"div"
className=
"relative z-
10
"
onClose=
{
onClose
}
onClick=
{
e
=>
e
.
preventDefault
()
}
>
<
Dialog
as=
"div"
className=
"relative z-
[100]
"
onClose=
{
onClose
}
onClick=
{
e
=>
e
.
preventDefault
()
}
>
<
Transition
.
Child
as=
{
Fragment
}
enter=
"ease-out duration-300"
...
...
web/app/components/base/portal-to-follow-elem/index.tsx
View file @
9458b897
'use client'
import
{
useBoolean
}
from
'ahooks'
import
React
,
{
useEffect
,
useRef
,
useState
}
from
'react'
import
type
{
FC
}
from
'react'
import
{
createRoot
}
from
'react-dom/client'
type
IPortalToFollowElementProps
=
{
portalElem
:
React
.
ReactNode
children
:
React
.
ReactNode
controlShow
?:
number
controlHide
?:
number
import
React
from
'react'
import
{
FloatingPortal
,
autoUpdate
,
flip
,
offset
,
shift
,
useDismiss
,
useFloating
,
useFocus
,
useHover
,
useInteractions
,
useMergeRefs
,
useRole
,
}
from
'@floating-ui/react'
import
type
{
Placement
}
from
'@floating-ui/react'
type
PortalToFollowElemOptions
=
{
/*
* top, bottom, left, right
* start, end. Default is middle
* combine: top-start, top-end
*/
placement
?:
Placement
open
?:
boolean
offset
?:
number
onOpenChange
?:
(
open
:
boolean
)
=>
void
}
const
PortalToFollowElement
:
FC
<
IPortalToFollowElementProps
>
=
({
portalElem
,
export
function
usePortalToFollowElem
({
placement
=
'bottom'
,
open
,
offset
:
offsetValue
=
0
,
onOpenChange
:
setControlledOpen
,
}:
PortalToFollowElemOptions
=
{})
{
const
setOpen
=
setControlledOpen
const
data
=
useFloating
({
placement
,
open
,
onOpenChange
:
setOpen
,
whileElementsMounted
:
autoUpdate
,
middleware
:
[
offset
(
offsetValue
),
flip
({
crossAxis
:
placement
.
includes
(
'-'
),
fallbackAxisSideDirection
:
'start'
,
padding
:
5
,
}),
shift
({
padding
:
5
}),
],
})
const
context
=
data
.
context
const
hover
=
useHover
(
context
,
{
move
:
false
,
enabled
:
open
==
null
,
})
const
focus
=
useFocus
(
context
,
{
enabled
:
open
==
null
,
})
const
dismiss
=
useDismiss
(
context
)
const
role
=
useRole
(
context
,
{
role
:
'tooltip'
})
const
interactions
=
useInteractions
([
hover
,
focus
,
dismiss
,
role
])
return
React
.
useMemo
(
()
=>
({
open
,
setOpen
,
...
interactions
,
...
data
,
}),
[
open
,
setOpen
,
interactions
,
data
],
)
}
type
ContextType
=
ReturnType
<
typeof
usePortalToFollowElem
>
|
null
const
PortalToFollowElemContext
=
React
.
createContext
<
ContextType
>
(
null
)
export
function
usePortalToFollowElemContext
()
{
const
context
=
React
.
useContext
(
PortalToFollowElemContext
)
if
(
context
==
null
)
throw
new
Error
(
'PortalToFollowElem components must be wrapped in <PortalToFollowElem />'
)
return
context
}
export
function
PortalToFollowElem
({
children
,
controlShow
,
controlHide
,
})
=>
{
const
[
isShowContent
,
{
setTrue
:
showContent
,
setFalse
:
hideContent
,
toggle
:
toggleContent
}]
=
useBoolean
(
false
)
const
[
wrapElem
,
setWrapElem
]
=
useState
<
HTMLDivElement
|
null
>
(
null
)
useEffect
(()
=>
{
if
(
controlShow
)
showContent
()
},
[
controlShow
])
useEffect
(()
=>
{
if
(
controlHide
)
hideContent
()
},
[
controlHide
])
// todo use click outside hidden
const
triggerElemRef
=
useRef
<
HTMLDivElement
>
(
null
)
const
calLoc
=
()
=>
{
const
triggerElem
=
triggerElemRef
.
current
if
(
!
triggerElem
)
{
return
{
display
:
'none'
,
}
}
const
{
left
:
triggerLeft
,
top
:
triggerTop
,
height
,
}
=
triggerElem
.
getBoundingClientRect
()
return
{
position
:
'fixed'
,
left
:
triggerLeft
,
top
:
triggerTop
+
height
,
zIndex
:
999
,
}
}
...
options
}:
{
children
:
React
.
ReactNode
}
&
PortalToFollowElemOptions
)
{
// This can accept any props as options, e.g. `placement`,
// or other positioning options.
const
tooltip
=
usePortalToFollowElem
(
options
)
return
(
<
PortalToFollowElemContext
.
Provider
value=
{
tooltip
}
>
{
children
}
</
PortalToFollowElemContext
.
Provider
>
)
}
export
const
PortalToFollowElemTrigger
=
React
.
forwardRef
<
HTMLElement
,
React
.
HTMLProps
<
HTMLElement
>
&
{
asChild
?:
boolean
}
>
(({
children
,
asChild
=
false
,
...
props
},
propRef
)
=>
{
const
context
=
usePortalToFollowElemContext
()
const
childrenRef
=
(
children
as
any
).
ref
const
ref
=
useMergeRefs
([
context
.
refs
.
setReference
,
propRef
,
childrenRef
])
useEffect
(()
=>
{
if
(
isShowContent
)
{
const
holder
=
document
.
createElement
(
'div'
)
const
root
=
createRoot
(
holder
)
const
style
=
calLoc
()
root
.
render
(
<
div
style=
{
style
as
React
.
CSSProperties
}
>
{
portalElem
}
</
div
>,
)
document
.
body
.
appendChild
(
holder
)
setWrapElem
(
holder
)
console
.
log
(
holder
)
}
else
{
wrapElem
?.
remove
?.()
setWrapElem
(
null
)
}
},
[
isShowContent
])
// `asChild` allows the user to pass any element as the anchor
if
(
asChild
&&
React
.
isValidElement
(
children
))
{
return
React
.
cloneElement
(
children
,
context
.
getReferenceProps
({
ref
,
...
props
,
...
children
.
props
,
'data-state'
:
context
.
open
?
'open'
:
'closed'
,
}),
)
}
return
(
<
div
ref=
{
triggerElemRef
as
React
.
RefObject
<
HTMLDivElement
>
}
onClick=
{
toggleContent
}
>
<
div
ref=
{
ref
}
className=
'inline-block'
// The user can style the trigger based on the state
data
-
state=
{
context
.
open
?
'open'
:
'closed'
}
{
...
context
.
getReferenceProps
(
props
)}
>
{
children
}
</
div
>
)
}
})
PortalToFollowElemTrigger
.
displayName
=
'PortalToFollowElemTrigger'
export
const
PortalToFollowElemContent
=
React
.
forwardRef
<
HTMLDivElement
,
React
.
HTMLProps
<
HTMLDivElement
>
>
(({
style
,
...
props
},
propRef
)
=>
{
const
context
=
usePortalToFollowElemContext
()
const
ref
=
useMergeRefs
([
context
.
refs
.
setFloating
,
propRef
])
if
(
!
context
.
open
)
return
null
return
(
<
FloatingPortal
>
<
div
ref=
{
ref
}
style=
{
{
...
context
.
floatingStyles
,
...
style
,
}
}
{
...
context
.
getFloatingProps
(
props
)}
/>
</
FloatingPortal
>
)
})
export
default
React
.
memo
(
PortalToFollowElement
)
PortalToFollowElemContent
.
displayName
=
'PortalToFollowElemContent'
web/app/components/base/select-support-portal/index.tsx
deleted
100644 → 0
View file @
d75e8aea
'use client'
import
React
,
{
FC
,
useState
}
from
'react'
import
PortalToFollowElem
from
'../portal-to-follow-elem'
import
{
ChevronDownIcon
,
CheckIcon
}
from
'@heroicons/react/24/outline'
import
cn
from
'classnames'
export
interface
ISelectProps
<
T
>
{
value
:
T
items
:
{
value
:
T
,
name
:
string
}[]
onChange
:
(
value
:
T
)
=>
void
}
const
Select
:
FC
<
ISelectProps
<
string
|
number
>>
=
({
value
,
items
,
onChange
})
=>
{
const
[
controlHide
,
setControlHide
]
=
useState
(
0
)
const
itemsElement
=
items
.
map
(
item
=>
{
const
isSelected
=
item
.
value
===
value
return
(
<
div
key=
{
item
.
value
}
className=
{
cn
(
'relative h-9 leading-9 px-10 rounded-lg text-sm text-gray-700 hover:bg-gray-100'
)
}
onClick=
{
()
=>
{
onChange
(
item
.
value
)
setControlHide
(
Date
.
now
())
}
}
>
{
isSelected
&&
(
<
div
className=
'absolute left-4 top-1/2 translate-y-[-50%] flex items-center justify-center w-4 h-4 text-primary-600'
>
<
CheckIcon
width=
{
16
}
height=
{
16
}
></
CheckIcon
>
</
div
>
)
}
{
item
.
name
}
</
div
>
)
})
return
(
<
div
>
<
PortalToFollowElem
portalElem=
{
(
<
div
className=
'p-1 rounded-lg bg-white cursor-pointer'
style=
{
{
boxShadow
:
'0px 10px 15px -3px rgba(0, 0, 0, 0.1), 0px 4px 6px rgba(0, 0, 0, 0.05)'
}
}
>
{
itemsElement
}
</
div
>
)
}
controlHide=
{
controlHide
}
>
<
div
className=
'relative '
>
<
div
className=
'flex items-center h-9 px-3 gap-1 cursor-pointer hover:bg-gray-50'
>
<
div
className=
'text-sm text-gray-700'
>
{
items
.
find
(
i
=>
i
.
value
===
value
)?.
name
}
</
div
>
<
ChevronDownIcon
width=
{
16
}
height=
{
16
}
/>
</
div
>
{
/* <div
className='absolute z-50 left-0 top-9 p-1 w-[112px] rounded-lg bg-white'
style={{
boxShadow: '0px 10px 15px -3px rgba(0, 0, 0, 0.1), 0px 4px 6px rgba(0, 0, 0, 0.05)'
}}
>
{itemsElement}
</div> */
}
</
div
>
</
PortalToFollowElem
>
</
div
>
)
}
export
default
React
.
memo
(
Select
)
web/app/components/explore/item-operation/index.tsx
View file @
9458b897
'use client'
import
type
{
FC
}
from
'react'
import
React
from
'react'
import
React
,
{
useEffect
,
useRef
,
useState
}
from
'react'
import
cn
from
'classnames'
import
{
useTranslation
}
from
'react-i18next'
import
{
useBoolean
}
from
'ahooks'
import
{
Edit03
,
Pin02
,
Trash03
}
from
'../../base/icons/src/vender/line/general'
import
s
from
'./style.module.css'
import
Popover
from
'@/app/components/base/popover
'
import
{
PortalToFollowElem
,
PortalToFollowElemContent
,
PortalToFollowElemTrigger
}
from
'@/app/components/base/portal-to-follow-elem
'
export
type
IItemOperationProps
=
{
className
?:
string
isItemHovering
:
boolean
isPinned
:
boolean
isShowRenameConversation
?:
boolean
onRenameConversation
?:
()
=>
void
...
...
@@ -20,6 +22,7 @@ export type IItemOperationProps = {
const
ItemOperation
:
FC
<
IItemOperationProps
>
=
({
className
,
isItemHovering
,
isPinned
,
togglePin
,
isShowRenameConversation
,
...
...
@@ -28,13 +31,37 @@ const ItemOperation: FC<IItemOperationProps> = ({
onDelete
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
[
open
,
setOpen
]
=
useState
(
false
)
const
ref
=
useRef
(
null
)
const
[
isHovering
,
{
setTrue
:
setIsHovering
,
setFalse
:
setNotHovering
}]
=
useBoolean
(
false
)
useEffect
(()
=>
{
if
(
!
isItemHovering
&&
!
isHovering
)
setOpen
(
false
)
},
[
isItemHovering
,
isHovering
])
return
(
<
Popover
htmlContent=
{
<
div
className=
'w-full py-1'
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
()
}
}
>
<
PortalToFollowElem
open=
{
open
}
onOpenChange=
{
setOpen
}
placement=
'bottom-end'
offset=
{
4
}
>
<
PortalToFollowElemTrigger
onClick=
{
()
=>
setOpen
(
v
=>
!
v
)
}
>
<
div
className=
{
cn
(
className
,
s
.
btn
,
'h-6 w-6 rounded-md border-none py-1'
,
open
&&
`${s.open} !bg-gray-100 !shadow-none`
)
}
></
div
>
</
PortalToFollowElemTrigger
>
<
PortalToFollowElemContent
className=
"z-50"
>
<
div
ref=
{
ref
}
className=
{
'min-w-[120px] p-1 bg-white rounded-lg border border--gray-200 shadow-lg'
}
onMouseEnter=
{
setIsHovering
}
onMouseLeave=
{
setNotHovering
}
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
()
}
}
>
<
div
className=
{
cn
(
s
.
actionItem
,
'hover:bg-gray-50 group'
)
}
onClick=
{
togglePin
}
>
<
Pin02
className=
'shrink-0 w-4 h-4 text-gray-500'
/>
<
span
className=
{
s
.
actionName
}
>
{
isPinned
?
t
(
'explore.sidebar.action.unpin'
)
:
t
(
'explore.sidebar.action.pin'
)
}
</
span
>
...
...
@@ -51,15 +78,9 @@ const ItemOperation: FC<IItemOperationProps> = ({
<
span
className=
{
cn
(
s
.
actionName
,
s
.
deleteActionItemChild
)
}
>
{
t
(
'explore.sidebar.action.delete'
)
}
</
span
>
</
div
>
)
}
</
div
>
}
trigger=
'click'
position=
'br'
btnElement=
{
<
div
/>
}
btnClassName=
{
open
=>
cn
(
className
,
s
.
btn
,
'h-6 w-6 rounded-md border-none py-1'
,
open
&&
'!bg-gray-100 !shadow-none'
)
}
className=
{
'!w-[120px] !px-0 h-fit !z-20'
}
/>
</
PortalToFollowElemContent
>
</
PortalToFollowElem
>
)
}
export
default
React
.
memo
(
ItemOperation
)
web/app/components/explore/item-operation/style.module.css
View file @
9458b897
...
...
@@ -2,7 +2,6 @@
@apply
h-9
py-2
px-3
mx-1
flex
items-center
gap-2
rounded-lg
cursor-pointer;
}
.actionName
{
@apply
text-gray-700
text-sm;
}
...
...
@@ -19,14 +18,13 @@
mask-image
:
url(~@/assets/action.svg)
;
}
body
.btn
{
body
.btn.open
,
body
.btn
:hover
{
background
:
url(~@/assets/action.svg)
center
center
no-repeat
transparent
;
background-size
:
16px
16px
;
/* mask-image: ; */
}
body
.btn
:hover
{
/* background-image: ; */
background-color
:
#F2F4F7
;
}
...
...
web/app/components/explore/sidebar/app-nav-item/index.tsx
View file @
9458b897
'use client'
import
cn
from
'classnames'
import
React
,
{
useRef
}
from
'react'
import
{
useRouter
}
from
'next/navigation'
import
{
useHover
}
from
'ahooks'
import
s
from
'./style.module.css'
import
ItemOperation
from
'@/app/components/explore/item-operation'
import
AppIcon
from
'@/app/components/base/app-icon'
...
...
@@ -30,35 +33,30 @@ export default function AppNavItem({
}:
IAppNavItemProps
)
{
const
router
=
useRouter
()
const
url
=
`/explore/installed/
${
id
}
`
const
ref
=
useRef
(
null
)
const
isHovering
=
useHover
(
ref
)
return
(
<
div
ref=
{
ref
}
key=
{
id
}
className=
{
cn
(
s
.
item
,
isSelected
?
s
.
active
:
'hover:bg-gray-200'
,
'flex h-8 justify-between px-2 rounded-lg text-sm font-normal '
,
'flex h-8
items-center
justify-between px-2 rounded-lg text-sm font-normal '
,
)
}
onClick=
{
()
=>
{
router
.
push
(
url
)
// use Link causes popup item always trigger jump. Can not be solved by e.stopPropagation().
}
}
>
<
div
className=
'flex items-center space-x-2 w-0 grow'
>
{
/* <div
className={cn(
'shrink-0 mr-2 h-6 w-6 rounded-md border bg-[#D5F5F6]',
)}
style={{
borderColor: '0.5px solid rgba(0, 0, 0, 0.05)'
}}
/> */
}
<
AppIcon
size=
'tiny'
icon=
{
icon
}
background=
{
icon_background
}
/>
<
div
className=
'overflow-hidden text-ellipsis whitespace-nowrap'
>
{
name
}
</
div
>
</
div
>
{
<
div
className=
{
cn
(
s
.
opBtn
,
'shrink-0'
)
}
onClick=
{
e
=>
e
.
stopPropagation
()
}
>
<
div
className=
'shrink-0 h-6'
onClick=
{
e
=>
e
.
stopPropagation
()
}
>
<
ItemOperation
isPinned=
{
isPinned
}
isItemHovering=
{
isHovering
}
togglePin=
{
togglePin
}
isShowDelete=
{
!
uninstallable
&&
!
isSelected
}
onDelete=
{
()
=>
onDelete
(
id
)
}
...
...
web/app/components/explore/sidebar/app-nav-item/style.module.css
View file @
9458b897
...
...
@@ -6,12 +6,4 @@
background
:
#FFFFFF
;
color
:
#344054
;
font-weight
:
500
;
}
.opBtn
{
visibility
:
hidden
;
}
.item
:hover
.opBtn
{
visibility
:
visible
;
}
\ No newline at end of file
web/app/components/explore/sidebar/index.tsx
View file @
9458b897
...
...
@@ -106,7 +106,7 @@ const SideBar: FC<{
{
installedApps
.
length
>
0
&&
(
<
div
className=
'mt-10'
>
<
div
className=
'pl-2 text-xs text-gray-500 font-medium uppercase'
>
{
t
(
'explore.sidebar.workspace'
)
}
</
div
>
<
div
className=
'mt-3 space-y-1 overflow-y-auto overflow-x-hidden
pb-20
'
<
div
className=
'mt-3 space-y-1 overflow-y-auto overflow-x-hidden'
style=
{
{
maxHeight
:
'calc(100vh - 250px)'
,
}
}
...
...
web/app/components/share/chat/sidebar/list/index.tsx
View file @
9458b897
'use client'
import
type
{
FC
}
from
'react'
import
React
,
{
useRef
,
useState
}
from
'react'
import
{
ChatBubbleOvalLeftEllipsisIcon
,
}
from
'@heroicons/react/24/outline'
import
{
useBoolean
,
useInfiniteScroll
}
from
'ahooks'
import
{
ChatBubbleOvalLeftEllipsisIcon
as
ChatBubbleOvalLeftEllipsisSolidIcon
}
from
'@heroicons/react/24/solid'
import
cn
from
'classnames'
import
{
useTranslation
}
from
'react-i18next'
import
RenameModal
from
'../rename-modal'
import
s
from
'./style.module.css
'
import
Item
from
'./item
'
import
type
{
ConversationItem
}
from
'@/models/share'
import
{
fetchConversations
,
renameConversation
}
from
'@/service/share'
import
{
fetchConversations
as
fetchUniversalConversations
,
renameConversation
as
renameUniversalConversation
}
from
'@/service/universal-chat'
import
ItemOperation
from
'@/app/components/explore/item-operation'
import
Toast
from
'@/app/components/base/toast'
export
type
IListProps
=
{
...
...
@@ -52,7 +48,6 @@ const List: FC<IListProps> = ({
onDelete
,
})
=>
{
const
{
t
}
=
useTranslation
()
const
listRef
=
useRef
<
HTMLDivElement
>
(
null
)
useInfiniteScroll
(
...
...
@@ -130,45 +125,17 @@ const List: FC<IListProps> = ({
>
{
list
.
map
((
item
)
=>
{
const
isCurrent
=
item
.
id
===
currentId
const
ItemIcon
=
isCurrent
?
ChatBubbleOvalLeftEllipsisSolidIcon
:
ChatBubbleOvalLeftEllipsisIcon
return
(
<
div
onClick=
{
()
=>
onCurrentIdChange
(
item
.
id
)
}
<
Item
key=
{
item
.
id
}
className=
{
cn
(
s
.
item
,
isCurrent
?
'bg-primary-50 text-primary-600'
:
'text-gray-700 hover:bg-gray-200 hover:text-gray-700'
,
'group flex justify-between items-center rounded-md px-2 py-2 text-sm font-medium cursor-pointer'
,
)
}
>
<
div
className=
'flex items-center w-0 grow'
>
<
ItemIcon
className=
{
cn
(
isCurrent
?
'text-primary-600'
:
'text-gray-400 group-hover:text-gray-500'
,
'mr-3 h-5 w-5 flex-shrink-0'
,
)
}
aria
-
hidden=
"true"
/>
<
span
>
{
item
.
name
}
</
span
>
</
div
>
{
item
.
id
!==
'-1'
&&
(
<
div
className=
{
cn
(
s
.
opBtn
,
'shrink-0'
)
}
onClick=
{
e
=>
e
.
stopPropagation
()
}
>
<
ItemOperation
isPinned=
{
isPinned
}
togglePin=
{
()
=>
onPinChanged
(
item
.
id
)
}
isShowRenameConversation
onRenameConversation=
{
()
=>
showRename
(
item
)
}
isShowDelete
onDelete=
{
()
=>
onDelete
(
item
.
id
)
}
/>
</
div
>
)
}
</
div
>
item=
{
item
}
isCurrent=
{
isCurrent
}
onClick=
{
onCurrentIdChange
}
isPinned=
{
isPinned
}
togglePin=
{
onPinChanged
}
onDelete=
{
onDelete
}
onRenameConversation=
{
showRename
}
/>
)
})
}
{
isShowRename
&&
(
...
...
web/app/components/share/chat/sidebar/list/item.tsx
0 → 100644
View file @
9458b897
'use client'
import
type
{
FC
}
from
'react'
import
React
,
{
useRef
}
from
'react'
import
cn
from
'classnames'
import
{
ChatBubbleOvalLeftEllipsisIcon
as
ChatBubbleOvalLeftEllipsisSolidIcon
}
from
'@heroicons/react/24/solid'
import
{
ChatBubbleOvalLeftEllipsisIcon
,
}
from
'@heroicons/react/24/outline'
import
{
useHover
}
from
'ahooks'
import
ItemOperation
from
'@/app/components/explore/item-operation'
import
type
{
ConversationItem
}
from
'@/models/share'
export
type
IItemProps
=
{
onClick
:
(
id
:
string
)
=>
void
item
:
ConversationItem
isCurrent
:
boolean
isPinned
:
boolean
togglePin
:
(
id
:
string
)
=>
void
onDelete
:
(
id
:
string
)
=>
void
onRenameConversation
:
(
item
:
ConversationItem
)
=>
void
}
const
Item
:
FC
<
IItemProps
>
=
({
isCurrent
,
item
,
onClick
,
isPinned
,
togglePin
,
onDelete
,
onRenameConversation
,
})
=>
{
const
ItemIcon
=
isCurrent
?
ChatBubbleOvalLeftEllipsisSolidIcon
:
ChatBubbleOvalLeftEllipsisIcon
const
ref
=
useRef
(
null
)
const
isHovering
=
useHover
(
ref
)
return
(
<
div
ref=
{
ref
}
onClick=
{
()
=>
onClick
(
item
.
id
)
}
key=
{
item
.
id
}
className=
{
cn
(
isCurrent
?
'bg-primary-50 text-primary-600'
:
'text-gray-700 hover:bg-gray-200 hover:text-gray-700'
,
'group flex justify-between items-center rounded-md px-2 py-2 text-sm font-medium cursor-pointer'
,
)
}
>
<
div
className=
'flex items-center w-0 grow'
>
<
ItemIcon
className=
{
cn
(
isCurrent
?
'text-primary-600'
:
'text-gray-400 group-hover:text-gray-500'
,
'mr-3 h-5 w-5 flex-shrink-0'
,
)
}
aria
-
hidden=
"true"
/>
<
span
>
{
item
.
name
}
</
span
>
</
div
>
{
item
.
id
!==
'-1'
&&
(
<
div
className=
'shrink-0 h-6'
onClick=
{
e
=>
e
.
stopPropagation
()
}
>
<
ItemOperation
isPinned=
{
isPinned
}
isItemHovering=
{
isHovering
}
togglePin=
{
()
=>
togglePin
(
item
.
id
)
}
isShowDelete
isShowRenameConversation
onRenameConversation=
{
()
=>
onRenameConversation
(
item
)
}
onDelete=
{
()
=>
onDelete
(
item
.
id
)
}
/>
</
div
>
)
}
</
div
>
)
}
export
default
React
.
memo
(
Item
)
web/app/components/share/chat/sidebar/list/style.module.css
deleted
100644 → 0
View file @
d75e8aea
.opBtn
{
visibility
:
hidden
;
}
.item
:hover
.opBtn
{
visibility
:
visible
;
}
\ No newline at end of file
web/package.json
View file @
9458b897
...
...
@@ -16,6 +16,7 @@
"dependencies"
:
{
"@babel/runtime"
:
"^7.22.3"
,
"@emoji-mart/data"
:
"^1.1.2"
,
"@floating-ui/react"
:
"^0.25.2"
,
"@formatjs/intl-localematcher"
:
"^0.2.32"
,
"@headlessui/react"
:
"^1.7.13"
,
"@heroicons/react"
:
"^2.0.16"
,
...
...
web/yarn.lock
View file @
9458b897
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment