@dilipod/ui 0.4.24 → 0.4.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dilipod/ui",
3
- "version": "0.4.24",
3
+ "version": "0.4.26",
4
4
  "description": "Dilipod Design System - Shared UI components and styles",
5
5
  "author": "Dilipod <hello@dilipod.com>",
6
6
  "license": "MIT",
@@ -7,7 +7,7 @@ import { cva, type VariantProps } from 'class-variance-authority'
7
7
  import { cn } from '../lib/utils'
8
8
 
9
9
  const buttonVariants = cva(
10
- 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-semibold ring-offset-background transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer',
10
+ 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-semibold transition-all duration-200 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer',
11
11
  {
12
12
  variants: {
13
13
  variant: {
@@ -124,7 +124,7 @@ const DateRangeSelect = React.forwardRef<HTMLSelectElement, DateRangeSelectProps
124
124
  value={value}
125
125
  onChange={(e) => onChange(e.target.value as DateRangePreset)}
126
126
  className={cn(
127
- 'h-9 rounded-sm border border-input bg-background px-3 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
127
+ 'h-9 rounded-sm border border-input bg-background px-3 text-sm focus:outline-none disabled:cursor-not-allowed disabled:opacity-50',
128
128
  className
129
129
  )}
130
130
  >
@@ -44,7 +44,7 @@ const DialogContent = React.forwardRef<
44
44
  {...props}
45
45
  >
46
46
  {children}
47
- <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-[var(--cyan)] focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-gray-100">
47
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-gray-100">
48
48
  <X className="h-4 w-4" />
49
49
  <span className="sr-only">Close</span>
50
50
  </DialogPrimitive.Close>
@@ -39,7 +39,7 @@ const FormField = React.forwardRef<HTMLDivElement, FormFieldProps>(
39
39
  'aria-describedby': error ? errorId : helperText ? helperId : undefined,
40
40
  className: cn(
41
41
  childProps.className,
42
- error && 'border-red-500 focus-visible:ring-red-500'
42
+ error && 'border-red-500'
43
43
  ),
44
44
  })
45
45
  }
@@ -205,7 +205,7 @@ export function ImpactMetricsForm({
205
205
  ...prev,
206
206
  time_saved_minutes_per_run: parseInt(e.target.value) || 0
207
207
  }))}
208
- className="w-16 px-2 py-1 text-2xl font-bold border border-border rounded-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan)] bg-background"
208
+ className="w-16 px-2 py-1 text-2xl font-bold border border-border rounded-sm focus:outline-none bg-background"
209
209
  min="0"
210
210
  />
211
211
  <span className="text-sm text-muted-foreground">min</span>
@@ -230,7 +230,7 @@ export function ImpactMetricsForm({
230
230
  ...prev,
231
231
  hourly_rate_euros: parseFloat(e.target.value) || 0
232
232
  }))}
233
- className="w-16 px-2 py-1 text-2xl font-bold border border-border rounded-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan)] bg-background"
233
+ className="w-16 px-2 py-1 text-2xl font-bold border border-border rounded-sm focus:outline-none bg-background"
234
234
  min="0"
235
235
  step="0.5"
236
236
  />
@@ -255,7 +255,7 @@ export function ImpactMetricsForm({
255
255
  ...prev,
256
256
  fte_equivalent: (parseFloat(e.target.value) || 0) / 100
257
257
  }))}
258
- className="w-16 px-2 py-1 text-2xl font-bold border border-border rounded-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan)] bg-background"
258
+ className="w-16 px-2 py-1 text-2xl font-bold border border-border rounded-sm focus:outline-none bg-background"
259
259
  min="0"
260
260
  max="1000"
261
261
  step="5"
@@ -15,10 +15,10 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
15
15
  <input
16
16
  type={type}
