mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
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:
@@ -134,8 +134,8 @@ export function Knowledge() {
|
|||||||
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[28px] pb-[24px]'>
|
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[28px] pb-[24px]'>
|
||||||
<div>
|
<div>
|
||||||
<div className='flex items-start gap-[12px]'>
|
<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]'>
|
<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-[#34D399]' />
|
<Database className='h-[14px] w-[14px] text-[#5BB377] dark:text-[#34D399]' />
|
||||||
</div>
|
</div>
|
||||||
<h1 className='font-medium text-[18px]'>Knowledge Base</h1>
|
<h1 className='font-medium text-[18px]'>Knowledge Base</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -295,8 +295,8 @@ export function LogsToolbar({
|
|||||||
{/* Header Section */}
|
{/* Header Section */}
|
||||||
<div className='flex items-start justify-between'>
|
<div className='flex items-start justify-between'>
|
||||||
<div className='flex items-start gap-[12px]'>
|
<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]'>
|
<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-[#FBBC04]' />
|
<Library className='h-[14px] w-[14px] text-[#D4A843] dark:text-[#FBBC04]' />
|
||||||
</div>
|
</div>
|
||||||
<h1 className='font-medium text-[18px]'>Logs</h1>
|
<h1 className='font-medium text-[18px]'>Logs</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -175,8 +175,8 @@ export default function Templates({
|
|||||||
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[28px] pb-[24px]'>
|
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[28px] pb-[24px]'>
|
||||||
<div>
|
<div>
|
||||||
<div className='flex items-start gap-[12px]'>
|
<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]'>
|
<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-[#33b4ff]' />
|
<Layout className='h-[14px] w-[14px] text-[#5BA8D9] dark:text-[#33b4ff]' />
|
||||||
</div>
|
</div>
|
||||||
<h1 className='font-medium text-[18px]'>Templates</h1>
|
<h1 className='font-medium text-[18px]'>Templates</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import type React from 'react'
|
||||||
import { Check } from 'lucide-react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
import { Check, RepeatIcon, SplitIcon } from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Popover,
|
Popover,
|
||||||
@@ -19,6 +20,32 @@ import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
|
|||||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||||
import { useWorkflowStore } from '@/stores/workflows/workflow/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
|
* Props for the OutputSelect component
|
||||||
*/
|
*/
|
||||||
@@ -71,7 +98,6 @@ export function OutputSelect({
|
|||||||
const [highlightedIndex, setHighlightedIndex] = useState(-1)
|
const [highlightedIndex, setHighlightedIndex] = useState(-1)
|
||||||
const triggerRef = useRef<HTMLDivElement>(null)
|
const triggerRef = useRef<HTMLDivElement>(null)
|
||||||
const popoverRef = useRef<HTMLDivElement>(null)
|
const popoverRef = useRef<HTMLDivElement>(null)
|
||||||
const contentRef = useRef<HTMLDivElement>(null)
|
|
||||||
const blocks = useWorkflowStore((state) => state.blocks)
|
const blocks = useWorkflowStore((state) => state.blocks)
|
||||||
const { isShowingDiff, isDiffReady, hasActiveDiff, baselineWorkflow } = useWorkflowDiffStore()
|
const { isShowingDiff, isDiffReady, hasActiveDiff, baselineWorkflow } = useWorkflowDiffStore()
|
||||||
const subBlockValues = useSubBlockStore((state) =>
|
const subBlockValues = useSubBlockStore((state) =>
|
||||||
@@ -185,8 +211,11 @@ export function OutputSelect({
|
|||||||
* @param o - The output object to check
|
* @param o - The output object to check
|
||||||
* @returns True if the output is selected, false otherwise
|
* @returns True if the output is selected, false otherwise
|
||||||
*/
|
*/
|
||||||
const isSelectedValue = (o: { id: string; label: string }) =>
|
const isSelectedValue = useCallback(
|
||||||
selectedOutputs.includes(o.id) || selectedOutputs.includes(o.label)
|
(o: { id: string; label: string }) =>
|
||||||
|
selectedOutputs.includes(o.id) || selectedOutputs.includes(o.label),
|
||||||
|
[selectedOutputs]
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets display text for selected outputs
|
* Gets display text for selected outputs
|
||||||
@@ -292,82 +321,94 @@ export function OutputSelect({
|
|||||||
* Handles output selection by toggling the selected state
|
* Handles output selection by toggling the selected state
|
||||||
* @param value - The output label to toggle
|
* @param value - The output label to toggle
|
||||||
*/
|
*/
|
||||||
const handleOutputSelection = (value: string) => {
|
const handleOutputSelection = useCallback(
|
||||||
const emittedValue =
|
(value: string) => {
|
||||||
valueMode === 'label' ? value : workflowOutputs.find((o) => o.label === value)?.id || value
|
const emittedValue =
|
||||||
const index = selectedOutputs.indexOf(emittedValue)
|
valueMode === 'label' ? value : workflowOutputs.find((o) => o.label === value)?.id || value
|
||||||
|
const index = selectedOutputs.indexOf(emittedValue)
|
||||||
|
|
||||||
const newSelectedOutputs =
|
const newSelectedOutputs =
|
||||||
index === -1
|
index === -1
|
||||||
? [...new Set([...selectedOutputs, emittedValue])]
|
? [...new Set([...selectedOutputs, emittedValue])]
|
||||||
: selectedOutputs.filter((id) => id !== emittedValue)
|
: selectedOutputs.filter((id) => id !== emittedValue)
|
||||||
|
|
||||||
onOutputSelect(newSelectedOutputs)
|
onOutputSelect(newSelectedOutputs)
|
||||||
}
|
},
|
||||||
|
[valueMode, workflowOutputs, selectedOutputs, onOutputSelect]
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles keyboard navigation within the output list
|
* Handles keyboard navigation within the output list
|
||||||
* Supports ArrowUp, ArrowDown, Enter, and Escape keys
|
* Supports ArrowUp, ArrowDown, Enter, and Escape keys
|
||||||
* @param e - Keyboard event
|
|
||||||
*/
|
*/
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
useEffect(() => {
|
||||||
if (flattenedOutputs.length === 0) return
|
if (!open || flattenedOutputs.length === 0) return
|
||||||
|
|
||||||
switch (e.key) {
|
const handleKeyboardEvent = (e: KeyboardEvent) => {
|
||||||
case 'ArrowDown':
|
switch (e.key) {
|
||||||
e.preventDefault()
|
case 'ArrowDown':
|
||||||
setHighlightedIndex((prev) => {
|
e.preventDefault()
|
||||||
const next = prev < flattenedOutputs.length - 1 ? prev + 1 : 0
|
e.stopPropagation()
|
||||||
return next
|
setHighlightedIndex((prev) => {
|
||||||
})
|
if (prev === -1 || prev >= flattenedOutputs.length - 1) {
|
||||||
break
|
return 0
|
||||||
|
}
|
||||||
|
return prev + 1
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setHighlightedIndex((prev) => {
|
e.stopPropagation()
|
||||||
const next = prev > 0 ? prev - 1 : flattenedOutputs.length - 1
|
setHighlightedIndex((prev) => {
|
||||||
return next
|
if (prev <= 0) {
|
||||||
})
|
return flattenedOutputs.length - 1
|
||||||
break
|
}
|
||||||
|
return prev - 1
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (highlightedIndex >= 0 && highlightedIndex < flattenedOutputs.length) {
|
e.stopPropagation()
|
||||||
handleOutputSelection(flattenedOutputs[highlightedIndex].label)
|
setHighlightedIndex((currentIndex) => {
|
||||||
}
|
if (currentIndex >= 0 && currentIndex < flattenedOutputs.length) {
|
||||||
break
|
handleOutputSelection(flattenedOutputs[currentIndex].label)
|
||||||
|
}
|
||||||
|
return currentIndex
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setOpen(false)
|
e.stopPropagation()
|
||||||
break
|
setOpen(false)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
window.addEventListener('keydown', handleKeyboardEvent, true)
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyboardEvent, true)
|
||||||
|
}, [open, flattenedOutputs, handleOutputSelection])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset highlighted index when popover opens/closes
|
* Reset highlighted index when popover opens/closes
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
// Find first selected item, or start at -1
|
|
||||||
const firstSelectedIndex = flattenedOutputs.findIndex((output) => isSelectedValue(output))
|
const firstSelectedIndex = flattenedOutputs.findIndex((output) => isSelectedValue(output))
|
||||||
setHighlightedIndex(firstSelectedIndex >= 0 ? firstSelectedIndex : -1)
|
setHighlightedIndex(firstSelectedIndex >= 0 ? firstSelectedIndex : -1)
|
||||||
|
|
||||||
// Focus the content for keyboard navigation
|
|
||||||
setTimeout(() => {
|
|
||||||
contentRef.current?.focus()
|
|
||||||
}, 0)
|
|
||||||
} else {
|
} else {
|
||||||
setHighlightedIndex(-1)
|
setHighlightedIndex(-1)
|
||||||
}
|
}
|
||||||
}, [open, flattenedOutputs])
|
}, [open, flattenedOutputs, isSelectedValue])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll highlighted item into view
|
* Scroll highlighted item into view
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (highlightedIndex >= 0 && contentRef.current) {
|
if (highlightedIndex >= 0 && popoverRef.current) {
|
||||||
const highlightedElement = contentRef.current.querySelector(
|
const highlightedElement = popoverRef.current.querySelector(
|
||||||
`[data-option-index="${highlightedIndex}"]`
|
`[data-option-index="${highlightedIndex}"]`
|
||||||
)
|
)
|
||||||
if (highlightedElement) {
|
if (highlightedElement) {
|
||||||
@@ -425,18 +466,35 @@ export function OutputSelect({
|
|||||||
minWidth={160}
|
minWidth={160}
|
||||||
border
|
border
|
||||||
disablePortal={disablePopoverPortal}
|
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]) => {
|
{Object.entries(groupedOutputs).map(([blockName, outputs]) => {
|
||||||
// Calculate the starting index for this group
|
|
||||||
const startIndex = flattenedOutputs.findIndex((o) => o.blockName === blockName)
|
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 (
|
return (
|
||||||
<div key={blockName}>
|
<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]'>
|
<div className='flex flex-col gap-[2px]'>
|
||||||
{outputs.map((output, localIndex) => {
|
{outputs.map((output, localIndex) => {
|
||||||
@@ -451,17 +509,9 @@ export function OutputSelect({
|
|||||||
onClick={() => handleOutputSelection(output.label)}
|
onClick={() => handleOutputSelection(output.label)}
|
||||||
onMouseEnter={() => setHighlightedIndex(globalIndex)}
|
onMouseEnter={() => setHighlightedIndex(globalIndex)}
|
||||||
>
|
>
|
||||||
<div
|
<span className='min-w-0 flex-1 truncate text-[var(--text-primary)]'>
|
||||||
className='flex h-[14px] w-[14px] flex-shrink-0 items-center justify-center rounded'
|
{output.path}
|
||||||
style={{
|
</span>
|
||||||
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>
|
|
||||||
{isSelectedValue(output) && <Check className='h-3 w-3 flex-shrink-0' />}
|
{isSelectedValue(output) && <Check className='h-3 w-3 flex-shrink-0' />}
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -99,15 +99,12 @@ const createHighlightFunction = (
|
|||||||
const placeholders: CodePlaceholder[] = []
|
const placeholders: CodePlaceholder[] = []
|
||||||
let processedCode = codeToHighlight
|
let processedCode = codeToHighlight
|
||||||
|
|
||||||
// Replace environment variables with placeholders
|
|
||||||
processedCode = processedCode.replace(createEnvVarPattern(), (match) => {
|
processedCode = processedCode.replace(createEnvVarPattern(), (match) => {
|
||||||
const placeholder = `__ENV_VAR_${placeholders.length}__`
|
const placeholder = `__ENV_VAR_${placeholders.length}__`
|
||||||
placeholders.push({ placeholder, original: match, type: 'env' })
|
placeholders.push({ placeholder, original: match, type: 'env' })
|
||||||
return placeholder
|
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) => {
|
processedCode = processedCode.replace(createReferencePattern(), (match) => {
|
||||||
if (shouldHighlightReference(match)) {
|
if (shouldHighlightReference(match)) {
|
||||||
const placeholder = `__VAR_REF_${placeholders.length}__`
|
const placeholder = `__VAR_REF_${placeholders.length}__`
|
||||||
@@ -117,25 +114,22 @@ const createHighlightFunction = (
|
|||||||
return match
|
return match
|
||||||
})
|
})
|
||||||
|
|
||||||
// Apply Prism syntax highlighting
|
|
||||||
const lang = effectiveLanguage === 'python' ? 'python' : 'javascript'
|
const lang = effectiveLanguage === 'python' ? 'python' : 'javascript'
|
||||||
let highlightedCode = highlight(processedCode, languages[lang], lang)
|
let highlightedCode = highlight(processedCode, languages[lang], lang)
|
||||||
|
|
||||||
// Apply dark mode token styling
|
|
||||||
highlightedCode = applyDarkModeTokenStyling(highlightedCode)
|
highlightedCode = applyDarkModeTokenStyling(highlightedCode)
|
||||||
|
|
||||||
// Restore and highlight the placeholders
|
|
||||||
placeholders.forEach(({ placeholder, original, type }) => {
|
placeholders.forEach(({ placeholder, original, type }) => {
|
||||||
if (type === 'env') {
|
if (type === 'env') {
|
||||||
highlightedCode = highlightedCode.replace(
|
highlightedCode = highlightedCode.replace(
|
||||||
placeholder,
|
placeholder,
|
||||||
`<span class="text-blue-500">${original}</span>`
|
`<span style="color: var(--brand-secondary);">${original}</span>`
|
||||||
)
|
)
|
||||||
} else if (type === 'var') {
|
} else if (type === 'var') {
|
||||||
const escaped = original.replace(/</g, '<').replace(/>/g, '>')
|
const escaped = original.replace(/</g, '<').replace(/>/g, '>')
|
||||||
highlightedCode = highlightedCode.replace(
|
highlightedCode = highlightedCode.replace(
|
||||||
placeholder,
|
placeholder,
|
||||||
`<span class="text-blue-500">${escaped}</span>`
|
`<span style="color: var(--brand-secondary);">${escaped}</span>`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -194,11 +188,9 @@ export function Code({
|
|||||||
wandControlRef,
|
wandControlRef,
|
||||||
hideInternalWand = false,
|
hideInternalWand = false,
|
||||||
}: CodeProps) {
|
}: CodeProps) {
|
||||||
// Route params
|
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const workspaceId = params.workspaceId as string
|
const workspaceId = params.workspaceId as string
|
||||||
|
|
||||||
// Local state
|
|
||||||
const [code, setCode] = useState<string>('')
|
const [code, setCode] = useState<string>('')
|
||||||
const [showTags, setShowTags] = useState(false)
|
const [showTags, setShowTags] = useState(false)
|
||||||
const [showEnvVars, setShowEnvVars] = useState(false)
|
const [showEnvVars, setShowEnvVars] = useState(false)
|
||||||
@@ -209,19 +201,16 @@ export function Code({
|
|||||||
const [activeLineNumber, setActiveLineNumber] = useState(1)
|
const [activeLineNumber, setActiveLineNumber] = useState(1)
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
|
|
||||||
// Refs
|
|
||||||
const editorRef = useRef<HTMLDivElement>(null)
|
const editorRef = useRef<HTMLDivElement>(null)
|
||||||
const handleStreamStartRef = useRef<() => void>(() => {})
|
const handleStreamStartRef = useRef<() => void>(() => {})
|
||||||
const handleGeneratedContentRef = useRef<(generatedCode: string) => void>(() => {})
|
const handleGeneratedContentRef = useRef<(generatedCode: string) => void>(() => {})
|
||||||
const handleStreamChunkRef = useRef<(chunk: string) => void>(() => {})
|
const handleStreamChunkRef = useRef<(chunk: string) => void>(() => {})
|
||||||
const hasEditedSinceFocusRef = useRef(false)
|
const hasEditedSinceFocusRef = useRef(false)
|
||||||
|
|
||||||
// Custom hooks
|
|
||||||
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
|
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
|
||||||
const emitTagSelection = useTagSelection(blockId, subBlockId)
|
const emitTagSelection = useTagSelection(blockId, subBlockId)
|
||||||
const [languageValue] = useSubBlockValue<string>(blockId, 'language')
|
const [languageValue] = useSubBlockValue<string>(blockId, 'language')
|
||||||
|
|
||||||
// Derived state
|
|
||||||
const effectiveLanguage = (languageValue as 'javascript' | 'python' | 'json') || language
|
const effectiveLanguage = (languageValue as 'javascript' | 'python' | 'json') || language
|
||||||
|
|
||||||
const trimmedCode = code.trim()
|
const trimmedCode = code.trim()
|
||||||
@@ -279,7 +268,6 @@ export function Code({
|
|||||||
return wandConfig
|
return wandConfig
|
||||||
}, [wandConfig, languageValue])
|
}, [wandConfig, languageValue])
|
||||||
|
|
||||||
// AI code generation integration
|
|
||||||
const wandHook = useWand({
|
const wandHook = useWand({
|
||||||
wandConfig: dynamicWandConfig || { enabled: false, prompt: '' },
|
wandConfig: dynamicWandConfig || { enabled: false, prompt: '' },
|
||||||
currentValue: code,
|
currentValue: code,
|
||||||
@@ -298,7 +286,6 @@ export function Code({
|
|||||||
const updatePromptValue = wandHook?.updatePromptValue || (() => {})
|
const updatePromptValue = wandHook?.updatePromptValue || (() => {})
|
||||||
const cancelGeneration = wandHook?.cancelGeneration || (() => {})
|
const cancelGeneration = wandHook?.cancelGeneration || (() => {})
|
||||||
|
|
||||||
// Store integration
|
|
||||||
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId, false, {
|
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId, false, {
|
||||||
isStreaming: isAiStreaming,
|
isStreaming: isAiStreaming,
|
||||||
onStreamingEnd: () => {
|
onStreamingEnd: () => {
|
||||||
@@ -320,7 +307,6 @@ export function Code({
|
|||||||
? getDefaultValueString()
|
? getDefaultValueString()
|
||||||
: storeValue
|
: storeValue
|
||||||
|
|
||||||
// Effects: JSON validation
|
|
||||||
const lastValidationStatus = useRef<boolean>(true)
|
const lastValidationStatus = useRef<boolean>(true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -345,7 +331,6 @@ export function Code({
|
|||||||
return () => clearTimeout(timeoutId)
|
return () => clearTimeout(timeoutId)
|
||||||
}, [isValidJson, onValidationChange, shouldValidateJson])
|
}, [isValidJson, onValidationChange, shouldValidateJson])
|
||||||
|
|
||||||
// Effects: AI stream handlers setup
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleStreamStartRef.current = () => {
|
handleStreamStartRef.current = () => {
|
||||||
setCode('')
|
setCode('')
|
||||||
@@ -359,7 +344,6 @@ export function Code({
|
|||||||
}
|
}
|
||||||
}, [isPreview, disabled, setStoreValue])
|
}, [isPreview, disabled, setStoreValue])
|
||||||
|
|
||||||
// Effects: Set read only state for textarea
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editorRef.current) return
|
if (!editorRef.current) return
|
||||||
|
|
||||||
@@ -388,7 +372,6 @@ export function Code({
|
|||||||
}
|
}
|
||||||
}, [readOnly])
|
}, [readOnly])
|
||||||
|
|
||||||
// Effects: Sync code with external value
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAiStreaming) return
|
if (isAiStreaming) return
|
||||||
const valueString = value?.toString() ?? ''
|
const valueString = value?.toString() ?? ''
|
||||||
@@ -397,7 +380,6 @@ export function Code({
|
|||||||
}
|
}
|
||||||
}, [value, code, isAiStreaming])
|
}, [value, code, isAiStreaming])
|
||||||
|
|
||||||
// Effects: Track active line number for cursor position
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const textarea = editorRef.current?.querySelector('textarea')
|
const textarea = editorRef.current?.querySelector('textarea')
|
||||||
if (!textarea) return
|
if (!textarea) return
|
||||||
@@ -422,7 +404,6 @@ export function Code({
|
|||||||
}
|
}
|
||||||
}, [code])
|
}, [code])
|
||||||
|
|
||||||
// Effects: Calculate visual line heights for proper gutter alignment
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editorRef.current) return
|
if (!editorRef.current) return
|
||||||
|
|
||||||
|
|||||||
@@ -154,13 +154,9 @@ export function ConditionInput({
|
|||||||
const removeEdge = useWorkflowStore((state) => state.removeEdge)
|
const removeEdge = useWorkflowStore((state) => state.removeEdge)
|
||||||
const edges = useWorkflowStore((state) => state.edges)
|
const edges = useWorkflowStore((state) => state.edges)
|
||||||
|
|
||||||
// Use a ref to track the previous store value for comparison
|
|
||||||
const prevStoreValueRef = useRef<string | null>(null)
|
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)
|
const isSyncingFromStoreRef = useRef(false)
|
||||||
// Use a ref to track if we've already initialized from store
|
|
||||||
const hasInitializedRef = useRef(false)
|
const hasInitializedRef = useRef(false)
|
||||||
// Track previous blockId to detect workflow changes
|
|
||||||
const previousBlockIdRef = useRef<string>(blockId)
|
const previousBlockIdRef = useRef<string>(blockId)
|
||||||
const shouldPersistRef = useRef<boolean>(false)
|
const shouldPersistRef = useRef<boolean>(false)
|
||||||
|
|
||||||
@@ -869,7 +865,6 @@ export function ConditionInput({
|
|||||||
}[] = []
|
}[] = []
|
||||||
let processedCode = codeToHighlight
|
let processedCode = codeToHighlight
|
||||||
|
|
||||||
// Replace environment variables with placeholders
|
|
||||||
processedCode = processedCode.replace(createEnvVarPattern(), (match) => {
|
processedCode = processedCode.replace(createEnvVarPattern(), (match) => {
|
||||||
const placeholder = `__ENV_VAR_${placeholders.length}__`
|
const placeholder = `__ENV_VAR_${placeholders.length}__`
|
||||||
placeholders.push({
|
placeholders.push({
|
||||||
@@ -881,8 +876,6 @@ export function ConditionInput({
|
|||||||
return placeholder
|
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(
|
processedCode = processedCode.replace(
|
||||||
createReferencePattern(),
|
createReferencePattern(),
|
||||||
(match) => {
|
(match) => {
|
||||||
@@ -901,14 +894,12 @@ export function ConditionInput({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Apply Prism syntax highlighting
|
|
||||||
let highlightedCode = highlight(
|
let highlightedCode = highlight(
|
||||||
processedCode,
|
processedCode,
|
||||||
languages.javascript,
|
languages.javascript,
|
||||||
'javascript'
|
'javascript'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Restore and highlight the placeholders
|
|
||||||
placeholders.forEach(
|
placeholders.forEach(
|
||||||
({ placeholder, original, type, shouldHighlight }) => {
|
({ placeholder, original, type, shouldHighlight }) => {
|
||||||
if (!shouldHighlight) return
|
if (!shouldHighlight) return
|
||||||
@@ -916,14 +907,13 @@ export function ConditionInput({
|
|||||||
if (type === 'env') {
|
if (type === 'env') {
|
||||||
highlightedCode = highlightedCode.replace(
|
highlightedCode = highlightedCode.replace(
|
||||||
placeholder,
|
placeholder,
|
||||||
`<span class="text-blue-500">${original}</span>`
|
`<span style="color: var(--brand-secondary);">${original}</span>`
|
||||||
)
|
)
|
||||||
} else if (type === 'var') {
|
} else if (type === 'var') {
|
||||||
// Escape the < and > for display
|
|
||||||
const escaped = original.replace(/</g, '<').replace(/>/g, '>')
|
const escaped = original.replace(/</g, '<').replace(/>/g, '>')
|
||||||
highlightedCode = highlightedCode.replace(
|
highlightedCode = highlightedCode.replace(
|
||||||
placeholder,
|
placeholder,
|
||||||
`<span class="text-blue-500">${escaped}</span>`
|
`<span style="color: var(--brand-secondary);">${escaped}</span>`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ export function OAuthRequiredModal({
|
|||||||
<div className='flex flex-1 items-center gap-[8px] text-[12px] text-[var(--text-primary)]'>
|
<div className='flex flex-1 items-center gap-[8px] text-[12px] text-[var(--text-primary)]'>
|
||||||
<span>{getScopeDescription(scope)}</span>
|
<span>{getScopeDescription(scope)}</span>
|
||||||
{newScopesSet.has(scope) && (
|
{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
|
New
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function formatDisplayText(text: string, context?: HighlightContext): Rea
|
|||||||
|
|
||||||
if (matchText.startsWith(REFERENCE.ENV_VAR_START)) {
|
if (matchText.startsWith(REFERENCE.ENV_VAR_START)) {
|
||||||
nodes.push(
|
nodes.push(
|
||||||
<span key={key++} className='text-[#34B5FF]'>
|
<span key={key++} className='text-[var(--brand-secondary)]'>
|
||||||
{matchText}
|
{matchText}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
@@ -77,7 +77,7 @@ export function formatDisplayText(text: string, context?: HighlightContext): Rea
|
|||||||
if (split && shouldHighlightReference(split.reference)) {
|
if (split && shouldHighlightReference(split.reference)) {
|
||||||
pushPlainText(split.leading)
|
pushPlainText(split.leading)
|
||||||
nodes.push(
|
nodes.push(
|
||||||
<span key={key++} className='text-[#34B5FF]'>
|
<span key={key++} className='text-[var(--brand-secondary)]'>
|
||||||
{split.reference}
|
{split.reference}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1223,6 +1223,10 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
directTags.forEach((directTag) => {
|
||||||
|
nestedTags.push(directTag)
|
||||||
|
})
|
||||||
|
|
||||||
Object.entries(groupedTags).forEach(([parent, children]) => {
|
Object.entries(groupedTags).forEach(([parent, children]) => {
|
||||||
const firstChildTag = children[0]?.fullTag
|
const firstChildTag = children[0]?.fullTag
|
||||||
if (firstChildTag) {
|
if (firstChildTag) {
|
||||||
@@ -1243,10 +1247,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
directTags.forEach((directTag) => {
|
|
||||||
nestedTags.push(directTag)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...group,
|
...group,
|
||||||
nestedTags,
|
nestedTags,
|
||||||
@@ -1262,7 +1262,17 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
nestedBlockTagGroups.forEach((group) => {
|
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) => {
|
group.nestedTags.forEach((nestedTag) => {
|
||||||
|
if (nestedTag.fullTag === rootTag) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (nestedTag.parentTag) {
|
if (nestedTag.parentTag) {
|
||||||
list.push({ tag: nestedTag.parentTag, group })
|
list.push({ tag: nestedTag.parentTag, group })
|
||||||
}
|
}
|
||||||
@@ -1280,7 +1290,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
return list
|
return list
|
||||||
}, [variableTags, nestedBlockTagGroups])
|
}, [variableTags, nestedBlockTagGroups])
|
||||||
|
|
||||||
// Auto-scroll selected item into view
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!visible || selectedIndex < 0) return
|
if (!visible || selectedIndex < 0) return
|
||||||
|
|
||||||
@@ -1318,7 +1327,7 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
|
|
||||||
const parts = tag.split('.')
|
const parts = tag.split('.')
|
||||||
if (parts.length >= 3 && blockGroup) {
|
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 block = useWorkflowStore.getState().blocks[blockGroup.blockId]
|
||||||
const blockConfig = block ? (getBlock(block.type) ?? null) : null
|
const blockConfig = block ? (getBlock(block.type) ?? null) : null
|
||||||
const mergedSubBlocks = getMergedSubBlocks(blockGroup.blockId)
|
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
|
if (!visible || tags.length === 0 || flatTagList.length === 0) return null
|
||||||
|
|
||||||
// Calculate caret position for proper anchoring
|
|
||||||
const inputElement = inputRef?.current
|
const inputElement = inputRef?.current
|
||||||
let caretViewport = { left: 0, top: 0 }
|
let caretViewport = { left: 0, top: 0 }
|
||||||
let side: 'top' | 'bottom' = 'bottom'
|
let side: 'top' | 'bottom' = 'bottom'
|
||||||
@@ -1516,7 +1524,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
blockColor = BLOCK_COLORS.PARALLEL
|
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
|
let tagIcon: string | React.ComponentType<{ className?: string }> = group.blockName
|
||||||
.charAt(0)
|
.charAt(0)
|
||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
@@ -1528,10 +1535,41 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
tagIcon = SplitIcon
|
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 (
|
return (
|
||||||
<div key={group.blockId}>
|
<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) => {
|
{group.nestedTags.map((nestedTag) => {
|
||||||
|
if (nestedTag.fullTag === rootTag) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const hasChildren = nestedTag.children && nestedTag.children.length > 0
|
const hasChildren = nestedTag.children && nestedTag.children.length > 0
|
||||||
|
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
@@ -1546,7 +1584,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
key={folderId}
|
key={folderId}
|
||||||
id={folderId}
|
id={folderId}
|
||||||
title={nestedTag.display}
|
title={nestedTag.display}
|
||||||
icon={<TagIcon icon={tagIcon} color={blockColor} />}
|
|
||||||
active={parentGlobalIndex === selectedIndex && parentGlobalIndex >= 0}
|
active={parentGlobalIndex === selectedIndex && parentGlobalIndex >= 0}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
if (nestedTag.parentTag) {
|
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)]'>
|
<span className='flex-1 truncate text-[var(--text-primary)]'>
|
||||||
{child.display}
|
{child.display}
|
||||||
</span>
|
</span>
|
||||||
@@ -1625,26 +1661,21 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct tag (no children)
|
|
||||||
const globalIndex = nestedTag.fullTag
|
const globalIndex = nestedTag.fullTag
|
||||||
? flatTagList.findIndex((item) => item.tag === nestedTag.fullTag)
|
? flatTagList.findIndex((item) => item.tag === nestedTag.fullTag)
|
||||||
: -1
|
: -1
|
||||||
|
|
||||||
let tagDescription = ''
|
let tagDescription = ''
|
||||||
let displayIcon = tagIcon
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(group.blockType === 'loop' || group.blockType === 'parallel') &&
|
(group.blockType === 'loop' || group.blockType === 'parallel') &&
|
||||||
!nestedTag.key.includes('.')
|
!nestedTag.key.includes('.')
|
||||||
) {
|
) {
|
||||||
if (nestedTag.key === 'index') {
|
if (nestedTag.key === 'index') {
|
||||||
displayIcon = '#'
|
|
||||||
tagDescription = 'number'
|
tagDescription = 'number'
|
||||||
} else if (nestedTag.key === 'currentItem') {
|
} else if (nestedTag.key === 'currentItem') {
|
||||||
displayIcon = 'i'
|
|
||||||
tagDescription = 'any'
|
tagDescription = 'any'
|
||||||
} else if (nestedTag.key === 'items') {
|
} else if (nestedTag.key === 'items') {
|
||||||
displayIcon = 'I'
|
|
||||||
tagDescription = 'array'
|
tagDescription = 'array'
|
||||||
}
|
}
|
||||||
} else if (nestedTag.fullTag) {
|
} 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)]'>
|
<span className='flex-1 truncate text-[var(--text-primary)]'>
|
||||||
{nestedTag.display}
|
{nestedTag.display}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -176,8 +176,8 @@ export function CodeEditor({
|
|||||||
const escapedOriginal = type === 'variable' ? escapeHtml(original) : original
|
const escapedOriginal = type === 'variable' ? escapeHtml(original) : original
|
||||||
const replacement =
|
const replacement =
|
||||||
type === 'env' || type === 'variable'
|
type === 'env' || type === 'variable'
|
||||||
? `<span style="color: #34B5FF;">${escapedOriginal}</span>`
|
? `<span style="color: var(--brand-secondary);">${escapedOriginal}</span>`
|
||||||
: `<span style="color: #34B5FF; font-weight: 500;">${original}</span>`
|
: `<span style="color: var(--brand-secondary); font-weight: 500;">${original}</span>`
|
||||||
|
|
||||||
highlighted = highlighted.replace(placeholder, replacement)
|
highlighted = highlighted.replace(placeholder, replacement)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { AlertCircle, Wand2 } from 'lucide-react'
|
import { AlertCircle, Wand2 } from 'lucide-react'
|
||||||
import { useParams } from 'next/navigation'
|
import { useParams } from 'next/navigation'
|
||||||
import {
|
import {
|
||||||
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
Modal,
|
Modal,
|
||||||
ModalBody,
|
ModalBody,
|
||||||
@@ -236,8 +237,8 @@ try {
|
|||||||
},
|
},
|
||||||
currentValue: functionCode,
|
currentValue: functionCode,
|
||||||
onGeneratedContent: (content) => {
|
onGeneratedContent: (content) => {
|
||||||
handleFunctionCodeChange(content) // Use existing handler to also trigger dropdown checks
|
handleFunctionCodeChange(content)
|
||||||
setCodeError(null) // Clear error on successful generation
|
setCodeError(null)
|
||||||
},
|
},
|
||||||
onStreamChunk: (chunk) => {
|
onStreamChunk: (chunk) => {
|
||||||
setFunctionCode((prev) => {
|
setFunctionCode((prev) => {
|
||||||
@@ -258,6 +259,7 @@ try {
|
|||||||
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
|
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
|
||||||
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 })
|
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 })
|
||||||
const [schemaParamSelectedIndex, setSchemaParamSelectedIndex] = useState(0)
|
const [schemaParamSelectedIndex, setSchemaParamSelectedIndex] = useState(0)
|
||||||
|
const schemaParamItemRefs = useRef<Map<number, HTMLElement>>(new Map())
|
||||||
|
|
||||||
const createToolMutation = useCreateCustomTool()
|
const createToolMutation = useCreateCustomTool()
|
||||||
const updateToolMutation = useUpdateCustomTool()
|
const updateToolMutation = useUpdateCustomTool()
|
||||||
@@ -286,6 +288,18 @@ try {
|
|||||||
}
|
}
|
||||||
}, [open])
|
}, [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 = () => {
|
const resetForm = () => {
|
||||||
setJsonSchema('')
|
setJsonSchema('')
|
||||||
setFunctionCode('')
|
setFunctionCode('')
|
||||||
@@ -341,7 +355,6 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Extracts parameters from JSON schema for autocomplete */
|
|
||||||
const schemaParameters = useMemo(() => {
|
const schemaParameters = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
if (!jsonSchema) return []
|
if (!jsonSchema) return []
|
||||||
@@ -360,7 +373,6 @@ try {
|
|||||||
}
|
}
|
||||||
}, [jsonSchema])
|
}, [jsonSchema])
|
||||||
|
|
||||||
/** Memoized schema validation result */
|
|
||||||
const isSchemaValid = useMemo(() => validateJsonSchema(jsonSchema), [jsonSchema])
|
const isSchemaValid = useMemo(() => validateJsonSchema(jsonSchema), [jsonSchema])
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
@@ -454,7 +466,6 @@ try {
|
|||||||
code: functionCode || '',
|
code: functionCode || '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// Get the ID from the created tool
|
|
||||||
savedToolId = result?.[0]?.id
|
savedToolId = result?.[0]?.id
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -874,11 +885,6 @@ try {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex min-w-0 flex-1 items-center justify-end gap-1 pr-[4px]'>
|
<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 ? (
|
{!isSchemaPromptActive ? (
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
@@ -953,11 +959,6 @@ try {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex min-w-0 flex-1 items-center justify-end gap-1 pr-[4px]'>
|
<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 ? (
|
{!isCodePromptActive ? (
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
@@ -985,18 +986,19 @@ try {
|
|||||||
</div>
|
</div>
|
||||||
{schemaParameters.length > 0 && (
|
{schemaParameters.length > 0 && (
|
||||||
<div className='mb-2 rounded-[6px] border bg-[var(--surface-2)] p-2'>
|
<div className='mb-2 rounded-[6px] border bg-[var(--surface-2)] p-2'>
|
||||||
<p className='text-[var(--text-tertiary)] text-xs'>
|
<div className='flex flex-wrap items-center gap-1.5 text-xs'>
|
||||||
<span className='font-medium'>Available parameters:</span>{' '}
|
<span className='font-medium text-[var(--text-tertiary)]'>
|
||||||
{schemaParameters.map((param, index) => (
|
Available parameters:
|
||||||
<span key={param.name}>
|
</span>
|
||||||
<code className='rounded bg-[var(--bg)] px-1 py-0.5 text-[var(--text-primary)]'>
|
{schemaParameters.map((param) => (
|
||||||
{param.name}
|
<Badge key={param.name} variant='blue-secondary' size='sm'>
|
||||||
</code>
|
{param.name}
|
||||||
{index < schemaParameters.length - 1 && ', '}
|
</Badge>
|
||||||
</span>
|
|
||||||
))}
|
))}
|
||||||
{'. '}Start typing a parameter name for autocomplete.
|
<span className='text-[var(--text-tertiary)]'>
|
||||||
</p>
|
Start typing a parameter name for autocomplete.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div ref={codeEditorRef} className='relative'>
|
<div ref={codeEditorRef} className='relative'>
|
||||||
@@ -1085,7 +1087,7 @@ try {
|
|||||||
</PopoverAnchor>
|
</PopoverAnchor>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
maxHeight={240}
|
maxHeight={240}
|
||||||
className='min-w-[260px] max-w-[260px]'
|
className='min-w-[280px]'
|
||||||
side='bottom'
|
side='bottom'
|
||||||
align='start'
|
align='start'
|
||||||
collisionPadding={6}
|
collisionPadding={6}
|
||||||
@@ -1105,18 +1107,20 @@ try {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
handleSchemaParamSelect(param.name)
|
handleSchemaParamSelect(param.name)
|
||||||
}}
|
}}
|
||||||
className='flex items-center gap-2'
|
ref={(el) => {
|
||||||
|
if (el) {
|
||||||
|
schemaParamItemRefs.current.set(index, el)
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<span className='flex-1 truncate text-[var(--text-primary)]'>
|
||||||
className='flex h-5 w-5 items-center justify-center rounded'
|
{param.name}
|
||||||
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>
|
</span>
|
||||||
|
{param.type && param.type !== 'any' && (
|
||||||
|
<span className='ml-auto text-[10px] text-[var(--text-secondary)]'>
|
||||||
|
{param.type}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
))}
|
))}
|
||||||
</PopoverScrollArea>
|
</PopoverScrollArea>
|
||||||
|
|||||||
@@ -156,13 +156,13 @@ export function useSubflowEditor(currentBlock: BlockState | null, currentBlockId
|
|||||||
if (type === 'env') {
|
if (type === 'env') {
|
||||||
highlightedCode = highlightedCode.replace(
|
highlightedCode = highlightedCode.replace(
|
||||||
placeholder,
|
placeholder,
|
||||||
`<span class="text-blue-500">${original}</span>`
|
`<span style="color: var(--brand-secondary);">${original}</span>`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const escaped = original.replace(/</g, '<').replace(/>/g, '>')
|
const escaped = original.replace(/</g, '<').replace(/>/g, '>')
|
||||||
highlightedCode = highlightedCode.replace(
|
highlightedCode = highlightedCode.replace(
|
||||||
placeholder,
|
placeholder,
|
||||||
`<span class="text-blue-500">${escaped}</span>`
|
`<span style="color: var(--brand-secondary);">${escaped}</span>`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export const SubflowNodeComponent = memo(({ data, id }: NodeProps<SubflowNodeDat
|
|||||||
ref={blockRef}
|
ref={blockRef}
|
||||||
onClick={() => setCurrentBlockId(id)}
|
onClick={() => setCurrentBlockId(id)}
|
||||||
className={cn(
|
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',
|
'transition-block-bg transition-ring',
|
||||||
'z-[20]'
|
'z-[20]'
|
||||||
)}
|
)}
|
||||||
@@ -162,7 +162,7 @@ export const SubflowNodeComponent = memo(({ data, id }: NodeProps<SubflowNodeDat
|
|||||||
{/* Header Section */}
|
{/* Header Section */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
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) => {
|
onMouseDown={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|||||||
@@ -45,7 +45,11 @@ const PermissionSelector = React.memo<PermissionSelectorProps>(
|
|||||||
onClick={() => !disabled && onChange(option.value)}
|
onClick={() => !disabled && onChange(option.value)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
title={option.description}
|
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}
|
{option.label}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ export const PermissionSelector = React.memo<PermissionSelectorProps>(
|
|||||||
className={cn(
|
className={cn(
|
||||||
'px-[8px] py-[4px] text-[12px]',
|
'px-[8px] py-[4px] text-[12px]',
|
||||||
radiusClasses,
|
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}
|
{option.label}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function WorkflowPreviewSubflowInner({ data }: NodeProps<WorkflowPreviewSubflowD
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='relative select-none rounded-[8px] border border-[var(--divider)]'
|
className='relative select-none rounded-[8px] border border-[var(--border)]'
|
||||||
style={{
|
style={{
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@@ -56,7 +56,7 @@ function WorkflowPreviewSubflowInner({ data }: NodeProps<WorkflowPreviewSubflowD
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Header - matches actual subflow header */}
|
{/* 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
|
<div
|
||||||
className='flex h-[24px] w-[24px] flex-shrink-0 items-center justify-center rounded-[6px]'
|
className='flex h-[24px] w-[24px] flex-shrink-0 items-center justify-center rounded-[6px]'
|
||||||
style={{ backgroundColor: blockIconBg }}
|
style={{ backgroundColor: blockIconBg }}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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 }
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -1,44 +1,10 @@
|
|||||||
export { Alert, AlertDescription, AlertTitle } from './alert'
|
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 { Avatar, AvatarFallback, AvatarImage } from './avatar'
|
||||||
export { Badge, badgeVariants } from './badge'
|
export { Badge, badgeVariants } from './badge'
|
||||||
export {
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbEllipsis,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
} from './breadcrumb'
|
|
||||||
export { Button, buttonVariants } from './button'
|
export { Button, buttonVariants } from './button'
|
||||||
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './card'
|
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './card'
|
||||||
export { Checkbox } from './checkbox'
|
export { Checkbox } from './checkbox'
|
||||||
export { CodeBlock } from './code-block'
|
|
||||||
export { Collapsible, CollapsibleContent, CollapsibleTrigger } from './collapsible'
|
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 {
|
export {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogClose,
|
DialogClose,
|
||||||
@@ -68,24 +34,10 @@ export {
|
|||||||
DropdownMenuSubTrigger,
|
DropdownMenuSubTrigger,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from './dropdown-menu'
|
} from './dropdown-menu'
|
||||||
export {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
useFormField,
|
|
||||||
} from './form'
|
|
||||||
export { Input } from './input'
|
export { Input } from './input'
|
||||||
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from './input-otp'
|
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from './input-otp'
|
||||||
export { OTPInputForm } from './input-otp-form'
|
|
||||||
export { Label } from './label'
|
export { Label } from './label'
|
||||||
export { Notice } from './notice'
|
|
||||||
export { Popover, PopoverContent, PopoverTrigger } from './popover'
|
|
||||||
export { Progress } from './progress'
|
export { Progress } from './progress'
|
||||||
export { RadioGroup, RadioGroupItem } from './radio-group'
|
|
||||||
export { ScrollArea, ScrollBar } from './scroll-area'
|
export { ScrollArea, ScrollBar } from './scroll-area'
|
||||||
export { SearchHighlight } from './search-highlight'
|
export { SearchHighlight } from './search-highlight'
|
||||||
export {
|
export {
|
||||||
@@ -101,18 +53,6 @@ export {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from './select'
|
} from './select'
|
||||||
export { Separator } from './separator'
|
export { Separator } from './separator'
|
||||||
export {
|
|
||||||
Sheet,
|
|
||||||
SheetClose,
|
|
||||||
SheetContent,
|
|
||||||
SheetDescription,
|
|
||||||
SheetFooter,
|
|
||||||
SheetHeader,
|
|
||||||
SheetOverlay,
|
|
||||||
SheetPortal,
|
|
||||||
SheetTitle,
|
|
||||||
SheetTrigger,
|
|
||||||
} from './sheet'
|
|
||||||
export { Skeleton } from './skeleton'
|
export { Skeleton } from './skeleton'
|
||||||
export { Slider } from './slider'
|
export { Slider } from './slider'
|
||||||
export { Switch } from './switch'
|
export { Switch } from './switch'
|
||||||
@@ -126,8 +66,6 @@ export {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from './table'
|
} from './table'
|
||||||
export { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs'
|
|
||||||
export { TagInput } from './tag-input'
|
export { TagInput } from './tag-input'
|
||||||
export { Textarea } from './textarea'
|
export { Textarea } from './textarea'
|
||||||
export { Toggle, toggleVariants } from './toggle'
|
|
||||||
export { ToolCallCompletion, ToolCallExecution } from './tool-call'
|
export { ToolCallCompletion, ToolCallExecution } from './tool-call'
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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 }
|
|
||||||
@@ -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 }
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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 }
|
|
||||||
@@ -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 }
|
|
||||||
Reference in New Issue
Block a user