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