17
17
  className={cn(
18
- 'flex h-10 w-full rounded-sm border bg-white px-3 py-2 text-base text-[var(--black)] ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-[var(--black)] placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm transition-colors',
18
+ 'flex h-10 w-full rounded-sm border bg-white px-3 py-2 text-base text-[var(--black)] file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-[var(--black)] placeholder:text-gray-500 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm transition-colors',
19
19
  error
20
- ? 'border-red-500 focus-visible:ring-red-500'
21
- : 'border-gray-300 focus-visible:ring-[var(--cyan)]',
20
+ ? 'border-red-500'
21
+ : 'border-gray-300',
22
22
  className
23
23
  )}
24
24
  ref={ref}
@@ -27,7 +27,7 @@ const RadioGroupItem = React.forwardRef<
27
27
  <RadioGroupPrimitive.Item
28
28
  ref={ref}
29
29
  className={cn(
30
- 'aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
30
+ 'aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
31
31
  className
32
32
  )}
33
33
  {...props}
@@ -91,7 +91,7 @@ const RadioGroupCard = React.forwardRef<
91
91
  ref={ref}
92
92
  id={cardId}
93
93
  className={cn(
94
- 'relative flex cursor-pointer rounded-lg border bg-background p-4 transition-all hover:border-[var(--cyan)]/50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 data-[state=checked]:border-[var(--cyan)] data-[state=checked]:ring-1 data-[state=checked]:ring-[var(--cyan)] disabled:cursor-not-allowed disabled:opacity-50',
94
+ 'relative flex cursor-pointer rounded-lg border bg-background p-4 transition-all hover:border-[var(--cyan)]/50 focus:outline-none focus-visible:outline-none data-[state=checked]:border-[var(--cyan)] data-[state=checked]:ring-1 data-[state=checked]:ring-[var(--cyan)] disabled:cursor-not-allowed disabled:opacity-50',
95
95
  className
96
96
  )}
97
97
  {...props}
@@ -15,14 +15,14 @@ const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
15
15
  <select
16
16
  ref={ref}
17
17
  className={cn(
18
- 'h-10 w-full rounded-sm border bg-white px-3 py-2 text-base text-[var(--black)] ring-offset-background',
18
+ 'h-10 w-full rounded-sm border bg-white px-3 py-2 text-base text-[var(--black)]',
19
19
  'placeholder:text-gray-500',
20
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
20
+ 'focus-visible:outline-none',
21
21
  'disabled:cursor-not-allowed disabled:opacity-50 md:text-sm transition-colors',
22
22
  'appearance-none pr-10',
23
23
  error
24
- ? 'border-red-500 focus-visible:ring-red-500'
25
- : 'border-gray-300 focus-visible:ring-[var(--cyan)]',
24
+ ? 'border-red-500'
25
+ : 'border-gray-300',
26
26
  className
27
27
  )}
28
28
  aria-invalid={error ? 'true' : undefined}
@@ -68,7 +68,7 @@ const SheetContent = React.forwardRef<
68
68
  >
69
69
  {children}
70
70
  {/* @ts-expect-error - Radix Dialog Close accepts className and children at runtime */}
71
- <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-[var(--cyan)] focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-gray-100">
71
+ <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-gray-100">
72
72
  <X className="h-4 w-4" />
73
73
  <span className="sr-only">Close</span>
74
74
  </SheetPrimitive.Close>
@@ -56,8 +56,8 @@ const Slider = React.forwardRef<
56
56
  </SliderPrimitive.Track>
57
57
  <SliderPrimitive.Thumb
58
58
  className={cn(
59
- 'block rounded-full border-2 border-[var(--cyan)] bg-white shadow-md ring-offset-background transition-colors',
60
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--cyan)] focus-visible:ring-offset-2',
59
+ 'block rounded-full border-2 border-[var(--cyan)] bg-white shadow-md transition-colors',
60
+ 'focus-visible:outline-none',
61
61
  'disabled:pointer-events-none disabled:opacity-50',
62
62
  'hover:border-[var(--cyan-dark,#00bfaa)] cursor-pointer',
63
63
  sizeStyles[size].thumb
