improvement(tag-dropdown): added option to select block in tag dropdown, custom tools modal improvements, light mode fixes (#2594)

* improvement(tag-dropdown): added option to select block in tag dropdown, custom tools modal improvements, light mode fixes

* fix UI bugs

* remove unused components

* tag drop ordering fix
This commit is contained in:
Waleed
2025-12-26 16:24:38 -08:00
committed by GitHub
parent 7793a6d597
commit b60b98e42c
31 changed files with 237 additions and 1489 deletions

View File

@@ -134,8 +134,8 @@ export function Knowledge() {
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[28px] pb-[24px]'>
<div>
<div className='flex items-start gap-[12px]'>
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#1E5A3E] bg-[#0F3D2C]'>
<Database className='h-[14px] w-[14px] text-[#34D399]' />
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#5BB377] bg-[#E8F7EE] dark:border-[#1E5A3E] dark:bg-[#0F3D2C]'>
<Database className='h-[14px] w-[14px] text-[#5BB377] dark:text-[#34D399]' />
</div>
<h1 className='font-medium text-[18px]'>Knowledge Base</h1>
</div>

View File

@@ -295,8 +295,8 @@ export function LogsToolbar({
{/* Header Section */}
<div className='flex items-start justify-between'>
<div className='flex items-start gap-[12px]'>
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#7A5F11] bg-[#514215]'>
<Library className='h-[14px] w-[14px] text-[#FBBC04]' />
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#D4A843] bg-[#FDF6E3] dark:border-[#7A5F11] dark:bg-[#514215]'>
<Library className='h-[14px] w-[14px] text-[#D4A843] dark:text-[#FBBC04]' />
</div>
<h1 className='font-medium text-[18px]'>Logs</h1>
</div>

View File

@@ -175,8 +175,8 @@ export default function Templates({
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[28px] pb-[24px]'>
<div>
<div className='flex items-start gap-[12px]'>
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#1A5070] bg-[#153347]'>
<Layout className='h-[14px] w-[14px] text-[#33b4ff]' />
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#5BA8D9] bg-[#E8F4FB] dark:border-[#1A5070] dark:bg-[#153347]'>
<Layout className='h-[14px] w-[14px] text-[#5BA8D9] dark:text-[#33b4ff]' />
</div>
<h1 className='font-medium text-[18px]'>Templates</h1>
</div>

View File

@@ -1,7 +1,8 @@
'use client'
import { useEffect, useMemo, useRef, useState } from 'react'
import { Check } from 'lucide-react'
import type React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Check, RepeatIcon, SplitIcon } from 'lucide-react'
import {
Badge,
Popover,
@@ -19,6 +20,32 @@ import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
/**
* Renders a tag icon with background color.
*
* @param icon - Either a letter string or a Lucide icon component
* @param color - Background color for the icon container
* @returns A styled icon element
*/
const TagIcon: React.FC<{
icon: string | React.ComponentType<{ className?: string }>
color: string
}> = ({ icon, color }) => (
<div
className='flex h-[14px] w-[14px] flex-shrink-0 items-center justify-center rounded'
style={{ background: color }}
>
{typeof icon === 'string' ? (
<span className='!text-white font-bold text-[10px]'>{icon}</span>
) : (
(() => {
const IconComponent = icon
return <IconComponent className='!text-white size-[9px]' />
})()
)}
</div>
)
/**
* Props for the OutputSelect component
*/
@@ -71,7 +98,6 @@ export function OutputSelect({
const [highlightedIndex, setHighlightedIndex] = useState(-1)
const triggerRef = useRef<HTMLDivElement>(null)
const popoverRef = useRef<HTMLDivElement>(null)
const contentRef = useRef<HTMLDivElement>(null)
const blocks = useWorkflowStore((state) => state.blocks)
const { isShowingDiff, isDiffReady, hasActiveDiff, baselineWorkflow } = useWorkflowDiffStore()
const subBlockValues = useSubBlockStore((state) =>
@@ -185,8 +211,11 @@ export function OutputSelect({
* @param o - The output object to check
* @returns True if the output is selected, false otherwise
*/
const isSelectedValue = (o: { id: string; label: string }) =>
selectedOutputs.includes(o.id) || selectedOutputs.includes(o.label)
const isSelectedValue = useCallback(
(o: { id: string; label: string }) =>
selectedOutputs.includes(o.id) || selectedOutputs.includes(o.label),
[selectedOutputs]
)
/**
* Gets display text for selected outputs
@@ -292,82 +321,94 @@ export function OutputSelect({
* Handles output selection by toggling the selected state
* @param value - The output label to toggle
*/
const handleOutputSelection = (value: string) => {
const emittedValue =
valueMode === 'label' ? value : workflowOutputs.find((o) => o.label === value)?.id || value
const index = selectedOutputs.indexOf(emittedValue)
const handleOutputSelection = useCallback(
(value: string) => {
const emittedValue =
valueMode === 'label' ? value : workflowOutputs.find((o) => o.label === value)?.id || value
const index = selectedOutputs.indexOf(emittedValue)
const newSelectedOutputs =
index === -1
? [...new Set([...selectedOutputs, emittedValue])]
: selectedOutputs.filter((id) => id !== emittedValue)
const newSelectedOutputs =
index === -1
? [...new Set([...selectedOutputs, emittedValue])]
: selectedOutputs.filter((id) => id !== emittedValue)
onOutputSelect(newSelectedOutputs)
}
onOutputSelect(newSelectedOutputs)
},
[valueMode, workflowOutputs, selectedOutputs, onOutputSelect]
)
/**
* Handles keyboard navigation within the output list
* Supports ArrowUp, ArrowDown, Enter, and Escape keys
* @param e - Keyboard event
*/
const handleKeyDown = (e: React.KeyboardEvent) => {
if (flattenedOutputs.length === 0) return
useEffect(() => {
if (!open || flattenedOutputs.length === 0) return
switch (e.key) {
case 'ArrowDown':
e.preventDefault()
setHighlightedIndex((prev) => {
const next = prev < flattenedOutputs.length - 1 ? prev + 1 : 0
return next
})
break
const handleKeyboardEvent = (e: KeyboardEvent) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault()
e.stopPropagation()
setHighlightedIndex((prev) => {
if (prev === -1 || prev >= flattenedOutputs.length - 1) {
return 0
}
return prev + 1
})
break
case 'ArrowUp':
e.preventDefault()
setHighlightedIndex((prev) => {
const next = prev > 0 ? prev - 1 : flattenedOutputs.length - 1
return next
})
break
case 'ArrowUp':
e.preventDefault()
e.stopPropagation()
setHighlightedIndex((prev) => {
if (prev <= 0) {
return flattenedOutputs.length - 1
}
return prev - 1
})
break
case 'Enter':
e.preventDefault()
if (highlightedIndex >= 0 && highlightedIndex < flattenedOutputs.length) {
handleOutputSelection(flattenedOutputs[highlightedIndex].label)
}
break
case 'Enter':
e.preventDefault()
e.stopPropagation()
setHighlightedIndex((currentIndex) => {
if (currentIndex >= 0 && currentIndex < flattenedOutputs.length) {
handleOutputSelection(flattenedOutputs[currentIndex].label)
}
return currentIndex
})
break
case 'Escape':
e.preventDefault()
setOpen(false)
break
case 'Escape':
e.preventDefault()
e.stopPropagation()
setOpen(false)
break
}
}
}
window.addEventListener('keydown', handleKeyboardEvent, true)
return () => window.removeEventListener('keydown', handleKeyboardEvent, true)
}, [open, flattenedOutputs, handleOutputSelection])
/**
* Reset highlighted index when popover opens/closes
*/
useEffect(() => {
if (open) {
// Find first selected item, or start at -1
const firstSelectedIndex = flattenedOutputs.findIndex((output) => isSelectedValue(output))
setHighlightedIndex(firstSelectedIndex >= 0 ? firstSelectedIndex : -1)
// Focus the content for keyboard navigation
setTimeout(() => {
contentRef.current?.focus()
}, 0)
} else {
setHighlightedIndex(-1)
}
}, [open, flattenedOutputs])
}, [open, flattenedOutputs, isSelectedValue])
/**
* Scroll highlighted item into view
*/
useEffect(() => {
if (highlightedIndex >= 0 && contentRef.current) {
const highlightedElement = contentRef.current.querySelector(
if (highlightedIndex >= 0 && popoverRef.current) {
const highlightedElement = popoverRef.current.querySelector(
`[data-option-index="${highlightedIndex}"]`
)
if (highlightedElement) {
@@ -425,18 +466,35 @@ export function OutputSelect({
minWidth={160}
border
disablePortal={disablePopoverPortal}
onKeyDown={handleKeyDown}
tabIndex={0}
style={{ outline: 'none' }}
>
<div ref={contentRef} className='space-y-[2px]'>
<div className='space-y-[2px]'>
{Object.entries(groupedOutputs).map(([blockName, outputs]) => {
// Calculate the starting index for this group
const startIndex = flattenedOutputs.findIndex((o) => o.blockName === blockName)
const firstOutput = outputs[0]
const blockConfig = getBlock(firstOutput.blockType)
const blockColor = getOutputColor(firstOutput.blockId, firstOutput.blockType)
let blockIcon: string | React.ComponentType<{ className?: string }> = blockName
.charAt(0)
.toUpperCase()
if (blockConfig?.icon) {
blockIcon = blockConfig.icon
} else if (firstOutput.blockType === 'loop') {
blockIcon = RepeatIcon
} else if (firstOutput.blockType === 'parallel') {
blockIcon = SplitIcon
}
return (
<div key={blockName}>
<PopoverSection>{blockName}</PopoverSection>
<PopoverSection>
<div className='flex items-center gap-1.5'>
<TagIcon icon={blockIcon} color={blockColor} />
<span>{blockName}</span>
</div>
</PopoverSection>
<div className='flex flex-col gap-[2px]'>
{outputs.map((output, localIndex) => {
@@ -451,17 +509,9 @@ export function OutputSelect({
onClick={() => handleOutputSelection(output.label)}
onMouseEnter={() => setHighlightedIndex(globalIndex)}
>
<div
className='flex h-[14px] w-[14px] flex-shrink-0 items-center justify-center rounded'
style={{
backgroundColor: getOutputColor(output.blockId, output.blockType),
}}
>
<span className='font-bold text-[10px] text-white'>
{blockName.charAt(0).toUpperCase()}
</span>
</div>
<span className='min-w-0 flex-1 truncate'>{output.path}</span>
<span className='min-w-0 flex-1 truncate text-[var(--text-primary)]'>
{output.path}
</span>
{isSelectedValue(output) && <Check className='h-3 w-3 flex-shrink-0' />}
</PopoverItem>
)

View File

@@ -99,15 +99,12 @@ const createHighlightFunction = (
const placeholders: CodePlaceholder[] = []
let processedCode = codeToHighlight
// Replace environment variables with placeholders
processedCode = processedCode.replace(createEnvVarPattern(), (match) => {
const placeholder = `__ENV_VAR_${placeholders.length}__`
placeholders.push({ placeholder, original: match, type: 'env' })
return placeholder
})
// Replace variable references with placeholders
// Use [^<>]+ to prevent matching across nested brackets (e.g., "<3 <real.ref>" should match separately)
processedCode = processedCode.replace(createReferencePattern(), (match) => {
if (shouldHighlightReference(match)) {
const placeholder = `__VAR_REF_${placeholders.length}__`
@@ -117,25 +114,22 @@ const createHighlightFunction = (
return match
})
// Apply Prism syntax highlighting
const lang = effectiveLanguage === 'python' ? 'python' : 'javascript'
let highlightedCode = highlight(processedCode, languages[lang], lang)
// Apply dark mode token styling
highlightedCode = applyDarkModeTokenStyling(highlightedCode)
// Restore and highlight the placeholders
placeholders.forEach(({ placeholder, original, type }) => {
if (type === 'env') {
highlightedCode = highlightedCode.replace(
placeholder,
`<span class="text-blue-500">${original}</span>`
`<span style="color: var(--brand-secondary);">${original}</span>`
)
} else if (type === 'var') {
const escaped = original.replace(/</g, '&lt;').replace(/>/g, '&gt;')
highlightedCode = highlightedCode.replace(
placeholder,
`<span class="text-blue-500">${escaped}</span>`
`<span style="color: var(--brand-secondary);">${escaped}</span>`
)
}
})
@@ -194,11 +188,9 @@ export function Code({
wandControlRef,
hideInternalWand = false,
}: CodeProps) {
// Route params
const params = useParams()
const workspaceId = params.workspaceId as string
// Local state
const [code, setCode] = useState<string>('')
const [showTags, setShowTags] = useState(false)
const [showEnvVars, setShowEnvVars] = useState(false)
@@ -209,19 +201,16 @@ export function Code({
const [activeLineNumber, setActiveLineNumber] = useState(1)
const [copied, setCopied] = useState(false)
// Refs
const editorRef = useRef<HTMLDivElement>(null)
const handleStreamStartRef = useRef<() => void>(() => {})
const handleGeneratedContentRef = useRef<(generatedCode: string) => void>(() => {})
const handleStreamChunkRef = useRef<(chunk: string) => void>(() => {})
const hasEditedSinceFocusRef = useRef(false)
// Custom hooks
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
const emitTagSelection = useTagSelection(blockId, subBlockId)
const [languageValue] = useSubBlockValue<string>(blockId, 'language')
// Derived state
const effectiveLanguage = (languageValue as 'javascript' | 'python' | 'json') || language
const trimmedCode = code.trim()
@@ -279,7 +268,6 @@ export function Code({
return wandConfig
}, [wandConfig, languageValue])
// AI code generation integration
const wandHook = useWand({
wandConfig: dynamicWandConfig || { enabled: false, prompt: '' },
currentValue: code,
@@ -298,7 +286,6 @@ export function Code({
const updatePromptValue = wandHook?.updatePromptValue || (() => {})
const cancelGeneration = wandHook?.cancelGeneration || (() => {})
// Store integration
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId, false, {
isStreaming: isAiStreaming,
onStreamingEnd: () => {
@@ -320,7 +307,6 @@ export function Code({
? getDefaultValueString()
: storeValue
// Effects: JSON validation
const lastValidationStatus = useRef<boolean>(true)
useEffect(() => {
@@ -345,7 +331,6 @@ export function Code({
return () => clearTimeout(timeoutId)
}, [isValidJson, onValidationChange, shouldValidateJson])
// Effects: AI stream handlers setup
useEffect(() => {
handleStreamStartRef.current = () => {
setCode('')
@@ -359,7 +344,6 @@ export function Code({
}
}, [isPreview, disabled, setStoreValue])
// Effects: Set read only state for textarea
useEffect(() => {
if (!editorRef.current) return
@@ -388,7 +372,6 @@ export function Code({
}
}, [readOnly])
// Effects: Sync code with external value
useEffect(() => {
if (isAiStreaming) return
const valueString = value?.toString() ?? ''
@@ -397,7 +380,6 @@ export function Code({
}
}, [value, code, isAiStreaming])
// Effects: Track active line number for cursor position
useEffect(() => {
const textarea = editorRef.current?.querySelector('textarea')
if (!textarea) return
@@ -422,7 +404,6 @@ export function Code({
}
}, [code])
// Effects: Calculate visual line heights for proper gutter alignment
useEffect(() => {
if (!editorRef.current) return

View File

@@ -154,13 +154,9 @@ export function ConditionInput({
const removeEdge = useWorkflowStore((state) => state.removeEdge)
const edges = useWorkflowStore((state) => state.edges)
// Use a ref to track the previous store value for comparison
const prevStoreValueRef = useRef<string | null>(null)
// Use a ref to track if we're currently syncing from store to prevent loops
const isSyncingFromStoreRef = useRef(false)
// Use a ref to track if we've already initialized from store
const hasInitializedRef = useRef(false)
// Track previous blockId to detect workflow changes
const previousBlockIdRef = useRef<string>(blockId)
const shouldPersistRef = useRef<boolean>(false)
@@ -869,7 +865,6 @@ export function ConditionInput({
}[] = []
let processedCode = codeToHighlight
// Replace environment variables with placeholders
processedCode = processedCode.replace(createEnvVarPattern(), (match) => {
const placeholder = `__ENV_VAR_${placeholders.length}__`
placeholders.push({
@@ -881,8 +876,6 @@ export function ConditionInput({
return placeholder
})
// Replace variable references with placeholders
// Use [^<>]+ to prevent matching across nested brackets (e.g., "<3 <real.ref>" should match separately)
processedCode = processedCode.replace(
createReferencePattern(),
(match) => {
@@ -901,14 +894,12 @@ export function ConditionInput({
}
)
// Apply Prism syntax highlighting
let highlightedCode = highlight(
processedCode,
languages.javascript,
'javascript'
)
// Restore and highlight the placeholders
placeholders.forEach(
({ placeholder, original, type, shouldHighlight }) => {
if (!shouldHighlight) return
@@ -916,14 +907,13 @@ export function ConditionInput({
if (type === 'env') {
highlightedCode = highlightedCode.replace(
placeholder,
`<span class="text-blue-500">${original}</span>`
`<span style="color: var(--brand-secondary);">${original}</span>`
)
} else if (type === 'var') {
// Escape the < and > for display
const escaped = original.replace(/</g, '&lt;').replace(/>/g, '&gt;')
highlightedCode = highlightedCode.replace(
placeholder,
`<span class="text-blue-500">${escaped}</span>`
`<span style="color: var(--brand-secondary);">${escaped}</span>`
)
}
}

View File

@@ -407,7 +407,7 @@ export function OAuthRequiredModal({
<div className='flex flex-1 items-center gap-[8px] text-[12px] text-[var(--text-primary)]'>
<span>{getScopeDescription(scope)}</span>
{newScopesSet.has(scope) && (
<span className='inline-flex items-center gap-[6px] rounded-[6px] bg-[rgba(245,158,11,0.2)] px-[7px] py-[1px] font-medium text-[#fcd34d] text-[11px]'>
<span className='inline-flex items-center gap-[6px] rounded-[6px] bg-[#fde68a] px-[7px] py-[1px] font-medium text-[#a16207] text-[11px] dark:bg-[rgba(245,158,11,0.2)] dark:text-[#fcd34d]'>
New
</span>
)}

View File

@@ -67,7 +67,7 @@ export function formatDisplayText(text: string, context?: HighlightContext): Rea
if (matchText.startsWith(REFERENCE.ENV_VAR_START)) {
nodes.push(
<span key={key++} className='text-[#34B5FF]'>
<span key={key++} className='text-[var(--brand-secondary)]'>
{matchText}
</span>
)
@@ -77,7 +77,7 @@ export function formatDisplayText(text: string, context?: HighlightContext): Rea
if (split && shouldHighlightReference(split.reference)) {
pushPlainText(split.leading)
nodes.push(
<span key={key++} className='text-[#34B5FF]'>
<span key={key++} className='text-[var(--brand-secondary)]'>
{split.reference}
</span>
)

View File

@@ -1223,6 +1223,10 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
}
})
directTags.forEach((directTag) => {
nestedTags.push(directTag)
})
Object.entries(groupedTags).forEach(([parent, children]) => {
const firstChildTag = children[0]?.fullTag
if (firstChildTag) {
@@ -1243,10 +1247,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
}
})
directTags.forEach((directTag) => {
nestedTags.push(directTag)
})
return {
...group,
nestedTags,
@@ -1262,7 +1262,17 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
})
nestedBlockTagGroups.forEach((group) => {
const normalizedBlockName = normalizeName(group.blockName)
const rootTagFromTags = group.tags.find((tag) => tag === normalizedBlockName)
const rootTag = rootTagFromTags || normalizedBlockName
list.push({ tag: rootTag, group })
group.nestedTags.forEach((nestedTag) => {
if (nestedTag.fullTag === rootTag) {
return
}
if (nestedTag.parentTag) {
list.push({ tag: nestedTag.parentTag, group })
}
@@ -1280,7 +1290,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
return list
}, [variableTags, nestedBlockTagGroups])
// Auto-scroll selected item into view
useEffect(() => {
if (!visible || selectedIndex < 0) return
@@ -1318,7 +1327,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
const parts = tag.split('.')
if (parts.length >= 3 && blockGroup) {
const arrayFieldName = parts[1] // e.g., "channels", "files", "users"
const arrayFieldName = parts[1]
const block = useWorkflowStore.getState().blocks[blockGroup.blockId]
const blockConfig = block ? (getBlock(block.type) ?? null) : null
const mergedSubBlocks = getMergedSubBlocks(blockGroup.blockId)
@@ -1403,7 +1412,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
if (!visible || tags.length === 0 || flatTagList.length === 0) return null
// Calculate caret position for proper anchoring
const inputElement = inputRef?.current
let caretViewport = { left: 0, top: 0 }
let side: 'top' | 'bottom' = 'bottom'
@@ -1516,7 +1524,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
blockColor = BLOCK_COLORS.PARALLEL
}
// Use actual block icon if available, otherwise fall back to special icons for loop/parallel or first letter
let tagIcon: string | React.ComponentType<{ className?: string }> = group.blockName
.charAt(0)
.toUpperCase()
@@ -1528,10 +1535,41 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
tagIcon = SplitIcon
}
const normalizedBlockName = normalizeName(group.blockName)
const rootTagFromTags = group.tags.find((tag) => tag === normalizedBlockName)
const rootTag = rootTagFromTags || normalizedBlockName
const rootTagGlobalIndex = flatTagList.findIndex((item) => item.tag === rootTag)
return (
<div key={group.blockId}>
<PopoverSection rootOnly>{group.blockName}</PopoverSection>
<PopoverItem
rootOnly
active={rootTagGlobalIndex === selectedIndex && rootTagGlobalIndex >= 0}
onMouseEnter={() => {
if (rootTagGlobalIndex >= 0) setSelectedIndex(rootTagGlobalIndex)
}}
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
handleTagSelect(rootTag, group)
}}
ref={(el) => {
if (el && rootTagGlobalIndex >= 0) {
itemRefs.current.set(rootTagGlobalIndex, el)
}
}}
>
<TagIcon icon={tagIcon} color={blockColor} />
<span className='flex-1 truncate font-medium text-[var(--text-primary)]'>
{group.blockName}
</span>
</PopoverItem>
{group.nestedTags.map((nestedTag) => {
if (nestedTag.fullTag === rootTag) {
return null
}
const hasChildren = nestedTag.children && nestedTag.children.length > 0
if (hasChildren) {
@@ -1546,7 +1584,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
key={folderId}
id={folderId}
title={nestedTag.display}
icon={<TagIcon icon={tagIcon} color={blockColor} />}
active={parentGlobalIndex === selectedIndex && parentGlobalIndex >= 0}
onSelect={() => {
if (nestedTag.parentTag) {
@@ -1609,7 +1646,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
}
}}
>
<TagIcon icon={tagIcon} color={blockColor} />
<span className='flex-1 truncate text-[var(--text-primary)]'>
{child.display}
</span>
@@ -1625,26 +1661,21 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
)
}
// Direct tag (no children)
const globalIndex = nestedTag.fullTag
? flatTagList.findIndex((item) => item.tag === nestedTag.fullTag)
: -1
let tagDescription = ''
let displayIcon = tagIcon
if (
(group.blockType === 'loop' || group.blockType === 'parallel') &&
!nestedTag.key.includes('.')
) {
if (nestedTag.key === 'index') {
displayIcon = '#'
tagDescription = 'number'
} else if (nestedTag.key === 'currentItem') {
displayIcon = 'i'
tagDescription = 'any'
} else if (nestedTag.key === 'items') {
displayIcon = 'I'
tagDescription = 'array'
}
} else if (nestedTag.fullTag) {
@@ -1687,7 +1718,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
}
}}
>
<TagIcon icon={displayIcon} color={blockColor} />
<span className='flex-1 truncate text-[var(--text-primary)]'>
{nestedTag.display}
</span>

View File

@@ -176,8 +176,8 @@ export function CodeEditor({
const escapedOriginal = type === 'variable' ? escapeHtml(original) : original
const replacement =
type === 'env' || type === 'variable'
? `<span style="color: #34B5FF;">${escapedOriginal}</span>`
: `<span style="color: #34B5FF; font-weight: 500;">${original}</span>`
? `<span style="color: var(--brand-secondary);">${escapedOriginal}</span>`
: `<span style="color: var(--brand-secondary); font-weight: 500;">${original}</span>`
highlighted = highlighted.replace(placeholder, replacement)
})

View File

@@ -3,6 +3,7 @@ import { createLogger } from '@sim/logger'
import { AlertCircle, Wand2 } from 'lucide-react'
import { useParams } from 'next/navigation'
import {
Badge,
Button,
Modal,
ModalBody,
@@ -236,8 +237,8 @@ try {
},
currentValue: functionCode,
onGeneratedContent: (content) => {
handleFunctionCodeChange(content) // Use existing handler to also trigger dropdown checks
setCodeError(null) // Clear error on successful generation
handleFunctionCodeChange(content)
setCodeError(null)
},
onStreamChunk: (chunk) => {
setFunctionCode((prev) => {
@@ -258,6 +259,7 @@ try {
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 })
const [schemaParamSelectedIndex, setSchemaParamSelectedIndex] = useState(0)
const schemaParamItemRefs = useRef<Map<number, HTMLElement>>(new Map())
const createToolMutation = useCreateCustomTool()
const updateToolMutation = useUpdateCustomTool()
@@ -286,6 +288,18 @@ try {
}
}, [open])
useEffect(() => {
if (!showSchemaParams || schemaParamSelectedIndex < 0) return
const element = schemaParamItemRefs.current.get(schemaParamSelectedIndex)
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
})
}
}, [schemaParamSelectedIndex, showSchemaParams])
const resetForm = () => {
setJsonSchema('')
setFunctionCode('')
@@ -341,7 +355,6 @@ try {
}
}
/** Extracts parameters from JSON schema for autocomplete */
const schemaParameters = useMemo(() => {
try {
if (!jsonSchema) return []
@@ -360,7 +373,6 @@ try {
}
}, [jsonSchema])
/** Memoized schema validation result */
const isSchemaValid = useMemo(() => validateJsonSchema(jsonSchema), [jsonSchema])
const handleSave = async () => {
@@ -454,7 +466,6 @@ try {
code: functionCode || '',
},
})
// Get the ID from the created tool
savedToolId = result?.[0]?.id
}
@@ -874,11 +885,6 @@ try {
)}
</div>
<div className='flex min-w-0 flex-1 items-center justify-end gap-1 pr-[4px]'>
{!isSchemaPromptActive && schemaPromptSummary && (
<span className='text-[var(--text-tertiary)] text-xs italic'>
with {schemaPromptSummary}
</span>
)}
{!isSchemaPromptActive ? (
<button
type='button'
@@ -953,11 +959,6 @@ try {
)}
</div>
<div className='flex min-w-0 flex-1 items-center justify-end gap-1 pr-[4px]'>
{!isCodePromptActive && codePromptSummary && (
<span className='text-[var(--text-tertiary)] text-xs italic'>
with {codePromptSummary}
</span>
)}
{!isCodePromptActive ? (
<button
type='button'
@@ -985,18 +986,19 @@ try {
</div>
{schemaParameters.length > 0 && (
<div className='mb-2 rounded-[6px] border bg-[var(--surface-2)] p-2'>
<p className='text-[var(--text-tertiary)] text-xs'>
<span className='font-medium'>Available parameters:</span>{' '}
{schemaParameters.map((param, index) => (
<span key={param.name}>
<code className='rounded bg-[var(--bg)] px-1 py-0.5 text-[var(--text-primary)]'>
{param.name}
</code>
{index < schemaParameters.length - 1 && ', '}
</span>
<div className='flex flex-wrap items-center gap-1.5 text-xs'>
<span className='font-medium text-[var(--text-tertiary)]'>
Available parameters:
</span>
{schemaParameters.map((param) => (
<Badge key={param.name} variant='blue-secondary' size='sm'>
{param.name}
</Badge>
))}
{'. '}Start typing a parameter name for autocomplete.
</p>
<span className='text-[var(--text-tertiary)]'>
Start typing a parameter name for autocomplete.
</span>
</div>
</div>
)}
<div ref={codeEditorRef} className='relative'>
@@ -1085,7 +1087,7 @@ try {
</PopoverAnchor>
<PopoverContent
maxHeight={240}
className='min-w-[260px] max-w-[260px]'
className='min-w-[280px]'
side='bottom'
align='start'
collisionPadding={6}
@@ -1105,18 +1107,20 @@ try {
e.stopPropagation()
handleSchemaParamSelect(param.name)
}}
className='flex items-center gap-2'
ref={(el) => {
if (el) {
schemaParamItemRefs.current.set(index, el)
}
}}
>
<div
className='flex h-5 w-5 items-center justify-center rounded'
style={{ backgroundColor: '#2F8BFF' }}
>
<span className='h-3 w-3 font-bold text-white text-xs'>P</span>
</div>
<span className='flex-1 truncate'>{param.name}</span>
<span className='text-[var(--text-tertiary)] text-xs'>
{param.type}
<span className='flex-1 truncate text-[var(--text-primary)]'>
{param.name}
</span>
{param.type && param.type !== 'any' && (
<span className='ml-auto text-[10px] text-[var(--text-secondary)]'>
{param.type}
</span>
)}
</PopoverItem>
))}
</PopoverScrollArea>

View File

@@ -156,13 +156,13 @@ export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId
if (type === 'env') {
highlightedCode = highlightedCode.replace(
placeholder,
`<span class="text-blue-500">${original}</span>`
`<span style="color: var(--brand-secondary);">${original}</span>`
)
} else {
const escaped = original.replace(/</g, '&lt;').replace(/>/g, '&gt;')
highlightedCode = highlightedCode.replace(
placeholder,
`<span class="text-blue-500">${escaped}</span>`
`<span style="color: var(--brand-secondary);">${escaped}</span>`
)
}
})

View File

@@ -144,7 +144,7 @@ export const SubflowNodeComponent = memo(({ data, id }: NodeProps<SubflowNodeDat
ref={blockRef}
onClick={() => setCurrentBlockId(id)}
className={cn(
'relative cursor-pointer select-none rounded-[8px] border border-[var(--divider)]',
'relative cursor-pointer select-none rounded-[8px] border border-[var(--border)]',
'transition-block-bg transition-ring',
'z-[20]'
)}
@@ -162,7 +162,7 @@ export const SubflowNodeComponent = memo(({ data, id }: NodeProps<SubflowNodeDat
{/* Header Section */}
<div
className={cn(
'workflow-drag-handle flex cursor-grab items-center justify-between rounded-t-[8px] border-[var(--divider)] border-b bg-[var(--surface-2)] py-[8px] pr-[12px] pl-[8px] [&:active]:cursor-grabbing'
'workflow-drag-handle flex cursor-grab items-center justify-between rounded-t-[8px] border-[var(--border)] border-b bg-[var(--surface-2)] py-[8px] pr-[12px] pl-[8px] [&:active]:cursor-grabbing'
)}
onMouseDown={(e) => {
e.stopPropagation()

View File

@@ -45,7 +45,11 @@ const PermissionSelector = React.memo<PermissionSelectorProps>(
onClick={() => !disabled && onChange(option.value)}
disabled={disabled}
title={option.description}
className='h-[22px] min-w-[38px] px-[6px] py-0 text-[11px]'
className={cn(
'h-[22px] min-w-[38px] px-[6px] py-0 text-[11px]',
value === option.value &&
'bg-[var(--border-1)] hover:bg-[var(--border-1)] dark:bg-[var(--surface-5)] dark:hover:bg-[var(--border-1)]'
)}
>
{option.label}
</Button>

View File

@@ -41,7 +41,9 @@ export const PermissionSelector = React.memo<PermissionSelectorProps>(
className={cn(
'px-[8px] py-[4px] text-[12px]',
radiusClasses,
disabled && 'cursor-not-allowed'
disabled && 'cursor-not-allowed',
value === option.value &&
'bg-[var(--border-1)] hover:bg-[var(--border-1)] dark:bg-[var(--surface-5)] dark:hover:bg-[var(--border-1)]'
)}
>
{option.label}

View File

@@ -36,7 +36,7 @@ function WorkflowPreviewSubflowInner({ data }: NodeProps<WorkflowPreviewSubflowD
return (
<div
className='relative select-none rounded-[8px] border border-[var(--divider)]'
className='relative select-none rounded-[8px] border border-[var(--border)]'
style={{
width,
height,
@@ -56,7 +56,7 @@ function WorkflowPreviewSubflowInner({ data }: NodeProps<WorkflowPreviewSubflowD
/>
{/* Header - matches actual subflow header */}
<div className='flex items-center gap-[10px] rounded-t-[8px] border-[var(--divider)] border-b bg-[var(--surface-2)] py-[8px] pr-[12px] pl-[8px]'>
<div className='flex items-center gap-[10px] rounded-t-[8px] border-[var(--border)] border-b bg-[var(--surface-2)] py-[8px] pr-[12px] pl-[8px]'>
<div
className='flex h-[24px] w-[24px] flex-shrink-0 items-center justify-center rounded-[6px]'
style={{ backgroundColor: blockIconBg }}

View File

@@ -1,197 +0,0 @@
'use client'
import * as React from 'react'
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
import { X } from 'lucide-react'
import { buttonVariants } from '@/components/ui/button'
import { cn } from '@/lib/core/utils/cn'
const AlertDialog = AlertDialogPrimitive.Root
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = AlertDialogPrimitive.Portal
// Context for communication between overlay and content
const AlertDialogCloseContext = React.createContext<{
triggerClose: () => void
} | null>(null)
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, style, onClick, ...props }, ref) => {
const [isStable, setIsStable] = React.useState(false)
const closeContext = React.useContext(AlertDialogCloseContext)
React.useEffect(() => {
// Add a small delay before allowing overlay interactions to prevent rapid state changes
const timer = setTimeout(() => setIsStable(true), 150)
return () => clearTimeout(timer)
}, [])
return (
<AlertDialogPrimitive.Overlay
className={cn(
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-[10000150] bg-white/50 data-[state=closed]:animate-out data-[state=open]:animate-in dark:bg-black/50',
className
)}
style={{ backdropFilter: 'blur(1.5px)', ...style }}
onClick={(e) => {
// Only allow overlay clicks after component is stable
if (!isStable) {
e.preventDefault()
return
}
// Only close if clicking directly on the overlay, not child elements
if (e.target === e.currentTarget) {
// Trigger close via context
closeContext?.triggerClose()
}
// Call original onClick if provided
onClick?.(e)
}}
{...props}
ref={ref}
/>
)
})
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> & {
hideCloseButton?: boolean
}
>(({ className, children, hideCloseButton = false, ...props }, ref) => {
const [isInteractionReady, setIsInteractionReady] = React.useState(false)
const hiddenCancelRef = React.useRef<HTMLButtonElement>(null)
React.useEffect(() => {
// Prevent rapid interactions that can cause instability
const timer = setTimeout(() => setIsInteractionReady(true), 100)
return () => clearTimeout(timer)
}, [])
const closeContextValue = React.useMemo(
() => ({
triggerClose: () => hiddenCancelRef.current?.click(),
}),
[]
)
return (
<AlertDialogPortal>
<AlertDialogCloseContext.Provider value={closeContextValue}>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-[10000151] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-[8px] border border-[var(--border-muted)] bg-[var(--surface-3)] px-6 py-5 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in dark:bg-[var(--surface-3)]',
className
)}
onPointerDown={(e) => {
// Prevent event bubbling that might interfere with parent hover states
e.stopPropagation()
}}
onPointerUp={(e) => {
// Prevent event bubbling that might interfere with parent hover states
e.stopPropagation()
}}
{...props}
>
{children}
{!hideCloseButton && (
<AlertDialogPrimitive.Cancel
className='absolute top-4 right-4 h-4 w-4 border-0 bg-transparent p-0 text-muted-foreground transition-colors hover:bg-transparent hover:bg-transparent hover:text-foreground focus:outline-none disabled:pointer-events-none'
disabled={!isInteractionReady}
tabIndex={-1}
>
<X className='h-4 w-4' />
<span className='sr-only'>Close</span>
</AlertDialogPrimitive.Cancel>
)}
{/* Hidden cancel button for overlay clicks */}
<AlertDialogPrimitive.Cancel
ref={hiddenCancelRef}
style={{ display: 'none' }}
tabIndex={-1}
aria-hidden='true'
/>
</AlertDialogPrimitive.Content>
</AlertDialogCloseContext.Provider>
</AlertDialogPortal>
)
})
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
)
AlertDialogHeader.displayName = 'AlertDialogHeader'
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...props}
/>
)
AlertDialogFooter.displayName = 'AlertDialogFooter'
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn('font-semibold text-lg', className)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn('font-[360] text-sm', className)}
{...props}
/>
))
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}

View File

@@ -1,100 +0,0 @@
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { ChevronRight, MoreHorizontal } from 'lucide-react'
import { cn } from '@/lib/core/utils/cn'
const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<'nav'> & {
separator?: React.ReactNode
}
>(({ ...props }, ref) => <nav ref={ref} aria-label='breadcrumb' {...props} />)
Breadcrumb.displayName = 'Breadcrumb'
const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<'ol'>>(
({ className, ...props }, ref) => (
<ol
ref={ref}
className={cn(
'flex flex-wrap items-center gap-1.5 break-words text-muted-foreground text-sm sm:gap-2.5',
className
)}
{...props}
/>
)
)
BreadcrumbList.displayName = 'BreadcrumbList'
const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<'li'>>(
({ className, ...props }, ref) => (
<li ref={ref} className={cn('inline-flex items-center gap-1.5', className)} {...props} />
)
)
BreadcrumbItem.displayName = 'BreadcrumbItem'
const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<'a'> & {
asChild?: boolean
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : 'a'
return (
<Comp
ref={ref}
className={cn('transition-colors hover:text-foreground', className)}
{...props}
/>
)
})
BreadcrumbLink.displayName = 'BreadcrumbLink'
const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<'span'>>(
({ className, ...props }, ref) => (
<span
ref={ref}
role='link'
aria-disabled='true'
aria-current='page'
className={cn('font-normal text-foreground', className)}
{...props}
/>
)
)
BreadcrumbPage.displayName = 'BreadcrumbPage'
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<'li'>) => (
<li
role='presentation'
aria-hidden='true'
className={cn('[&>svg]:h-3.5 [&>svg]:w-3.5', className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
)
BreadcrumbSeparator.displayName = 'BreadcrumbSeparator'
const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<'span'>) => (
<span
role='presentation'
aria-hidden='true'
className={cn('flex h-9 w-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontal className='h-4 w-4' />
<span className='sr-only'>More</span>
</span>
)
BreadcrumbEllipsis.displayName = 'BreadcrumbElipssis'
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
}

View File

@@ -1,19 +0,0 @@
import type React from 'react'
import { CopyButton } from '@/components/ui/copy-button'
import { cn } from '@/lib/core/utils/cn'
interface CodeBlockProps extends React.HTMLAttributes<HTMLPreElement> {
code: string
language?: string
}
export function CodeBlock({ code, language, className, ...props }: CodeBlockProps) {
return (
<div className={cn('relative rounded-md border bg-muted', className)}>
<pre className='overflow-x-auto p-4 text-sm' {...props}>
<code>{code}</code>
</pre>
<CopyButton text={code} className='absolute top-2 right-2' />
</div>
)
}

View File

@@ -1,66 +0,0 @@
'use client'
import { forwardRef, useMemo, useState } from 'react'
import { HexColorPicker } from 'react-colorful'
import type { ButtonProps } from '@/components/ui/button'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { cn } from '@/lib/core/utils/cn'
import { useForwardedRef } from '@/hooks/use-forwarded-ref'
interface ColorPickerProps {
value: string
onChange: (value: string) => void
onBlur?: () => void
}
const ColorPicker = forwardRef<
HTMLInputElement,
Omit<ButtonProps, 'value' | 'onChange' | 'onBlur'> & ColorPickerProps & ButtonProps
>(({ disabled, value, onChange, onBlur, name, className, size, ...props }, forwardedRef) => {
const ref = useForwardedRef(forwardedRef)
const [open, setOpen] = useState(false)
const parsedValue = useMemo(() => {
return value || '#FFFFFF'
}, [value])
return (
<Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger asChild disabled={disabled} onBlur={onBlur}>
<Button
{...props}
className={cn('block', className)}
name={name}
onClick={() => {
setOpen(true)
}}
size={size}
style={{
backgroundColor: parsedValue,
}}
variant='outline'
>
<div />
</Button>
</PopoverTrigger>
<PopoverContent className='w-full'>
<div className='space-y-3'>
<HexColorPicker color={parsedValue} onChange={onChange} />
<Input
maxLength={7}
onChange={(e) => {
onChange(e?.currentTarget?.value)
}}
ref={ref}
value={parsedValue}
/>
</div>
</PopoverContent>
</Popover>
)
})
ColorPicker.displayName = 'ColorPicker'
export { ColorPicker }

View File

@@ -1,244 +0,0 @@
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
'use client'
import * as React from 'react'
import type { DialogProps } from '@radix-ui/react-dialog'
import { Command as CommandPrimitive } from 'cmdk'
import { Search } from 'lucide-react'
import { Dialog, DialogContent } from '@/components/ui/dialog'
import { cn } from '@/lib/core/utils/cn'
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
// This file is not typed correctly from shadcn, so we're disabling the type checker
// @ts-nocheck
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive> & {
children?: React.ReactNode
filter?: (value: string, search: string) => number
}
>(({ className, filter, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
className
)}
filter={filter}
{...props}
/>
))
Command.displayName = CommandPrimitive.displayName
const CommandDialog = ({ children, ...props }: DialogProps) => {
return (
<Dialog {...props}>
<DialogContent className='overflow-hidden p-0 shadow-lg'>
<Command className='[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5'>
{children}
</Command>
</DialogContent>
</Dialog>
)
}
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> & {
placeholder?: string
onValueChange?: (value: string) => void
}
>(({ className, onValueChange, ...props }, ref) => (
<div className='flex items-center border-b px-3' cmdk-input-wrapper=''>
<Search className='mr-2 h-4 w-4 shrink-0 opacity-50' />
<CommandPrimitive.Input
ref={ref}
className={cn(
'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}
onChange={(e) => onValueChange?.(e.target.value)}
/>
</div>
))
CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List> & {
children?: React.ReactNode
}
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)}
{...props}
/>
))
CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty> & {
children?: React.ReactNode
}
>((props, ref) => (
<CommandPrimitive.Empty ref={ref} className='py-6 text-center text-sm' {...props} />
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group> & {
children?: React.ReactNode
className?: string
}
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:text-xs',
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn('-mx-1 h-px bg-border', className)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item> & {
children?: React.ReactNode
onSelect?: () => void
className?: string
value?: string
keywords?: string
}
>(({ className, value, keywords, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
className
)}
value={value}
{...props}
/>
))
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('ml-auto text-muted-foreground text-xs tracking-widest', className)}
{...props}
/>
)
}
CommandShortcut.displayName = 'CommandShortcut'
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}

View File

@@ -1,47 +0,0 @@
'use client'
import { useState } from 'react'
import { Check, Copy } from 'lucide-react'
import { Button } from '@/components/ui/button'
interface CopyButtonProps {
text: string
className?: string
showLabel?: boolean
}
export function CopyButton({ text, className = '', showLabel = true }: CopyButtonProps) {
const [copied, setCopied] = useState(false)
const copyToClipboard = () => {
navigator.clipboard.writeText(text)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<div className='absolute top-1 right-1 flex items-center gap-1.5 opacity-0 transition-opacity group-hover:opacity-100'>
{showLabel && (
<div className='rounded-md bg-background/80 px-2 py-1 text-muted-foreground text-xs'>
{copied ? 'Copied!' : 'Click to copy'}
</div>
)}
<Button
type='button'
variant='ghost'
size='icon'
className={`h-6 w-6 p-0 ${className}`}
onClick={(e) => {
e.stopPropagation() // Prevent click from affecting parent elements
copyToClipboard()
}}
>
{copied ? (
<Check className='h-3.5 w-3.5 text-green-500' />
) : (
<Copy className='h-3.5 w-3.5 text-muted-foreground' />
)}
</Button>
</div>
)
}

View File

@@ -1,168 +0,0 @@
'use client'
import * as React from 'react'
import type * as LabelPrimitive from '@radix-ui/react-label'
import { Slot } from '@radix-ui/react-slot'
import {
Controller,
type ControllerProps,
type FieldPath,
type FieldValues,
FormProvider,
useFormContext,
} from 'react-hook-form'
import { Label } from '@/components/ui/label'
import { cn } from '@/lib/core/utils/cn'
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error('useFormField should be used within <FormField>')
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue)
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn('space-y-2', className)} {...props} />
</FormItemContext.Provider>
)
}
)
FormItem.displayName = 'FormItem'
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
return (
<Label
ref={ref}
className={cn(error && 'text-destructive', className)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = 'FormLabel'
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = 'FormControl'
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
return (
<p
ref={ref}
id={formDescriptionId}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
})
FormDescription.displayName = 'FormDescription'
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message ?? '') : children
if (!body) {
return null
}
return (
<p
ref={ref}
id={formMessageId}
className={cn('font-medium text-destructive text-sm', className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = 'FormMessage'
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}

View File

@@ -1,44 +1,10 @@
export { Alert, AlertDescription, AlertTitle } from './alert'
export {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from './alert-dialog'
export { Avatar, AvatarFallback, AvatarImage } from './avatar'
export { Badge, badgeVariants } from './badge'
export {
Breadcrumb,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from './breadcrumb'
export { Button, buttonVariants } from './button'
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './card'
export { Checkbox } from './checkbox'
export { CodeBlock } from './code-block'
export { Collapsible, CollapsibleContent, CollapsibleTrigger } from './collapsible'
export { ColorPicker } from './color-picker'
export {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from './command'
export { CopyButton } from './copy-button'
export {
Dialog,
DialogClose,
@@ -68,24 +34,10 @@ export {
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from './dropdown-menu'
export {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
useFormField,
} from './form'
export { Input } from './input'
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from './input-otp'
export { OTPInputForm } from './input-otp-form'
export { Label } from './label'
export { Notice } from './notice'
export { Popover, PopoverContent, PopoverTrigger } from './popover'
export { Progress } from './progress'
export { RadioGroup, RadioGroupItem } from './radio-group'
export { ScrollArea, ScrollBar } from './scroll-area'
export { SearchHighlight } from './search-highlight'
export {
@@ -101,18 +53,6 @@ export {
SelectValue,
} from './select'
export { Separator } from './separator'
export {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetOverlay,
SheetPortal,
SheetTitle,
SheetTrigger,
} from './sheet'
export { Skeleton } from './skeleton'
export { Slider } from './slider'
export { Switch } from './switch'
@@ -126,8 +66,6 @@ export {
TableHeader,
TableRow,
} from './table'
export { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs'
export { TagInput } from './tag-input'
export { Textarea } from './textarea'
export { Toggle, toggleVariants } from './toggle'
export { ToolCallCompletion, ToolCallExecution } from './tool-call'

View File

@@ -1,61 +0,0 @@
'use client'
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'
interface OTPInputFormProps {
onSubmit: (otp: string) => void
isLoading?: boolean
error?: string | null
length?: number
}
export function OTPInputForm({
onSubmit,
isLoading = false,
error = null,
length = 6,
}: OTPInputFormProps) {
const [value, setValue] = useState('')
const handleComplete = (value: string) => {
setValue(value)
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (value.length === length && !isLoading) {
onSubmit(value)
}
}
return (
<form onSubmit={handleSubmit} className='space-y-4'>
<div className='flex justify-center'>
<InputOTP
maxLength={length}
value={value}
onChange={setValue}
onComplete={handleComplete}
disabled={isLoading}
pattern='[0-9]*'
inputMode='numeric'
containerClassName='gap-2'
>
<InputOTPGroup>
{Array.from({ length }).map((_, i) => (
<InputOTPSlot key={i} index={i} className='h-12 w-10' />
))}
</InputOTPGroup>
</InputOTP>
</div>
{error && <p className='text-center text-destructive text-sm'>{error}</p>}
<Button type='submit' className='w-full' disabled={value.length !== length || isLoading}>
{isLoading ? 'Verifying...' : 'Verify'}
</Button>
</form>
)
}

View File

@@ -1,64 +0,0 @@
import type React from 'react'
import { AlertCircle, AlertTriangle, Check, Info } from 'lucide-react'
import { cn } from '@/lib/core/utils/cn'
export type NoticeVariant = 'info' | 'warning' | 'success' | 'error' | 'default'
interface NoticeProps {
children: React.ReactNode
variant?: NoticeVariant
className?: string
icon?: React.ReactNode
title?: string
}
const variantStyles = {
default: {
container: 'bg-background border-border',
text: 'text-foreground',
title: 'text-foreground font-medium',
icon: <Info className='mr-2 h-4 w-4 flex-shrink-0 text-muted-foreground' />,
},
info: {
container: 'bg-blue-50 border-blue-200 dark:bg-blue-950/20 dark:border-blue-800/50',
text: 'text-blue-800 dark:text-blue-300',
title: 'text-blue-800 dark:text-blue-300 font-medium',
icon: <Info className='mr-2 h-4 w-4 flex-shrink-0 text-blue-500 dark:text-blue-400' />,
},
warning: {
container: 'bg-amber-50 border-amber-200 dark:bg-amber-950/20 dark:border-amber-800/50',
text: 'text-amber-800 dark:text-amber-300',
title: 'text-amber-800 dark:text-amber-300 font-medium',
icon: (
<AlertTriangle className='mr-2 h-4 w-4 flex-shrink-0 text-amber-500 dark:text-amber-400' />
),
},
success: {
container: 'bg-green-50 border-green-200 dark:bg-green-950/20 dark:border-green-800/50',
text: 'text-green-800 dark:text-green-300',
title: 'text-green-800 dark:text-green-300 font-medium',
icon: <Check className='mr-2 h-4 w-4 flex-shrink-0 text-green-500 dark:text-green-400' />,
},
error: {
container: 'bg-red-50 border-red-200 dark:bg-red-950/20 dark:border-red-800/50',
text: 'text-red-800 dark:text-red-300',
title: 'text-red-800 dark:text-red-300 font-medium',
icon: <AlertCircle className='mr-2 h-4 w-4 flex-shrink-0 text-red-500 dark:text-red-400' />,
},
}
export function Notice({ children, variant = 'info', className, icon, title }: NoticeProps) {
const styles = variantStyles[variant]
return (
<div className={cn('flex rounded-md border p-3', styles.container, className)}>
<div className='flex items-start'>
{icon !== null && (icon || styles.icon)}
<div className='flex-1'>
{title && <div className={cn('mb-1', styles.title)}>{title}</div>}
<div className={cn('text-sm', styles.text)}>{children}</div>
</div>
</div>
</div>
)
}

View File

@@ -1,30 +0,0 @@
'use client'
import * as React from 'react'
import * as PopoverPrimitive from '@radix-ui/react-popover'
import { cn } from '@/lib/core/utils/cn'
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=closed]:animate-out data-[state=open]:animate-in',
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent }

View File

@@ -1,37 +0,0 @@
'use client'
import * as React from 'react'
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
import { Circle } from 'lucide-react'
import { cn } from '@/lib/core/utils/cn'
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return <RadioGroupPrimitive.Root className={cn('grid gap-2', className)} {...props} ref={ref} />
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
'aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}
>
<RadioGroupPrimitive.Indicator className='flex items-center justify-center'>
<Circle className='h-2.5 w-2.5 fill-current text-current' />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
})
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
export { RadioGroup, RadioGroupItem }

View File

@@ -1,121 +0,0 @@
'use client'
import * as React from 'react'
import * as SheetPrimitive from '@radix-ui/react-dialog'
import { cva, type VariantProps } from 'class-variance-authority'
import { X } from 'lucide-react'
import { cn } from '@/lib/core/utils/cn'
const Sheet = SheetPrimitive.Root
const SheetTrigger = SheetPrimitive.Trigger
const SheetClose = SheetPrimitive.Close
const SheetPortal = SheetPrimitive.Portal
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, style, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-white/50 data-[state=closed]:animate-out data-[state=open]:animate-in dark:bg-black/50',
className
)}
style={{ backdropFilter: 'blur(4.8px)', ...style }}
{...props}
ref={ref}
/>
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
{
variants: {
side: {
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
bottom:
'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
right:
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
},
},
defaultVariants: {
side: 'right',
},
}
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = 'right', className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
{children}
<SheetPrimitive.Close className='absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary'>
<X className='h-4 w-4' />
<span className='sr-only'>Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
))
SheetContent.displayName = SheetPrimitive.Content.displayName
const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
)
SheetHeader.displayName = 'SheetHeader'
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...props}
/>
)
SheetFooter.displayName = 'SheetFooter'
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn('font-semibold text-foreground text-lg', className)}
{...props}
/>
))
SheetTitle.displayName = SheetPrimitive.Title.displayName
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
))
SheetDescription.displayName = SheetPrimitive.Description.displayName
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}

View File

@@ -1,54 +0,0 @@
'use client'
import * as React from 'react'
import * as TabsPrimitive from '@radix-ui/react-tabs'
import { cn } from '@/lib/core/utils/cn'
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 font-medium text-sm ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@@ -1,43 +0,0 @@
'use client'
import * as React from 'react'
import * as TogglePrimitive from '@radix-ui/react-toggle'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/core/utils/cn'
// TODO: FIX STYLING
const toggleVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
{
variants: {
variant: {
default: 'bg-transparent',
outline: 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
},
size: {
default: 'h-10 px-3',
sm: 'h-9 px-2.5',
lg: 'h-11 px-5',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
)
const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>
>(({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
))
Toggle.displayName = TogglePrimitive.Root.displayName
export { Toggle, toggleVariants }