mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
improvement(sidebar): collapsed sidebar UX, quick-create, hover consistency, and UI polish (#3807)
* improvement(sidebar): collapsed sidebar UX, quick-create, hover consistency, and UI polish Made-with: Cursor * fix(sidebar): use stable handlers for root workflow items instead of inline lambdas Made-with: Cursor * fix(sidebar): reset actionsOpen state before triggering rename in collapsed dropdown Made-with: Cursor
This commit is contained in:
@@ -188,7 +188,8 @@ html[data-sidebar-collapsed] .sidebar-container .sidebar-collapse-btn {
|
||||
--border-1: #e0e0e0; /* stronger border */
|
||||
--surface-6: #e5e5e5; /* popovers, elevated surfaces */
|
||||
--surface-7: #d9d9d9;
|
||||
--surface-active: #ececec; /* hover/active state */
|
||||
--surface-hover: #f2f2f2; /* hover state */
|
||||
--surface-active: #ececec; /* active/selected state */
|
||||
|
||||
--workflow-edge: #e0e0e0; /* workflow handles/edges - matches border-1 */
|
||||
|
||||
@@ -342,7 +343,8 @@ html[data-sidebar-collapsed] .sidebar-container .sidebar-collapse-btn {
|
||||
--border-1: #3d3d3d;
|
||||
--surface-6: #454545;
|
||||
--surface-7: #505050;
|
||||
--surface-active: #2c2c2c; /* hover/active state */
|
||||
--surface-hover: #262626; /* hover state */
|
||||
--surface-active: #2c2c2c; /* active/selected state */
|
||||
|
||||
--workflow-edge: #454545; /* workflow handles/edges - same as surface-6 in dark */
|
||||
|
||||
|
||||
@@ -1138,9 +1138,12 @@ export function Document({
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{effectiveDocumentName}
|
||||
</span>
|
||||
? This will permanently delete the document and all {documentData?.chunkCount ?? 0}{' '}
|
||||
chunk
|
||||
{documentData?.chunkCount === 1 ? '' : 's'} within it.{' '}
|
||||
?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently delete the document and all {documentData?.chunkCount ?? 0}{' '}
|
||||
chunk
|
||||
{documentData?.chunkCount === 1 ? '' : 's'} within it.
|
||||
</span>{' '}
|
||||
{documentData?.connectorId ? (
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This document is synced from a connector. Deleting it will permanently exclude it
|
||||
|
||||
@@ -1106,8 +1106,10 @@ export function KnowledgeBase({
|
||||
<p className='text-[var(--text-secondary)]'>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{knowledgeBaseName}</span>?
|
||||
The knowledge base and all {pagination.total} document
|
||||
{pagination.total === 1 ? '' : 's'} within it will be removed.{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
The knowledge base and all {pagination.total} document
|
||||
{pagination.total === 1 ? '' : 's'} within it will be removed.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-tertiary)]'>
|
||||
You can restore it from Recently Deleted in Settings.
|
||||
</span>
|
||||
@@ -1147,7 +1149,9 @@ export function KnowledgeBase({
|
||||
it from future syncs. To temporarily hide it from search, disable it instead.
|
||||
</span>
|
||||
) : (
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently delete the document.
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
)
|
||||
@@ -1177,7 +1181,10 @@ export function KnowledgeBase({
|
||||
<p className='text-[var(--text-secondary)]'>
|
||||
Are you sure you want to delete {selectedDocuments.size} document
|
||||
{selectedDocuments.size === 1 ? '' : 's'}?{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently delete the selected document
|
||||
{selectedDocuments.size === 1 ? '' : 's'}.
|
||||
</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -416,9 +416,11 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM
|
||||
<ModalBody>
|
||||
<div className='space-y-2'>
|
||||
<p className='text-[var(--text-secondary)]'>
|
||||
Are you sure you want to delete the "{selectedTag?.displayName}" tag? This will
|
||||
remove this tag from {selectedTagUsage?.documentCount || 0} document
|
||||
{selectedTagUsage?.documentCount !== 1 ? 's' : ''}.{' '}
|
||||
Are you sure you want to delete the "{selectedTag?.displayName}" tag?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will remove this tag from {selectedTagUsage?.documentCount || 0} document
|
||||
{selectedTagUsage?.documentCount !== 1 ? 's' : ''}.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -211,8 +211,13 @@ export function ConnectorsSection({
|
||||
<ModalHeader>Delete Connector</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[var(--text-secondary)] text-sm'>
|
||||
Are you sure you want to remove this connected source? Documents already synced will
|
||||
remain in the knowledge base.
|
||||
Are you sure you want to remove this connected source?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will stop future syncs from this source.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-tertiary)]'>
|
||||
Documents already synced will remain in the knowledge base.
|
||||
</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -47,10 +47,17 @@ export const DeleteKnowledgeBaseModal = memo(function DeleteKnowledgeBaseModal({
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{knowledgeBaseName}</span>?
|
||||
All associated documents, chunks, and embeddings will be removed.
|
||||
<span className='text-[var(--text-error)]'>
|
||||
All associated documents, chunks, and embeddings will be removed.
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
'Are you sure you want to delete this knowledge base? All associated documents, chunks, and embeddings will be removed.'
|
||||
<>
|
||||
Are you sure you want to delete this knowledge base?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
All associated documents, chunks, and embeddings will be removed.
|
||||
</span>
|
||||
</>
|
||||
)}{' '}
|
||||
<span className='text-[var(--text-tertiary)]'>
|
||||
You can restore it from Recently Deleted in Settings.
|
||||
|
||||
@@ -1268,7 +1268,9 @@ export const NotificationSettings = memo(function NotificationSettings({
|
||||
<ModalHeader>Delete Notification</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[var(--text-secondary)] text-caption'>
|
||||
This will permanently remove the notification and stop all deliveries.{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove the notification and stop all deliveries.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
|
||||
@@ -371,8 +371,10 @@ export function ApiKeys() {
|
||||
<ModalBody>
|
||||
<p className='text-[var(--text-secondary)]'>
|
||||
Deleting{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{deleteKey?.name}</span> will
|
||||
immediately revoke access for any integrations using it.{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{deleteKey?.name}</span>{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
will immediately revoke access for any integrations using it.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
|
||||
@@ -404,7 +404,10 @@ export function BYOK() {
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{PROVIDERS.find((p) => p.id === deleteConfirmProvider)?.name}
|
||||
</span>{' '}
|
||||
API key? This workspace will revert to using platform hosted keys.
|
||||
API key?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This workspace will revert to using platform hosted keys.
|
||||
</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -366,7 +366,9 @@ export function Copilot() {
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{deleteKey?.name || 'Unnamed Key'}
|
||||
</span>{' '}
|
||||
will immediately revoke access for any integrations using it.{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
will immediately revoke access for any integrations using it.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
|
||||
@@ -164,8 +164,10 @@ export function RowModal({ mode, isOpen, onClose, table, row, rowIds, onSuccess
|
||||
)}
|
||||
<p className='text-[var(--text-secondary)]'>
|
||||
Are you sure you want to delete{' '}
|
||||
{isSingleRow ? 'this row' : `these ${deleteCount} rows`}? This will permanently remove
|
||||
all data in {isSingleRow ? 'this row' : 'these rows'}.{' '}
|
||||
{isSingleRow ? 'this row' : `these ${deleteCount} rows`}?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove all data in {isSingleRow ? 'this row' : 'these rows'}.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
|
||||
@@ -1809,6 +1809,9 @@ export function Table({
|
||||
<p className='text-[var(--text-secondary)]'>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{tableData?.name}</span>?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
All {tableData?.rowCount ?? 0} rows will be removed.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-tertiary)]'>
|
||||
You can restore it from Recently Deleted in Settings.
|
||||
</span>
|
||||
@@ -1845,8 +1848,10 @@ export function Table({
|
||||
<ModalBody>
|
||||
<p className='text-[var(--text-secondary)]'>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{deletingColumn}</span>? This
|
||||
will remove all data in this column.{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{deletingColumn}</span>?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will remove all data in this column.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
|
||||
@@ -320,8 +320,10 @@ export function Tables() {
|
||||
<ModalBody>
|
||||
<p className='text-[var(--text-secondary)]'>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{activeTable?.name}</span>?
|
||||
All {activeTable?.rowCount} rows will be removed.{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{activeTable?.name}</span>?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
All {activeTable?.rowCount} rows will be removed.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-tertiary)]'>
|
||||
You can restore it from Recently Deleted in Settings.
|
||||
</span>
|
||||
|
||||
@@ -929,7 +929,7 @@ export function Chat() {
|
||||
>
|
||||
{shouldShowConfigureStartInputsButton && (
|
||||
<div
|
||||
className='flex flex-none cursor-pointer items-center whitespace-nowrap rounded-md border border-[var(--border-1)] bg-[var(--surface-5)] px-2.5 py-0.5 font-medium font-sans text-[var(--text-primary)] text-caption hover-hover:bg-[var(--surface-7)] dark:hover-hover:border-[var(--surface-7)] dark:hover-hover:bg-[var(--border-1)]'
|
||||
className='flex flex-none cursor-pointer items-center whitespace-nowrap rounded-md border border-[var(--border-1)] bg-[var(--surface-5)] px-2.5 py-0.5 font-medium font-sans text-[var(--text-primary)] text-caption hover-hover:bg-[var(--surface-active)]'
|
||||
title='Add chat inputs to Start block'
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
@@ -883,7 +883,7 @@ console.log(data);`
|
||||
</p>
|
||||
{missingFields.any && (
|
||||
<div
|
||||
className='flex flex-none cursor-pointer items-center whitespace-nowrap rounded-md border border-[var(--border-1)] bg-[var(--surface-5)] px-[9px] py-0.5 font-medium font-sans text-[var(--text-primary)] text-caption hover-hover:bg-[var(--surface-7)] dark:hover-hover:border-[var(--surface-7)] dark:hover-hover:bg-[var(--border-1)]'
|
||||
className='flex flex-none cursor-pointer items-center whitespace-nowrap rounded-md border border-[var(--border-1)] bg-[var(--surface-5)] px-[9px] py-0.5 font-medium font-sans text-[var(--text-primary)] text-caption hover-hover:bg-[var(--surface-active)]'
|
||||
title='Add required A2A input fields to Start block'
|
||||
onClick={handleAddA2AInputs}
|
||||
>
|
||||
|
||||
@@ -280,7 +280,7 @@ export const EnvVarDropdown: React.FC<EnvVarDropdownProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={visible} onOpenChange={(open) => !open && onClose?.()}>
|
||||
<Popover open={visible} onOpenChange={(open) => !open && onClose?.()} colorScheme='inverted'>
|
||||
<PopoverAnchor asChild>
|
||||
<div
|
||||
className={cn('pointer-events-none', className)}
|
||||
|
||||
@@ -553,7 +553,7 @@ export function FileUpload({
|
||||
return (
|
||||
<div
|
||||
key={fileKey}
|
||||
className='relative rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 hover-hover:border-[var(--surface-7)] hover-hover:bg-[var(--surface-5)] dark:bg-[var(--surface-5)] dark:hover-hover:bg-[var(--border-1)]'
|
||||
className='relative rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 hover-hover:bg-[var(--surface-active)] dark:bg-[var(--surface-5)]'
|
||||
>
|
||||
<div className='truncate pr-6 text-sm' title={file.name}>
|
||||
<span className='text-[var(--text-primary)]'>{truncateMiddle(file.name)}</span>
|
||||
|
||||
@@ -108,7 +108,7 @@ export function GroupedCheckboxList({
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'flex w-full cursor-pointer items-center justify-between rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 font-medium font-sans text-[var(--text-primary)] text-sm outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-[var(--surface-5)]',
|
||||
'hover-hover:border-[var(--surface-7)] hover-hover:bg-[var(--surface-5)] dark:hover-hover:border-[var(--surface-7)] dark:hover-hover:bg-[var(--border-1)]'
|
||||
'hover-hover:bg-[var(--surface-active)]'
|
||||
)}
|
||||
>
|
||||
<span className='flex flex-1 items-center gap-2 truncate text-[var(--text-muted)]'>
|
||||
|
||||
@@ -1061,6 +1061,7 @@ try {
|
||||
setShowSchemaParams(false)
|
||||
}
|
||||
}}
|
||||
colorScheme='inverted'
|
||||
>
|
||||
<PopoverAnchor asChild>
|
||||
<div
|
||||
@@ -1178,8 +1179,11 @@ try {
|
||||
<ModalHeader>Delete Custom Tool</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[var(--text-secondary)]'>
|
||||
This will permanently delete the tool and remove it from any workflows that are using
|
||||
it. <span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently delete the tool and remove it from any workflows that are
|
||||
using it.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -874,7 +874,10 @@ export const Panel = memo(function Panel() {
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{currentWorkflow?.name ?? 'this workflow'}
|
||||
</span>
|
||||
? All associated blocks, executions, and configuration will be removed.{' '}
|
||||
?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
All associated blocks, executions, and configuration will be removed.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-tertiary)]'>
|
||||
You can restore it from Recently Deleted in Settings.
|
||||
</span>
|
||||
|
||||
@@ -1822,7 +1822,7 @@ export function useWorkflowExecution() {
|
||||
try {
|
||||
const pointer = await loadExecutionPointer(reconnectWorkflowId)
|
||||
if (cleanupRan) return
|
||||
if (pointer && pointer.executionId) {
|
||||
if (pointer?.executionId) {
|
||||
executionId = pointer.executionId
|
||||
fromEventId = pointer.lastEventId
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MouseEvent as ReactMouseEvent } from 'react'
|
||||
import { type MouseEvent as ReactMouseEvent, useState } from 'react'
|
||||
import { Folder, MoreHorizontal, Plus } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import {
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/emcn'
|
||||
import { Pencil, SquareArrowUpRight } from '@/components/emcn/icons'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { ConversationListItem } from '@/app/workspace/[workspaceId]/components'
|
||||
import type { useHoverMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
|
||||
@@ -33,6 +34,7 @@ interface CollapsedSidebarMenuProps {
|
||||
interface CollapsedTaskFlyoutItemProps {
|
||||
task: { id: string; href: string; name: string; isActive?: boolean; isUnread?: boolean }
|
||||
isCurrentRoute: boolean
|
||||
isMenuOpen?: boolean
|
||||
isEditing?: boolean
|
||||
editValue?: string
|
||||
inputRef?: React.RefObject<HTMLInputElement | null>
|
||||
@@ -56,9 +58,9 @@ interface CollapsedWorkflowFlyoutItemProps {
|
||||
onEditValueChange?: (value: string) => void
|
||||
onEditKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void
|
||||
onEditBlur?: () => void
|
||||
onContextMenu?: (e: ReactMouseEvent, workflow: WorkflowMetadata) => void
|
||||
onMorePointerDown?: () => void
|
||||
onMoreClick?: (e: ReactMouseEvent<HTMLButtonElement>, workflow: WorkflowMetadata) => void
|
||||
onOpenInNewTab?: () => void
|
||||
onRename?: () => void
|
||||
canRename?: boolean
|
||||
}
|
||||
|
||||
const EDIT_ROW_CLASS =
|
||||
@@ -68,10 +70,12 @@ function FlyoutMoreButton({
|
||||
ariaLabel,
|
||||
onPointerDown,
|
||||
onClick,
|
||||
isVisible,
|
||||
}: {
|
||||
ariaLabel: string
|
||||
onPointerDown?: () => void
|
||||
onClick: (e: ReactMouseEvent<HTMLButtonElement>) => void
|
||||
isVisible?: boolean
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
@@ -79,7 +83,10 @@ function FlyoutMoreButton({
|
||||
aria-label={ariaLabel}
|
||||
onPointerDown={onPointerDown}
|
||||
onClick={onClick}
|
||||
className='-translate-y-1/2 absolute top-1/2 right-[8px] z-10 flex h-[18px] w-[18px] items-center justify-center rounded-[4px] opacity-0 transition-opacity hover:bg-[var(--surface-7)] focus-visible:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100'
|
||||
className={cn(
|
||||
'-translate-y-1/2 absolute top-1/2 right-[8px] z-10 flex h-[18px] w-[18px] items-center justify-center rounded-sm opacity-0 transition-opacity group-hover:opacity-100',
|
||||
isVisible && 'opacity-100'
|
||||
)}
|
||||
>
|
||||
<MoreHorizontal className='h-[16px] w-[16px] text-[var(--text-icon)]' />
|
||||
</button>
|
||||
@@ -154,7 +161,7 @@ export function CollapsedSidebarMenu({
|
||||
<button
|
||||
type='button'
|
||||
aria-label={ariaLabel}
|
||||
className='mx-0.5 flex h-[30px] items-center rounded-[8px] px-2 hover:bg-[var(--surface-active)]'
|
||||
className='mx-0.5 flex h-[30px] items-center rounded-[8px] px-2 hover-hover:bg-[var(--surface-hover)]'
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
@@ -180,6 +187,7 @@ export function CollapsedSidebarMenu({
|
||||
export function CollapsedTaskFlyoutItem({
|
||||
task,
|
||||
isCurrentRoute,
|
||||
isMenuOpen = false,
|
||||
isEditing = false,
|
||||
editValue,
|
||||
inputRef,
|
||||
@@ -221,12 +229,13 @@ export function CollapsedTaskFlyoutItem({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='group relative mx-0.5'>
|
||||
<div className='group relative'>
|
||||
<Link
|
||||
href={task.href}
|
||||
className={cn(
|
||||
'flex min-h-[30px] min-w-0 items-center rounded-[5px] px-2 py-[5px] pr-[30px] font-medium text-[12px] text-[var(--text-body)] hover:bg-[var(--surface-active)] group-focus-within:bg-[var(--surface-active)] group-hover:bg-[var(--surface-active)]',
|
||||
isCurrentRoute && 'bg-[var(--surface-active)]'
|
||||
'flex min-w-0 cursor-default select-none items-center rounded-[5px] px-2 py-2 pr-[30px] font-medium text-[var(--text-body)] text-caption outline-none transition-colors',
|
||||
!(isCurrentRoute || isMenuOpen) && 'group-hover:bg-[var(--surface-hover)]',
|
||||
(isCurrentRoute || isMenuOpen) && 'bg-[var(--surface-active)]'
|
||||
)}
|
||||
onContextMenu={
|
||||
task.id !== 'new' && onContextMenu ? (e) => onContextMenu(e, task.id) : undefined
|
||||
@@ -236,7 +245,9 @@ export function CollapsedTaskFlyoutItem({
|
||||
title={task.name}
|
||||
isActive={!!task.isActive}
|
||||
isUnread={!!task.isUnread}
|
||||
statusIndicatorClassName={!isCurrentRoute ? 'group-hover:hidden' : undefined}
|
||||
statusIndicatorClassName={
|
||||
!(isCurrentRoute || isMenuOpen) ? 'group-hover:hidden' : undefined
|
||||
}
|
||||
/>
|
||||
</Link>
|
||||
{showActions && (
|
||||
@@ -248,6 +259,7 @@ export function CollapsedTaskFlyoutItem({
|
||||
e.stopPropagation()
|
||||
onMoreClick?.(e, task.id)
|
||||
}}
|
||||
isVisible={isMenuOpen}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -265,11 +277,12 @@ export function CollapsedWorkflowFlyoutItem({
|
||||
onEditValueChange,
|
||||
onEditKeyDown,
|
||||
onEditBlur,
|
||||
onContextMenu,
|
||||
onMorePointerDown,
|
||||
onMoreClick,
|
||||
onOpenInNewTab,
|
||||
onRename,
|
||||
canRename = true,
|
||||
}: CollapsedWorkflowFlyoutItemProps) {
|
||||
const showActions = !!onMoreClick
|
||||
const hasActions = !!onOpenInNewTab || !!onRename
|
||||
const [actionsOpen, setActionsOpen] = useState(false)
|
||||
|
||||
if (isEditing) {
|
||||
return (
|
||||
@@ -299,28 +312,65 @@ export function CollapsedWorkflowFlyoutItem({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='group relative mx-0.5'>
|
||||
<div className='group relative'>
|
||||
<Link
|
||||
href={href}
|
||||
className={cn(
|
||||
'flex min-h-[30px] min-w-0 items-center gap-2 rounded-[5px] px-2 py-[5px] pr-[30px] font-medium text-[12px] text-[var(--text-body)] hover:bg-[var(--surface-active)] group-focus-within:bg-[var(--surface-active)] group-hover:bg-[var(--surface-active)]',
|
||||
isCurrentRoute && 'bg-[var(--surface-active)]'
|
||||
'flex min-w-0 cursor-default select-none items-center gap-2 rounded-[5px] px-2 py-2 pr-[30px] font-medium text-[var(--text-body)] text-caption outline-none transition-colors',
|
||||
!(isCurrentRoute || actionsOpen) && 'group-hover:bg-[var(--surface-hover)]',
|
||||
(isCurrentRoute || actionsOpen) && 'bg-[var(--surface-active)]'
|
||||
)}
|
||||
onContextMenu={onContextMenu ? (e) => onContextMenu(e, workflow) : undefined}
|
||||
onContextMenu={
|
||||
hasActions
|
||||
? (e) => {
|
||||
e.preventDefault()
|
||||
setActionsOpen(true)
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<WorkflowColorSwatch color={workflow.color} />
|
||||
<span className='min-w-0 flex-1 truncate'>{workflow.name}</span>
|
||||
</Link>
|
||||
{showActions && (
|
||||
<FlyoutMoreButton
|
||||
ariaLabel='Workflow options'
|
||||
onPointerDown={onMorePointerDown}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onMoreClick?.(e, workflow)
|
||||
{hasActions && (
|
||||
<DropdownMenuSub
|
||||
open={actionsOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setActionsOpen(false)
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<DropdownMenuSubTrigger
|
||||
aria-label='Workflow options'
|
||||
className='-translate-y-1/2 absolute top-1/2 right-[8px] z-10 h-[18px] w-[18px] min-w-0 justify-center gap-0 rounded-sm p-0 opacity-0 transition-opacity focus:bg-transparent group-hover:opacity-100 data-[state=open]:bg-transparent data-[state=open]:opacity-100 [&>svg:last-child]:hidden [&_svg]:pointer-events-auto [&_svg]:size-[16px]'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setActionsOpen((prev) => !prev)
|
||||
}}
|
||||
>
|
||||
<MoreHorizontal className='h-[16px] w-[16px] text-[var(--text-icon)]' />
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
{onOpenInNewTab && (
|
||||
<DropdownMenuItem onSelect={onOpenInNewTab}>
|
||||
<SquareArrowUpRight className='h-[14px] w-[14px]' />
|
||||
Open in new tab
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onRename && (
|
||||
<DropdownMenuItem
|
||||
disabled={!canRename}
|
||||
onSelect={(e) => {
|
||||
e.preventDefault()
|
||||
setActionsOpen(false)
|
||||
onRename()
|
||||
}}
|
||||
>
|
||||
<Pencil className='h-[14px] w-[14px]' />
|
||||
Rename
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
@@ -338,9 +388,9 @@ export function CollapsedFolderItems({
|
||||
onEditValueChange,
|
||||
onEditKeyDown,
|
||||
onEditBlur,
|
||||
onWorkflowContextMenu,
|
||||
onWorkflowMorePointerDown,
|
||||
onWorkflowMoreClick,
|
||||
onWorkflowOpenInNewTab,
|
||||
onWorkflowRename,
|
||||
canRenameWorkflow,
|
||||
}: {
|
||||
nodes: FolderTreeNode[]
|
||||
workflowsByFolder: Record<string, WorkflowMetadata[]>
|
||||
@@ -353,9 +403,9 @@ export function CollapsedFolderItems({
|
||||
onEditValueChange?: (value: string) => void
|
||||
onEditKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void
|
||||
onEditBlur?: () => void
|
||||
onWorkflowContextMenu?: (e: ReactMouseEvent, workflow: WorkflowMetadata) => void
|
||||
onWorkflowMorePointerDown?: () => void
|
||||
onWorkflowMoreClick?: (e: ReactMouseEvent<HTMLButtonElement>, workflow: WorkflowMetadata) => void
|
||||
onWorkflowOpenInNewTab?: (workflow: WorkflowMetadata) => void
|
||||
onWorkflowRename?: (workflow: WorkflowMetadata) => void
|
||||
canRenameWorkflow?: boolean
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
@@ -374,7 +424,7 @@ export function CollapsedFolderItems({
|
||||
|
||||
return (
|
||||
<DropdownMenuSub key={folder.id}>
|
||||
<DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubTrigger className='focus:bg-[var(--surface-hover)] data-[state=open]:bg-[var(--surface-hover)]'>
|
||||
<Folder className='h-[14px] w-[14px]' />
|
||||
<span className='truncate'>{folder.name}</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
@@ -391,9 +441,9 @@ export function CollapsedFolderItems({
|
||||
onEditValueChange={onEditValueChange}
|
||||
onEditKeyDown={onEditKeyDown}
|
||||
onEditBlur={onEditBlur}
|
||||
onWorkflowContextMenu={onWorkflowContextMenu}
|
||||
onWorkflowMorePointerDown={onWorkflowMorePointerDown}
|
||||
onWorkflowMoreClick={onWorkflowMoreClick}
|
||||
onWorkflowOpenInNewTab={onWorkflowOpenInNewTab}
|
||||
onWorkflowRename={onWorkflowRename}
|
||||
canRenameWorkflow={canRenameWorkflow}
|
||||
/>
|
||||
{folderWorkflows.map((workflow) => (
|
||||
<CollapsedWorkflowFlyoutItem
|
||||
@@ -408,9 +458,11 @@ export function CollapsedFolderItems({
|
||||
onEditValueChange={onEditValueChange}
|
||||
onEditKeyDown={onEditKeyDown}
|
||||
onEditBlur={onEditBlur}
|
||||
onContextMenu={onWorkflowContextMenu}
|
||||
onMorePointerDown={onWorkflowMorePointerDown}
|
||||
onMoreClick={onWorkflowMoreClick}
|
||||
onOpenInNewTab={
|
||||
onWorkflowOpenInNewTab ? () => onWorkflowOpenInNewTab(workflow) : undefined
|
||||
}
|
||||
onRename={onWorkflowRename ? () => onWorkflowRename(workflow) : undefined}
|
||||
canRename={canRenameWorkflow}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
|
||||
@@ -191,7 +191,7 @@ export function SettingsSidebar({
|
||||
<button
|
||||
type='button'
|
||||
onClick={handleBack}
|
||||
className='group mx-0.5 flex h-[30px] items-center gap-2 rounded-lg px-2 text-sm hover-hover:bg-[var(--surface-active)]'
|
||||
className='group mx-0.5 flex h-[30px] items-center gap-2 rounded-lg px-2 text-sm hover-hover:bg-[var(--surface-hover)]'
|
||||
>
|
||||
<div className='flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center text-[var(--text-icon)]'>
|
||||
<ChevronDown className='h-[10px] w-[10px] rotate-90' />
|
||||
@@ -259,7 +259,8 @@ export function SettingsSidebar({
|
||||
const active = activeSection === item.id
|
||||
const isLocked = item.requiresMax && !subscriptionAccess.hasUsableMaxAccess
|
||||
const itemClassName = cn(
|
||||
'group mx-0.5 flex h-[30px] items-center gap-2 rounded-[8px] px-2 text-[14px] hover:bg-[var(--surface-active)]',
|
||||
'group mx-0.5 flex h-[30px] items-center gap-2 rounded-[8px] px-2 text-[14px]',
|
||||
!active && 'hover-hover:bg-[var(--surface-hover)]',
|
||||
active && 'bg-[var(--surface-active)]'
|
||||
)
|
||||
const content = (
|
||||
|
||||
@@ -75,7 +75,10 @@ export function DeleteModal({
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{displayNames.join(', ')}
|
||||
</span>
|
||||
? All associated blocks, executions, and configuration will be removed.
|
||||
?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
All associated blocks, executions, and configuration will be removed.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -83,12 +86,21 @@ export function DeleteModal({
|
||||
return (
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{displayNames[0]}</span>? All
|
||||
associated blocks, executions, and configuration will be removed.
|
||||
<span className='font-medium text-[var(--text-primary)]'>{displayNames[0]}</span>?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
All associated blocks, executions, and configuration will be removed.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return 'Are you sure you want to delete this workflow? All associated blocks, executions, and configuration will be removed.'
|
||||
return (
|
||||
<>
|
||||
Are you sure you want to delete this workflow?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
All associated blocks, executions, and configuration will be removed.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (itemType === 'folder') {
|
||||
@@ -99,8 +111,11 @@ export function DeleteModal({
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{displayNames.join(', ')}
|
||||
</span>
|
||||
? This will permanently remove all workflows, logs, and knowledge bases within these
|
||||
folders.
|
||||
?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove all workflows, logs, and knowledge bases within these
|
||||
folders.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -108,12 +123,21 @@ export function DeleteModal({
|
||||
return (
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{displayNames[0]}</span>? This
|
||||
will permanently remove all associated workflows, logs, and knowledge bases.
|
||||
<span className='font-medium text-[var(--text-primary)]'>{displayNames[0]}</span>?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove all associated workflows, logs, and knowledge bases.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return 'Are you sure you want to delete this folder? This will permanently remove all associated workflows, logs, and knowledge bases.'
|
||||
return (
|
||||
<>
|
||||
Are you sure you want to delete this folder?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove all associated workflows, logs, and knowledge bases.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (itemType === 'task') {
|
||||
@@ -124,7 +148,10 @@ export function DeleteModal({
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{displayNames.length} tasks
|
||||
</span>
|
||||
? This will permanently remove all conversation history.
|
||||
?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove all conversation history.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -132,12 +159,21 @@ export function DeleteModal({
|
||||
return (
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{displayNames[0]}</span>? This
|
||||
will permanently remove all conversation history.
|
||||
<span className='font-medium text-[var(--text-primary)]'>{displayNames[0]}</span>?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove all conversation history.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return 'Are you sure you want to delete this task? This will permanently remove all conversation history.'
|
||||
return (
|
||||
<>
|
||||
Are you sure you want to delete this task?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove all conversation history.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (itemType === 'mixed') {
|
||||
@@ -148,12 +184,23 @@ export function DeleteModal({
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{displayNames.join(', ')}
|
||||
</span>
|
||||
? This will permanently remove all selected workflows and folders, including their
|
||||
contents.
|
||||
?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove all selected workflows and folders, including their
|
||||
contents.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return 'Are you sure you want to delete the selected items? This will permanently remove all selected workflows and folders, including their contents.'
|
||||
return (
|
||||
<>
|
||||
Are you sure you want to delete the selected items?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove all selected workflows and folders, including their
|
||||
contents.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// workspace type
|
||||
@@ -161,12 +208,22 @@ export function DeleteModal({
|
||||
return (
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{displayNames[0]}</span>? This
|
||||
will permanently remove all associated workflows, folders, logs, and knowledge bases.
|
||||
<span className='font-medium text-[var(--text-primary)]'>{displayNames[0]}</span>?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove all associated workflows, folders, logs, and knowledge
|
||||
bases.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return 'Are you sure you want to delete this workspace? This will permanently remove all associated workflows, folders, logs, and knowledge bases.'
|
||||
return (
|
||||
<>
|
||||
Are you sure you want to delete this workspace?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove all associated workflows, folders, logs, and knowledge bases.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -448,8 +448,11 @@ export function FolderItem({
|
||||
aria-label={`${folder.name} folder, ${isExpanded ? 'expanded' : 'collapsed'}`}
|
||||
className={clsx(
|
||||
'group mx-0.5 flex h-[30px] cursor-pointer items-center gap-2 rounded-lg px-2 text-sm',
|
||||
!isAnyDragActive && 'hover-hover:bg-[var(--surface-active)]',
|
||||
isSelected ? 'bg-[var(--surface-active)]' : '',
|
||||
!isSelected &&
|
||||
!isContextMenuOpen &&
|
||||
!isAnyDragActive &&
|
||||
'hover-hover:bg-[var(--surface-hover)]',
|
||||
(isSelected || isContextMenuOpen) && 'bg-[var(--surface-active)]',
|
||||
(isDragging || (isAnyDragActive && isSelected)) && 'opacity-50'
|
||||
)}
|
||||
onClick={handleClick}
|
||||
@@ -511,8 +514,9 @@ export function FolderItem({
|
||||
onPointerDown={handleMorePointerDown}
|
||||
onClick={handleMoreClick}
|
||||
className={clsx(
|
||||
'flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded-sm opacity-0 transition-opacity hover-hover:bg-[var(--surface-7)]',
|
||||
!isAnyDragActive && 'group-hover:opacity-100'
|
||||
'flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded-sm opacity-0 transition-opacity',
|
||||
!isAnyDragActive && 'group-hover:opacity-100',
|
||||
isContextMenuOpen && 'opacity-100'
|
||||
)}
|
||||
>
|
||||
<MoreHorizontal className='h-[16px] w-[16px] text-[var(--text-icon)]' />
|
||||
|
||||
@@ -386,8 +386,11 @@ export function WorkflowItem({
|
||||
data-item-id={workflow.id}
|
||||
className={clsx(
|
||||
'group mx-0.5 flex h-[30px] items-center gap-2 rounded-lg px-2 text-sm',
|
||||
active && 'bg-[var(--surface-active)]',
|
||||
!active && !isAnyDragActive && 'hover-hover:bg-[var(--surface-active)]',
|
||||
(active || isContextMenuOpen) && 'bg-[var(--surface-active)]',
|
||||
!active &&
|
||||
!isContextMenuOpen &&
|
||||
!isAnyDragActive &&
|
||||
'hover-hover:bg-[var(--surface-hover)]',
|
||||
isSelected && selectedWorkflows.size > 1 && !active && 'bg-[var(--surface-active)]',
|
||||
(isDragging || (isAnyDragActive && isSelected)) && 'opacity-50'
|
||||
)}
|
||||
@@ -445,8 +448,9 @@ export function WorkflowItem({
|
||||
onPointerDown={handleMorePointerDown}
|
||||
onClick={handleMoreClick}
|
||||
className={clsx(
|
||||
'flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded-sm opacity-0 transition-opacity hover-hover:bg-[var(--surface-7)]',
|
||||
!isAnyDragActive && 'group-hover:opacity-100'
|
||||
'flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded-sm opacity-0 transition-opacity',
|
||||
!isAnyDragActive && 'group-hover:opacity-100',
|
||||
isContextMenuOpen && 'opacity-100'
|
||||
)}
|
||||
>
|
||||
<MoreHorizontal className='h-[16px] w-[16px] text-[var(--text-icon)]' />
|
||||
|
||||
@@ -19,7 +19,8 @@ import {
|
||||
Plus,
|
||||
UserPlus,
|
||||
} from '@/components/emcn'
|
||||
import { getDisplayPlanName } from '@/lib/billing/plan-helpers'
|
||||
import { getDisplayPlanName, isFree } from '@/lib/billing/plan-helpers'
|
||||
import { isBillingEnabled } from '@/lib/core/config/feature-flags'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { ContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu'
|
||||
import { DeleteModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/delete-modal/delete-modal'
|
||||
@@ -27,6 +28,7 @@ import { CreateWorkspaceModal } from '@/app/workspace/[workspaceId]/w/components
|
||||
import { InviteModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/invite-modal'
|
||||
import { useSubscriptionData } from '@/hooks/queries/subscription'
|
||||
import { usePermissionConfig } from '@/hooks/use-permission-config'
|
||||
import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
|
||||
|
||||
const logger = createLogger('WorkspaceHeader')
|
||||
|
||||
@@ -131,9 +133,14 @@ export function WorkspaceHeader({
|
||||
}, [])
|
||||
|
||||
const { isInvitationsDisabled } = usePermissionConfig()
|
||||
const { data: subscriptionResponse } = useSubscriptionData()
|
||||
const rawPlanName = getDisplayPlanName(subscriptionResponse?.data?.plan)
|
||||
const planDisplayName = rawPlanName.includes('for Teams') ? rawPlanName : `${rawPlanName} Plan`
|
||||
const { data: subscriptionResponse } = useSubscriptionData({ enabled: isBillingEnabled })
|
||||
const { navigateToSettings } = useSettingsNavigation()
|
||||
const currentPlan = subscriptionResponse?.data?.plan
|
||||
const showPlanInfo = isBillingEnabled && typeof currentPlan !== 'undefined'
|
||||
const rawPlanName = showPlanInfo ? getDisplayPlanName(currentPlan) : ''
|
||||
const planDisplayName =
|
||||
showPlanInfo && rawPlanName.includes('for Teams') ? rawPlanName : `${rawPlanName} Plan`
|
||||
const isFreePlan = showPlanInfo && isFree(currentPlan)
|
||||
|
||||
// Listen for open-invite-modal event from context menu
|
||||
useEffect(() => {
|
||||
@@ -395,11 +402,30 @@ export function WorkspaceHeader({
|
||||
>
|
||||
{workspaceInitial}
|
||||
</div>
|
||||
<div className='flex min-w-0 flex-col'>
|
||||
<div className='flex min-w-0 flex-1 flex-col'>
|
||||
<span className='truncate font-medium text-[var(--text-primary)] text-small'>
|
||||
{activeWorkspace?.name || 'Loading...'}
|
||||
</span>
|
||||
<span className='text-[var(--text-tertiary)] text-xs'>{planDisplayName}</span>
|
||||
{showPlanInfo && (
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='truncate text-[var(--text-tertiary)] text-xs'>
|
||||
{planDisplayName}
|
||||
</span>
|
||||
{isFreePlan && (
|
||||
<button
|
||||
type='button'
|
||||
className='flex-shrink-0 rounded-full bg-[color-mix(in_srgb,var(--brand-accent)_16%,transparent)] px-2 py-0.5 font-medium text-[11px] text-[var(--brand-accent)] leading-none transition-opacity hover:opacity-85'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setIsWorkspaceMenuOpen(false)
|
||||
navigateToSettings({ section: 'subscription' })
|
||||
}}
|
||||
>
|
||||
Upgrade
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -463,7 +489,9 @@ export function WorkspaceHeader({
|
||||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
'group flex cursor-pointer select-none items-center gap-2 rounded-[5px] px-2 py-[5px] font-medium text-[var(--text-body)] text-caption outline-none transition-colors hover-hover:bg-[var(--surface-active)]',
|
||||
'group flex cursor-pointer select-none items-center gap-2 rounded-[5px] px-2 py-[5px] font-medium text-[var(--text-body)] text-caption outline-none transition-colors',
|
||||
workspace.id !== workspaceId &&
|
||||
'hover-hover:bg-[var(--surface-hover)]',
|
||||
workspace.id === workspaceId && 'bg-[var(--surface-active)]'
|
||||
)}
|
||||
onClick={() => onWorkspaceSwitch(workspace)}
|
||||
@@ -482,7 +510,7 @@ export function WorkspaceHeader({
|
||||
const rect = e.currentTarget.getBoundingClientRect()
|
||||
openContextMenuAt(workspace, rect.right, rect.top)
|
||||
}}
|
||||
className='flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded-sm opacity-0 transition-opacity hover-hover:bg-[var(--surface-7)] group-hover:opacity-100'
|
||||
className='flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded-sm opacity-0 transition-opacity group-hover:opacity-100'
|
||||
>
|
||||
<MoreHorizontal className='h-[14px] w-[14px] text-[var(--text-tertiary)]' />
|
||||
</button>
|
||||
@@ -496,7 +524,7 @@ export function WorkspaceHeader({
|
||||
<div className='mt-1 flex flex-col gap-0.5'>
|
||||
<button
|
||||
type='button'
|
||||
className='flex w-full cursor-pointer select-none items-center gap-2 rounded-[5px] px-2 py-[5px] font-medium text-[var(--text-body)] text-caption outline-none transition-colors hover-hover:bg-[var(--surface-active)] disabled:pointer-events-none disabled:opacity-50'
|
||||
className='flex w-full cursor-pointer select-none items-center gap-2 rounded-[5px] px-2 py-[5px] font-medium text-[var(--text-body)] text-caption outline-none transition-colors hover-hover:bg-[var(--surface-hover)] disabled:pointer-events-none disabled:opacity-50'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setIsWorkspaceMenuOpen(false)
|
||||
@@ -514,7 +542,7 @@ export function WorkspaceHeader({
|
||||
<DropdownMenuSeparator />
|
||||
<button
|
||||
type='button'
|
||||
className='flex w-full cursor-pointer select-none items-center gap-2 rounded-[5px] px-2 py-[5px] font-medium text-[var(--text-body)] text-caption outline-none transition-colors hover-hover:bg-[var(--surface-active)]'
|
||||
className='flex w-full cursor-pointer select-none items-center gap-2 rounded-[5px] px-2 py-[5px] font-medium text-[var(--text-body)] text-caption outline-none transition-colors hover-hover:bg-[var(--surface-hover)]'
|
||||
onClick={() => {
|
||||
setIsInviteModalOpen(true)
|
||||
setIsWorkspaceMenuOpen(false)
|
||||
|
||||
@@ -113,6 +113,7 @@ const SidebarTaskItem = memo(function SidebarTaskItem({
|
||||
isSelected,
|
||||
isActive,
|
||||
isUnread,
|
||||
isMenuOpen,
|
||||
showCollapsedTooltips,
|
||||
onMultiSelectClick,
|
||||
onContextMenu,
|
||||
@@ -124,6 +125,7 @@ const SidebarTaskItem = memo(function SidebarTaskItem({
|
||||
isSelected: boolean
|
||||
isActive: boolean
|
||||
isUnread: boolean
|
||||
isMenuOpen: boolean
|
||||
showCollapsedTooltips: boolean
|
||||
onMultiSelectClick: (taskId: string, shiftKey: boolean, metaKey: boolean) => void
|
||||
onContextMenu: (e: React.MouseEvent, taskId: string) => void
|
||||
@@ -136,8 +138,10 @@ const SidebarTaskItem = memo(function SidebarTaskItem({
|
||||
<Link
|
||||
href={task.href}
|
||||
className={cn(
|
||||
'group mx-0.5 flex h-[30px] items-center gap-2 rounded-lg px-2 text-sm hover-hover:bg-[var(--surface-active)]',
|
||||
(isCurrentRoute || isSelected) && 'bg-[var(--surface-active)]'
|
||||
'group mx-0.5 flex h-[30px] items-center gap-2 rounded-lg px-2 text-sm',
|
||||
!(isCurrentRoute || isSelected || isMenuOpen) &&
|
||||
'hover-hover:bg-[var(--surface-hover)]',
|
||||
(isCurrentRoute || isSelected || isMenuOpen) && 'bg-[var(--surface-active)]'
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (task.id === 'new') return
|
||||
@@ -177,7 +181,10 @@ const SidebarTaskItem = memo(function SidebarTaskItem({
|
||||
e.stopPropagation()
|
||||
onMoreClick(e, task.id)
|
||||
}}
|
||||
className='flex h-[18px] w-[18px] items-center justify-center rounded-sm opacity-0 hover-hover:bg-[var(--surface-7)] group-hover:opacity-100'
|
||||
className={cn(
|
||||
'flex h-[18px] w-[18px] items-center justify-center rounded-sm opacity-0 group-hover:opacity-100',
|
||||
isMenuOpen && 'opacity-100'
|
||||
)}
|
||||
>
|
||||
<MoreHorizontal className='h-[16px] w-[16px] text-[var(--text-icon)]' />
|
||||
</button>
|
||||
@@ -214,8 +221,8 @@ const SidebarNavItem = memo(function SidebarNavItem({
|
||||
onContextMenu?: (e: React.MouseEvent, href: string) => void
|
||||
}) {
|
||||
const Icon = item.icon
|
||||
const baseClasses =
|
||||
'group flex h-[30px] items-center gap-2 rounded-lg mx-0.5 px-2 text-sm hover-hover:bg-[var(--surface-active)]'
|
||||
const baseClasses = 'group flex h-[30px] items-center gap-2 rounded-lg mx-0.5 px-2 text-sm'
|
||||
const hoverClasses = !active ? 'hover-hover:bg-[var(--surface-hover)]' : ''
|
||||
const activeClasses = active ? 'bg-[var(--surface-active)]' : ''
|
||||
|
||||
const content = (
|
||||
@@ -230,7 +237,7 @@ const SidebarNavItem = memo(function SidebarNavItem({
|
||||
href={item.href}
|
||||
data-item-id={item.id}
|
||||
data-tour={`nav-${item.id}`}
|
||||
className={`${baseClasses} ${activeClasses}`}
|
||||
className={`${baseClasses} ${hoverClasses} ${activeClasses}`}
|
||||
onClick={
|
||||
item.onClick
|
||||
? (e) => {
|
||||
@@ -249,7 +256,7 @@ const SidebarNavItem = memo(function SidebarNavItem({
|
||||
type='button'
|
||||
data-item-id={item.id}
|
||||
data-tour={`nav-${item.id}`}
|
||||
className={`${baseClasses} ${activeClasses}`}
|
||||
className={`${baseClasses} ${hoverClasses} ${activeClasses}`}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
{content}
|
||||
@@ -490,6 +497,11 @@ export const Sidebar = memo(function Sidebar() {
|
||||
taskIds: [],
|
||||
names: [],
|
||||
})
|
||||
const [menuOpenTaskId, setMenuOpenTaskId] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTaskContextMenuOpen) setMenuOpenTaskId(null)
|
||||
}, [isTaskContextMenuOpen])
|
||||
|
||||
const captureTaskSelection = useCallback((taskId: string) => {
|
||||
const { selectedTasks, selectTaskOnly } = useFolderStore.getState()
|
||||
@@ -507,6 +519,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
const handleTaskContextMenu = useCallback(
|
||||
(e: React.MouseEvent, taskId: string) => {
|
||||
captureTaskSelection(taskId)
|
||||
setMenuOpenTaskId(taskId)
|
||||
tasksHover.setLocked(true)
|
||||
preventTaskDismiss()
|
||||
handleTaskContextMenuBase(e)
|
||||
@@ -528,6 +541,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
}
|
||||
tasksHover.setLocked(true)
|
||||
captureTaskSelection(taskId)
|
||||
setMenuOpenTaskId(taskId)
|
||||
const rect = e.currentTarget.getBoundingClientRect()
|
||||
handleTaskContextMenuBase({
|
||||
preventDefault: () => {},
|
||||
@@ -545,77 +559,6 @@ export const Sidebar = memo(function Sidebar() {
|
||||
]
|
||||
)
|
||||
|
||||
const {
|
||||
isOpen: isCollapsedWorkflowContextMenuOpen,
|
||||
position: collapsedWorkflowContextMenuPosition,
|
||||
menuRef: collapsedWorkflowMenuRef,
|
||||
handleContextMenu: handleCollapsedWorkflowContextMenuBase,
|
||||
closeMenu: closeCollapsedWorkflowContextMenu,
|
||||
preventDismiss: preventCollapsedWorkflowDismiss,
|
||||
} = useContextMenu()
|
||||
|
||||
const collapsedWorkflowContextMenuRef = useRef<{
|
||||
workflowId: string
|
||||
workflowName: string
|
||||
} | null>(null)
|
||||
|
||||
const captureCollapsedWorkflowSelection = useCallback(
|
||||
(workflow: { id: string; name: string }) => {
|
||||
collapsedWorkflowContextMenuRef.current = {
|
||||
workflowId: workflow.id,
|
||||
workflowName: workflow.name,
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const handleCollapsedWorkflowContextMenu = useCallback(
|
||||
(e: React.MouseEvent, workflow: { id: string; name: string }) => {
|
||||
captureCollapsedWorkflowSelection(workflow)
|
||||
workflowsHover.setLocked(true)
|
||||
preventCollapsedWorkflowDismiss()
|
||||
handleCollapsedWorkflowContextMenuBase(e)
|
||||
},
|
||||
[
|
||||
captureCollapsedWorkflowSelection,
|
||||
handleCollapsedWorkflowContextMenuBase,
|
||||
preventCollapsedWorkflowDismiss,
|
||||
workflowsHover,
|
||||
]
|
||||
)
|
||||
|
||||
const handleCollapsedWorkflowMorePointerDown = useCallback(() => {
|
||||
if (isCollapsedWorkflowContextMenuOpen) {
|
||||
preventCollapsedWorkflowDismiss()
|
||||
}
|
||||
}, [isCollapsedWorkflowContextMenuOpen, preventCollapsedWorkflowDismiss])
|
||||
|
||||
const handleCollapsedWorkflowMoreClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement>, workflow: { id: string; name: string }) => {
|
||||
if (isCollapsedWorkflowContextMenuOpen) {
|
||||
closeCollapsedWorkflowContextMenu()
|
||||
return
|
||||
}
|
||||
|
||||
workflowsHover.setLocked(true)
|
||||
captureCollapsedWorkflowSelection(workflow)
|
||||
const rect = e.currentTarget.getBoundingClientRect()
|
||||
handleCollapsedWorkflowContextMenuBase({
|
||||
preventDefault: () => {},
|
||||
stopPropagation: () => {},
|
||||
clientX: rect.right,
|
||||
clientY: rect.top,
|
||||
} as React.MouseEvent)
|
||||
},
|
||||
[
|
||||
isCollapsedWorkflowContextMenuOpen,
|
||||
closeCollapsedWorkflowContextMenu,
|
||||
captureCollapsedWorkflowSelection,
|
||||
handleCollapsedWorkflowContextMenuBase,
|
||||
workflowsHover,
|
||||
]
|
||||
)
|
||||
|
||||
const { handleDuplicateWorkspace: duplicateWorkspace } = useDuplicateWorkspace({
|
||||
workspaceId,
|
||||
})
|
||||
@@ -859,10 +802,6 @@ export const Sidebar = memo(function Sidebar() {
|
||||
itemType: 'workflow',
|
||||
onSave: async (workflowIdToRename, name) => {
|
||||
await updateWorkflow(workflowIdToRename, { name })
|
||||
collapsedWorkflowContextMenuRef.current = {
|
||||
workflowId: workflowIdToRename,
|
||||
workflowName: name,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -871,8 +810,8 @@ export const Sidebar = memo(function Sidebar() {
|
||||
}, [isTaskContextMenuOpen, taskFlyoutRename.editingId, tasksHover.setLocked])
|
||||
|
||||
useEffect(() => {
|
||||
workflowsHover.setLocked(isCollapsedWorkflowContextMenuOpen || !!workflowFlyoutRename.editingId)
|
||||
}, [isCollapsedWorkflowContextMenuOpen, workflowFlyoutRename.editingId, workflowsHover.setLocked])
|
||||
workflowsHover.setLocked(!!workflowFlyoutRename.editingId)
|
||||
}, [workflowFlyoutRename.editingId, workflowsHover.setLocked])
|
||||
|
||||
const handleTaskOpenInNewTab = useCallback(() => {
|
||||
const { taskIds: ids } = contextMenuSelectionRef.current
|
||||
@@ -902,22 +841,20 @@ export const Sidebar = memo(function Sidebar() {
|
||||
taskFlyoutRename.startRename({ id: taskId, name: task.name })
|
||||
}, [taskFlyoutRename, tasks, tasksHover])
|
||||
|
||||
const handleCollapsedWorkflowOpenInNewTab = useCallback(() => {
|
||||
const workflow = collapsedWorkflowContextMenuRef.current
|
||||
if (!workflow) return
|
||||
window.open(
|
||||
`/workspace/${workspaceId}/w/${workflow.workflowId}`,
|
||||
'_blank',
|
||||
'noopener,noreferrer'
|
||||
)
|
||||
}, [workspaceId])
|
||||
const handleCollapsedWorkflowOpenInNewTab = useCallback(
|
||||
(workflow: { id: string }) => {
|
||||
window.open(`/workspace/${workspaceId}/w/${workflow.id}`, '_blank', 'noopener,noreferrer')
|
||||
},
|
||||
[workspaceId]
|
||||
)
|
||||
|
||||
const handleStartCollapsedWorkflowRename = useCallback(() => {
|
||||
const workflow = collapsedWorkflowContextMenuRef.current
|
||||
if (!workflow) return
|
||||
workflowsHover.setLocked(true)
|
||||
workflowFlyoutRename.startRename({ id: workflow.workflowId, name: workflow.workflowName })
|
||||
}, [workflowFlyoutRename, workflowsHover])
|
||||
const handleCollapsedWorkflowRename = useCallback(
|
||||
(workflow: { id: string; name: string }) => {
|
||||
workflowsHover.setLocked(true)
|
||||
workflowFlyoutRename.startRename({ id: workflow.id, name: workflow.name })
|
||||
},
|
||||
[workflowFlyoutRename, workflowsHover]
|
||||
)
|
||||
|
||||
const [hasOverflowTop, setHasOverflowTop] = useState(false)
|
||||
const [hasOverflowBottom, setHasOverflowBottom] = useState(false)
|
||||
@@ -1237,7 +1174,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<div className='relative flex h-[30px] items-center'>
|
||||
<Link
|
||||
href={`/workspace/${workspaceId}/home`}
|
||||
className='sidebar-collapse-hide sidebar-collapse-remove flex h-[30px] items-center rounded-[8px] px-1.5 hover:bg-[var(--surface-active)]'
|
||||
className='sidebar-collapse-hide sidebar-collapse-remove flex h-[30px] items-center rounded-[8px] px-1.5 hover-hover:bg-[var(--surface-hover)]'
|
||||
tabIndex={isCollapsed ? -1 : 0}
|
||||
>
|
||||
{brand.logoUrl ? (
|
||||
@@ -1259,7 +1196,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<button
|
||||
type='button'
|
||||
onClick={toggleCollapsed}
|
||||
className='sidebar-collapse-show group absolute left-0 flex h-[30px] w-[30px] items-center justify-center rounded-[8px] hover:bg-[var(--surface-active)]'
|
||||
className='sidebar-collapse-show group absolute left-0 flex h-[30px] w-[30px] items-center justify-center rounded-[8px] hover-hover:bg-[var(--surface-hover)]'
|
||||
aria-label='Expand sidebar'
|
||||
tabIndex={isCollapsed ? 0 : -1}
|
||||
>
|
||||
@@ -1291,7 +1228,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
type='button'
|
||||
onClick={toggleCollapsed}
|
||||
className={cn(
|
||||
'sidebar-collapse-btn ml-auto flex h-[30px] items-center justify-center overflow-hidden rounded-lg transition-all duration-200 hover-hover:bg-[var(--surface-active)]',
|
||||
'sidebar-collapse-btn ml-auto flex h-[30px] items-center justify-center overflow-hidden rounded-lg transition-all duration-200 hover-hover:bg-[var(--surface-hover)]',
|
||||
isCollapsed ? 'w-0 opacity-0' : 'w-[30px] opacity-100'
|
||||
)}
|
||||
aria-label='Collapse sidebar'
|
||||
@@ -1388,7 +1325,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='h-[18px] w-[18px] rounded-sm p-0 hover-hover:bg-[var(--surface-active)]'
|
||||
className='h-[18px] w-[18px] rounded-sm p-0 hover-hover:bg-[var(--surface-hover)]'
|
||||
onClick={handleNewTask}
|
||||
>
|
||||
<Plus className='h-[16px] w-[16px]' />
|
||||
@@ -1420,6 +1357,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
key={task.id}
|
||||
task={task}
|
||||
isCurrentRoute={task.id !== 'new' && pathname === task.href}
|
||||
isMenuOpen={menuOpenTaskId === task.id}
|
||||
isEditing={task.id === taskFlyoutRename.editingId}
|
||||
editValue={taskFlyoutRename.value}
|
||||
inputRef={taskFlyoutRename.inputRef}
|
||||
@@ -1472,6 +1410,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
isSelected={isSelected}
|
||||
isActive={!!task.isActive}
|
||||
isUnread={!!task.isUnread}
|
||||
isMenuOpen={menuOpenTaskId === task.id}
|
||||
showCollapsedTooltips={showCollapsedTooltips}
|
||||
onMultiSelectClick={handleTaskClick}
|
||||
onContextMenu={handleTaskContextMenu}
|
||||
@@ -1484,7 +1423,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<button
|
||||
type='button'
|
||||
onClick={handleSeeMoreTasks}
|
||||
className='mx-0.5 flex h-[30px] items-center gap-2 rounded-lg px-2 text-[var(--text-icon)] text-sm hover-hover:bg-[var(--surface-active)]'
|
||||
className='mx-0.5 flex h-[30px] items-center gap-2 rounded-lg px-2 text-[var(--text-icon)] text-sm hover-hover:bg-[var(--surface-hover)]'
|
||||
>
|
||||
<MoreHorizontal className='h-[16px] w-[16px] flex-shrink-0' />
|
||||
<span className='font-base'>See more</span>
|
||||
@@ -1511,7 +1450,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='h-[18px] w-[18px] rounded-sm p-0 hover-hover:bg-[var(--surface-active)]'
|
||||
className='h-[18px] w-[18px] rounded-sm p-0 hover-hover:bg-[var(--surface-hover)]'
|
||||
disabled={!canEdit}
|
||||
>
|
||||
{isImporting || isCreatingFolder ? (
|
||||
@@ -1551,7 +1490,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='h-[18px] w-[18px] rounded-sm p-0 hover-hover:bg-[var(--surface-active)]'
|
||||
className='h-[18px] w-[18px] rounded-sm p-0 hover-hover:bg-[var(--surface-hover)]'
|
||||
onClick={handleCreateWorkflow}
|
||||
disabled={isCreatingWorkflow || !canEdit}
|
||||
>
|
||||
@@ -1594,9 +1533,9 @@ export const Sidebar = memo(function Sidebar() {
|
||||
onEditValueChange={workflowFlyoutRename.setValue}
|
||||
onEditKeyDown={workflowFlyoutRename.handleKeyDown}
|
||||
onEditBlur={handleWorkflowRenameBlur}
|
||||
onWorkflowContextMenu={handleCollapsedWorkflowContextMenu}
|
||||
onWorkflowMorePointerDown={handleCollapsedWorkflowMorePointerDown}
|
||||
onWorkflowMoreClick={handleCollapsedWorkflowMoreClick}
|
||||
onWorkflowOpenInNewTab={handleCollapsedWorkflowOpenInNewTab}
|
||||
onWorkflowRename={handleCollapsedWorkflowRename}
|
||||
canRenameWorkflow={canEdit}
|
||||
/>
|
||||
{(workflowsByFolder.root || []).map((workflow) => (
|
||||
<CollapsedWorkflowFlyoutItem
|
||||
@@ -1611,9 +1550,9 @@ export const Sidebar = memo(function Sidebar() {
|
||||
onEditValueChange={workflowFlyoutRename.setValue}
|
||||
onEditKeyDown={workflowFlyoutRename.handleKeyDown}
|
||||
onEditBlur={handleWorkflowRenameBlur}
|
||||
onContextMenu={handleCollapsedWorkflowContextMenu}
|
||||
onMorePointerDown={handleCollapsedWorkflowMorePointerDown}
|
||||
onMoreClick={handleCollapsedWorkflowMoreClick}
|
||||
onOpenInNewTab={() => handleCollapsedWorkflowOpenInNewTab(workflow)}
|
||||
onRename={() => handleCollapsedWorkflowRename(workflow)}
|
||||
canRename={canEdit}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
@@ -1655,7 +1594,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<button
|
||||
type='button'
|
||||
data-item-id='help'
|
||||
className='group mx-0.5 flex h-[30px] items-center gap-2 rounded-[8px] px-2 text-[14px] hover:bg-[var(--surface-active)]'
|
||||
className='group mx-0.5 flex h-[30px] items-center gap-2 rounded-[8px] px-2 text-[14px] hover-hover:bg-[var(--surface-hover)]'
|
||||
>
|
||||
<HelpCircle className='h-[16px] w-[16px] flex-shrink-0 text-[var(--text-icon)]' />
|
||||
<span className='sidebar-collapse-hide truncate font-base text-[var(--text-body)]'>
|
||||
@@ -1732,22 +1671,6 @@ export const Sidebar = memo(function Sidebar() {
|
||||
disableDelete={!canEdit}
|
||||
/>
|
||||
|
||||
<ContextMenu
|
||||
isOpen={isCollapsedWorkflowContextMenuOpen}
|
||||
position={collapsedWorkflowContextMenuPosition}
|
||||
menuRef={collapsedWorkflowMenuRef}
|
||||
onClose={closeCollapsedWorkflowContextMenu}
|
||||
onOpenInNewTab={handleCollapsedWorkflowOpenInNewTab}
|
||||
onRename={handleStartCollapsedWorkflowRename}
|
||||
onDelete={noop}
|
||||
showOpenInNewTab={true}
|
||||
showRename={true}
|
||||
showDuplicate={false}
|
||||
showColorChange={false}
|
||||
showDelete={false}
|
||||
disableRename={!canEdit}
|
||||
/>
|
||||
|
||||
{/* Task Delete Confirmation Modal */}
|
||||
<DeleteModal
|
||||
isOpen={isTaskDeleteModalOpen}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Input } from '../input/input'
|
||||
import { Popover, PopoverAnchor, PopoverContent, PopoverScrollArea } from '../popover/popover'
|
||||
|
||||
const comboboxVariants = cva(
|
||||
'flex w-full rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 font-sans font-medium text-[var(--text-primary)] placeholder:text-[var(--text-muted)] outline-none focus-visible:border-[var(--text-muted)] disabled:cursor-not-allowed disabled:opacity-50 hover-hover:bg-[var(--surface-7)] dark:hover-hover:border-[var(--surface-7)] dark:hover-hover:bg-[var(--border-1)]',
|
||||
'flex w-full rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 font-sans font-medium text-[var(--text-primary)] placeholder:text-[var(--text-muted)] outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
@@ -520,7 +520,7 @@ const Combobox = memo(
|
||||
<Input
|
||||
ref={inputRef}
|
||||
className={cn(
|
||||
'w-full pr-10 font-medium transition-colors hover-hover:bg-[var(--surface-7)] dark:hover-hover:border-[var(--surface-7)] dark:hover-hover:bg-[var(--border-1)]',
|
||||
'w-full pr-10 font-medium transition-colors',
|
||||
(overlayContent || SelectedIcon) && 'text-transparent caret-foreground',
|
||||
SelectedIcon && !overlayContent && 'pl-7',
|
||||
open && 'focus-visible:border-[var(--border-1)]',
|
||||
@@ -747,8 +747,8 @@ const Combobox = memo(
|
||||
className={cn(
|
||||
'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-1.5 font-medium font-sans',
|
||||
size === 'sm' ? 'py-[5px] text-caption' : 'py-1.5 text-sm',
|
||||
'hover-hover:bg-[var(--border-1)]',
|
||||
(isHighlighted || isSelected) && 'bg-[var(--border-1)]',
|
||||
'hover-hover:bg-[var(--surface-active)]',
|
||||
(isHighlighted || isSelected) && 'bg-[var(--surface-active)]',
|
||||
option.disabled && 'cursor-not-allowed opacity-50'
|
||||
)}
|
||||
>
|
||||
@@ -787,8 +787,8 @@ const Combobox = memo(
|
||||
className={cn(
|
||||
'relative flex cursor-pointer select-none items-center rounded-sm px-1.5 font-medium font-sans',
|
||||
size === 'sm' ? 'py-[5px] text-caption' : 'py-1.5 text-sm',
|
||||
'hover-hover:bg-[var(--border-1)]',
|
||||
!multiSelectValues?.length && 'bg-[var(--border-1)]'
|
||||
'hover-hover:bg-[var(--surface-active)]',
|
||||
!multiSelectValues?.length && 'bg-[var(--surface-active)]'
|
||||
)}
|
||||
>
|
||||
<span className='flex-1 truncate text-[var(--text-primary)]'>
|
||||
@@ -821,8 +821,8 @@ const Combobox = memo(
|
||||
className={cn(
|
||||
'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-1.5 font-medium font-sans',
|
||||
size === 'sm' ? 'py-[5px] text-caption' : 'py-1.5 text-sm',
|
||||
'hover-hover:bg-[var(--border-1)]',
|
||||
(isHighlighted || isSelected) && 'bg-[var(--border-1)]',
|
||||
'hover-hover:bg-[var(--surface-active)]',
|
||||
(isHighlighted || isSelected) && 'bg-[var(--surface-active)]',
|
||||
option.disabled && 'cursor-not-allowed opacity-50'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -40,7 +40,7 @@ import { cn } from '@/lib/core/utils/cn'
|
||||
* Matches the combobox and input styling patterns.
|
||||
*/
|
||||
const datePickerVariants = cva(
|
||||
'flex w-full rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 font-sans font-medium text-[var(--text-primary)] placeholder:text-[var(--text-muted)] outline-none focus-visible:border-[var(--text-muted)] disabled:cursor-not-allowed disabled:opacity-50 hover-hover:border-[var(--surface-7)] hover-hover:bg-[var(--surface-5)] dark:hover-hover:border-[var(--surface-7)] dark:hover-hover:bg-[var(--border-1)]',
|
||||
'flex w-full rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 font-sans font-medium text-[var(--text-primary)] placeholder:text-[var(--text-muted)] outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
@@ -26,7 +26,7 @@ import { cn } from '@/lib/core/utils/cn'
|
||||
* Currently supports a 'default' variant.
|
||||
*/
|
||||
const inputVariants = cva(
|
||||
'flex w-full touch-manipulation rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 font-medium font-sans text-sm text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none focus-visible:border-[var(--text-muted)] disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'flex w-full touch-manipulation rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 font-medium font-sans text-sm text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
@@ -116,8 +116,8 @@ const STYLES = {
|
||||
/** Interactive state styles: default, secondary (brand), inverted (dark bg in light mode) */
|
||||
states: {
|
||||
default: {
|
||||
active: 'bg-[var(--border-1)]',
|
||||
hover: 'hover-hover:bg-[var(--border-1)]',
|
||||
active: 'bg-[var(--surface-active)]',
|
||||
hover: 'hover-hover:bg-[var(--surface-active)]',
|
||||
},
|
||||
secondary: {
|
||||
active: 'bg-[var(--brand-secondary)] text-white [&_svg]:text-white',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
|
||||
const textareaVariants = cva(
|
||||
'flex w-full touch-manipulation rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-2 font-medium font-sans text-sm text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none focus-visible:border-[var(--text-muted)] resize-none overflow-auto disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'flex w-full touch-manipulation rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-2 font-medium font-sans text-sm text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none resize-none overflow-auto disabled:cursor-not-allowed disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
@@ -40,7 +40,7 @@ import { cn } from '@/lib/core/utils/cn'
|
||||
* Matches the input and combobox styling patterns.
|
||||
*/
|
||||
const timePickerVariants = cva(
|
||||
'flex w-full rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 font-sans font-medium text-[var(--text-primary)] placeholder:text-[var(--text-muted)] outline-none focus-visible:border-[var(--text-muted)] disabled:cursor-not-allowed disabled:opacity-50 hover-hover:border-[var(--surface-7)] hover-hover:bg-[var(--surface-5)] dark:hover-hover:border-[var(--surface-7)] dark:hover-hover:bg-[var(--border-1)] transition-colors',
|
||||
'flex w-full rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 font-sans font-medium text-[var(--text-primary)] placeholder:text-[var(--text-muted)] outline-none disabled:cursor-not-allowed disabled:opacity-50 transition-colors',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
@@ -256,7 +256,7 @@ const TimePicker = React.forwardRef<HTMLDivElement, TimePickerProps>(
|
||||
<div className='flex items-center gap-1.5'>
|
||||
<input
|
||||
ref={hourInputRef}
|
||||
className='w-[40px] rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-1.5 py-[5px] text-center font-medium font-sans text-[var(--text-primary)] text-small outline-none transition-colors placeholder:text-[var(--text-muted)] focus-visible:border-[var(--text-muted)]'
|
||||
className='w-[40px] rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-1.5 py-[5px] text-center font-medium font-sans text-[var(--text-primary)] text-small outline-none transition-colors placeholder:text-[var(--text-muted)]'
|
||||
value={hour}
|
||||
onChange={handleHourChange}
|
||||
onBlur={handleHourBlur}
|
||||
@@ -268,7 +268,7 @@ const TimePicker = React.forwardRef<HTMLDivElement, TimePickerProps>(
|
||||
/>
|
||||
<span className='font-medium text-[var(--text-muted)] text-small'>:</span>
|
||||
<input
|
||||
className='w-[40px] rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-1.5 py-[5px] text-center font-medium font-sans text-[var(--text-primary)] text-small outline-none transition-colors placeholder:text-[var(--text-muted)] focus-visible:border-[var(--text-muted)]'
|
||||
className='w-[40px] rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-1.5 py-[5px] text-center font-medium font-sans text-[var(--text-primary)] text-small outline-none transition-colors placeholder:text-[var(--text-muted)]'
|
||||
value={minute}
|
||||
onChange={handleMinuteChange}
|
||||
onBlur={handleMinuteBlur}
|
||||
@@ -291,7 +291,7 @@ const TimePicker = React.forwardRef<HTMLDivElement, TimePickerProps>(
|
||||
'px-2 py-[5px] font-medium font-sans text-caption transition-colors',
|
||||
ampm === period
|
||||
? 'bg-[var(--brand-secondary)] text-[var(--bg)]'
|
||||
: 'bg-[var(--surface-5)] text-[var(--text-secondary)] hover-hover:bg-[var(--surface-7)] hover-hover:text-[var(--text-primary)] dark:hover-hover:bg-[var(--surface-5)]'
|
||||
: 'bg-[var(--surface-5)] text-[var(--text-secondary)] hover-hover:bg-[var(--surface-active)] hover-hover:text-[var(--text-primary)]'
|
||||
)}
|
||||
>
|
||||
{period}
|
||||
|
||||
@@ -1287,7 +1287,9 @@ export function AccessControl() {
|
||||
<p className='text-[var(--text-secondary)]'>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{deletingGroup?.name}</span>?
|
||||
All members will be removed from this group.{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
All members will be removed from this group.
|
||||
</span>{' '}
|
||||
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
|
||||
</p>
|
||||
</ModalBody>
|
||||
|
||||
Reference in New Issue
Block a user