@@ -0,0 +1,236 @@
1
+ 'use client'
2
+
3
+ import { useState, useEffect, useRef, useCallback, ReactNode } from 'react'
4
+ import { Button } from './button'
5
+ import { Textarea } from './textarea'
6
+ import {
7
+ CircleNotch,
8
+ PaperPlaneTilt,
9
+ } from '@phosphor-icons/react'
10
+
11
+ // ============================================
12
+ // Types
13
+ // ============================================
14
+
15
+ export interface SupportChatMessage {
16
+ id: string
17
+ role: 'customer' | 'admin' | 'system'
18
+ content: string
19
+ type: 'message' | 'event' | 'suggestion'
20
+ created_at: string
21
+ metadata?: Record<string, unknown>
22
+ }
23
+
24
+ export interface SupportChatProps {
25
+ /** Fetch messages from this endpoint */
26
+ messagesEndpoint: string
27
+ /** POST new messages to this endpoint */
28
+ sendEndpoint: string
29
+ /** Role of the current user */
30
+ currentRole: 'customer' | 'admin'
31
+ /** Poll interval in ms (0 to disable). Default: 5000 */
32
+ pollInterval?: number
33
+ /** Placeholder for the input */
34
+ placeholder?: string
35
+ /** Custom empty state */
36
+ emptyMessage?: string | ReactNode
37
+ /** Additional CSS class */
38
+ className?: string
39
+ }
40
+
41
+ // ============================================
42
+ // Helpers
43
+ // ============================================
44
+
45
+ function formatMessageTime(dateStr: string): string {
46
+ const d = new Date(dateStr)
47
+ const now = new Date()
48
+ const diffMs = now.getTime() - d.getTime()
49
+ const diffMins = Math.floor(diffMs / 60000)
50
+ const diffHours = Math.floor(diffMs / 3600000)
51
+
52
+ if (diffMins < 1) return 'just now'
53
+ if (diffMins < 60) return `${diffMins}m ago`
54
+ if (diffHours < 24) return `${diffHours}h ago`
55
+
56
+ return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })
57
+ }
58
+
59
+ // ============================================
60
+ // Component
61
+ // ============================================
62
+
63
+ export function SupportChat({
64
+ messagesEndpoint,
65
+ sendEndpoint,
66
+ currentRole,
67
+ pollInterval = 5000,
68
+ placeholder = 'Type a message...',
69
+ emptyMessage = 'No messages yet. Start the conversation!',
70
+ className = '',
71
+ }: SupportChatProps) {
72
+ const [messages, setMessages] = useState<SupportChatMessage[]>([])
73
+ const [loading, setLoading] = useState(true)
74
+ const [sending, setSending] = useState(false)
75
+ const [inputValue, setInputValue] = useState('')
76
+ const scrollRef = useRef<HTMLDivElement>(null)
77
+ const prevMessageCountRef = useRef(0)
78
+
79
+ const fetchMessages = useCallback(async () => {
80
+ try {
81
+ const res = await fetch(messagesEndpoint)
82
+ if (!res.ok) return
83
+ const data = await res.json()
84
+ setMessages(data.messages || [])
85
+ } catch {
86
+ // Silently fail
87
+ } finally {
88
+ setLoading(false)
89
+ }
90
+ }, [messagesEndpoint])
91
+
92
+ // Initial fetch + polling
93
+ useEffect(() => {
94
+ fetchMessages()
95
+ if (pollInterval > 0) {
96
+ const interval = setInterval(fetchMessages, pollInterval)
97
+ return () => clearInterval(interval)
98
+ }
99
+ }, [fetchMessages, pollInterval])
100
+
101
+ // Auto-scroll when new messages arrive
102
+ useEffect(() => {
103
+ if (messages.length > prevMessageCountRef.current) {
104
+ scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' })
105
+ }
106
+ prevMessageCountRef.current = messages.length
107
+ }, [messages.length])
108
+
109
+ const handleSend = async () => {
110
+ const content = inputValue.trim()
111
+ if (!content || sending) return
112
+
113
+ setSending(true)
114
+ setInputValue('')
115
+
116
+ // Optimistic update
117
+ const optimisticMsg: SupportChatMessage = {
118
+ id: `temp-${Date.now()}`,
119
+ role: currentRole,
120
+ content,
121
+ type: 'message',
122
+ created_at: new Date().toISOString(),
123
+ }
124
+ setMessages((prev) => [...prev, optimisticMsg])
125
+
126
+ try {
127
+ const res = await fetch(sendEndpoint, {
128
+ method: 'POST',
129
+ headers: { 'Content-Type': 'application/json' },
130
+ body: JSON.stringify({ content }),
131
+ })
132
+
133
+ if (res.ok) {
134
+ // Replace optimistic message with real one
135
+ await fetchMessages()
136
+ } else {
137
+ // Remove optimistic message on failure
138
+ setMessages((prev) => prev.filter((m) => m.id !== optimisticMsg.id))
139
+ }
140
+ } catch {
141
+ setMessages((prev) => prev.filter((m) => m.id !== optimisticMsg.id))
142
+ } finally {
143
+ setSending(false)
144
+ }
145
+ }
146
+
147
+ const handleKeyDown = (e: React.KeyboardEvent) => {
148
+ if (e.key === 'Enter' && !e.shiftKey) {
149
+ e.preventDefault()
150
+ handleSend()
151
+ }
152
+ }
153
+
154
+ if (loading) {
155
+ return (
156
+ <div className={`flex items-center justify-center py-8 ${className}`}>
157
+ <CircleNotch className="w-5 h-5 animate-spin text-muted-foreground" />
158
+ </div>
159
+ )
160
+ }
161
+
162
+ return (
163
+ <div className={`flex flex-col ${className}`}>
164
+ {/* Messages area */}
165
+ <div ref={scrollRef} className="flex-1 overflow-y-auto space-y-3 mb-4 max-h-[400px] min-h-[200px] px-1">
166
+ {messages.length === 0 ? (
167
+ <div className="flex items-center justify-center h-full text-sm text-muted-foreground py-8">
168
+ {typeof emptyMessage === 'string' ? <p>{emptyMessage}</p> : emptyMessage}
169
+ </div>
170
+ ) : (
171
+ messages.map((msg) => {
172
+ // System events — centered, subtle
173
+ if (msg.type === 'event' || msg.role === 'system') {
174
+ return (
175
+ <div key={msg.id} className="flex justify-center">
176
+ <div className="bg-gray-50 border border-gray-100 rounded-full px-3 py-1 text-xs text-muted-foreground max-w-[90%] text-center">
177
+ {msg.content}
178
+ <span className="ml-2 opacity-60">{formatMessageTime(msg.created_at)}</span>
179
+ </div>
180
+ </div>
181
+ )
182
+ }
183
+
184
+ // Regular messages
185
+ const isCurrentUser = msg.role === currentRole
186
+ return (
187
+ <div key={msg.id} className={`flex ${isCurrentUser ? 'justify-end' : 'justify-start'}`}>
188
+ <div className={`max-w-[80%] rounded-lg px-3 py-2 ${
189
+ isCurrentUser
190
+ ? 'bg-[var(--cyan)] text-white'
191
+ : 'bg-gray-100 text-[var(--black)]'
192
+ }`}>
193
+ {!isCurrentUser && (
194
+ <p className="text-xs font-medium mb-0.5 opacity-70">
195
+ {msg.role === 'admin' ? 'Dilipod Team' : 'You'}
196
+ </p>
197
+ )}
198
+ <p className="text-sm whitespace-pre-wrap">{msg.content}</p>
199
+ <p className={`text-[10px] mt-1 ${isCurrentUser ? 'text-white/60' : 'text-muted-foreground'}`}>
200
+ {formatMessageTime(msg.created_at)}
201
+ </p>
202
+ </div>
203
+ </div>
204
+ )
205
+ })
206
+ )}
207
+ </div>
208
+
209
+ {/* Input area */}
210
+ <div className="flex gap-2 border-t border-gray-100 pt-3">
211
+ <Textarea
212
+ value={inputValue}
213
+ onChange={(e) => setInputValue(e.target.value)}
214
+ onKeyDown={handleKeyDown}
215
+ placeholder={placeholder}
216
+ rows={1}
217
+ className="resize-none min-h-[36px] py-2 flex-1"
218
+ />
219
+ <Button
220
+ onClick={handleSend}
221
+ disabled={sending || !inputValue.trim()}
222
+ size="sm"
223
+ className="flex-shrink-0 h-9"
224
+ >
225
+ {sending ? (
226
+ <CircleNotch className="w-4 h-4 animate-spin" />
227
+ ) : (
228
+ <PaperPlaneTilt className="w-4 h-4" weight="bold" />
229
+ )}
230
+ </Button>
231
+ </div>
232
+ </div>
233
+ )
234
+ }
235
+
236
+ export default SupportChat
@@ -32,7 +32,7 @@ const Switch = React.forwardRef<
32
32
  return (
33
33
  <SwitchPrimitive.Root
34
34
  className={cn(
35
- 'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-[var(--cyan)] data-[state=unchecked]:bg-input',
35
+ 'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-[var(--cyan)] data-[state=unchecked]:bg-input',
36
36
  sizeStyles[size].root,
37
37
  className
38
38
  )}
@@ -28,7 +28,7 @@ const TabsTrigger = React.forwardRef<
28
28
  <TabsPrimitive.Trigger
29
29
  ref={ref}
30
30
  className={cn(
31
- 'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium 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',
31
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
32
32
  className
33
33
  )}
34
34
  {...props}
@@ -43,7 +43,7 @@ const TabsContent = React.forwardRef<
43
43
  <TabsPrimitive.Content
44
44
  ref={ref}
45
45
  className={cn(
46
- 'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
46
+ 'mt-2 focus-visible:outline-none',
47
47
  className
48
48
  )}
49
49
  {...props}
@@ -14,10 +14,10 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
14
14
  return (
15
15
  <textarea
16
16
  className={cn(
17
- 'flex min-h-[80px] w-full rounded-sm border bg-white px-3 py-2 text-base text-[var(--black)] ring-offset-background placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm transition-colors resize-none',
17
+ 'flex min-h-[80px] w-full rounded-sm border bg-white px-3 py-2 text-base text-[var(--black)] placeholder:text-gray-500 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm transition-colors resize-none',
18
18
  error
19
- ? 'border-red-500 focus-visible:ring-red-500'
20
- : 'border-gray-300 focus-visible:ring-[var(--cyan)]',
19
+ ? 'border-red-500'
20
+ : 'border-gray-300',
21
21
  className
22
22
  )}
23
23
  ref={ref}
@@ -63,7 +63,7 @@ const ToastAction = React.forwardRef<
63
63
  <ToastPrimitives.Action
64
64
  ref={ref}
65
65
  className={cn(
66
- 'inline-flex h-8 shrink-0 items-center justify-center rounded-sm border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
66
+ 'inline-flex h-8 shrink-0 items-center justify-center rounded-sm border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-gray-100 focus:outline-none disabled:pointer-events-none disabled:opacity-50',
67
67
  className
68
68
  )}
69
69
  {...props}
package/src/index.ts CHANGED
@@ -281,6 +281,10 @@ export type { ImpactMetrics, ImpactMetricsFormProps } from './components/impact-
281
281
  export { ActivityTimeline } from './components/activity-timeline'
282
282
  export type { Activity, ActivityTimelineProps } from './components/activity-timeline'
283
283
 
284
+ // Support Chat Components
285
+ export { SupportChat } from './components/support-chat'
286
+ export type { SupportChatMessage, SupportChatProps } from './components/support-chat'
287
+
284
288
  // Workflow Components
285
289
  export { WorkflowFlow } from './components/workflow-flow'
286
290
  export type { WorkflowFlowProps, N8nWorkflow as WorkflowFlowN8nWorkflow, N8nNode } from './components/workflow-flow'
@@ -241,10 +241,9 @@ a:not([disabled]), button:not([disabled]) {
241
241
  cursor: pointer;
242
242
  }
243
243
 
244
- /* Better focus styles */
244
+ /* Remove default focus outline */
245
245
  *:focus-visible {
246
- outline: 2px solid hsl(var(--ring));
247
- outline-offset: 2px;
246
+ outline: none;
248
247
  }
249
248
 
250
249
  /* Smooth scroll */