@@ -32,6 +32,7 @@ import {
3232} from 'lucide-react' ;
3333import { cn } from '@object-ui/components' ;
3434import { ScrollArea } from '@object-ui/components' ;
35+ import { enableTouchDrag , isTouchDevice } from '../utils/touchDragPolyfill' ;
3536
3637interface ComponentPaletteProps {
3738 className ?: string ;
@@ -98,6 +99,77 @@ const CATEGORIES = {
9899 'Navigation' : [ 'tabs' , 'breadcrumb' , 'pagination' , 'menubar' ]
99100} ;
100101
102+ // Component item with touch support
103+ interface ComponentItemProps {
104+ type : string ;
105+ config : any ;
106+ Icon : any ;
107+ isResizable : boolean ;
108+ onDragStart : ( e : React . DragEvent , type : string ) => void ;
109+ onDragEnd : ( ) => void ;
110+ }
111+
112+ const ComponentItem : React . FC < ComponentItemProps > = React . memo ( ( {
113+ type,
114+ config,
115+ Icon,
116+ isResizable,
117+ onDragStart,
118+ onDragEnd
119+ } ) => {
120+ const itemRef = React . useRef < HTMLDivElement > ( null ) ;
121+ const { setDraggingType } = useDesigner ( ) ;
122+
123+ // Setup touch drag support
124+ React . useEffect ( ( ) => {
125+ if ( ! itemRef . current || ! isTouchDevice ( ) ) return ;
126+
127+ const cleanup = enableTouchDrag ( itemRef . current , {
128+ dragData : { componentType : type } ,
129+ onDragStart : ( ) => {
130+ setDraggingType ( type ) ;
131+ } ,
132+ onDragEnd : ( ) => {
133+ setDraggingType ( null ) ;
134+ }
135+ } ) ;
136+
137+ return cleanup ;
138+ } , [ type , setDraggingType ] ) ;
139+
140+ return (
141+ < div
142+ ref = { itemRef }
143+ draggable
144+ onDragStart = { ( e ) => onDragStart ( e , type ) }
145+ onDragEnd = { onDragEnd }
146+ className = { cn (
147+ "group flex flex-col items-center justify-center gap-1.5 md:gap-2 p-2.5 md:p-3 rounded-xl border-2 border-transparent hover:border-indigo-200 hover:bg-gradient-to-br hover:from-indigo-50 hover:to-purple-50 hover:shadow-lg cursor-grab active:cursor-grabbing transition-all duration-200 bg-white relative overflow-hidden" ,
148+ "h-20 md:h-24 hover:scale-105 active:scale-95 touch-none"
149+ ) }
150+ aria-label = { `${ config . label || type } ${ isResizable ? ' (resizable)' : '' } ` }
151+ >
152+ { /* Resizable badge indicator */ }
153+ { isResizable && (
154+ < div
155+ className = "absolute top-1 right-1 w-1.5 h-1.5 md:w-2 md:h-2 bg-gradient-to-br from-emerald-400 to-green-500 rounded-full opacity-0 group-hover:opacity-100 transition-opacity shadow-sm ring-2 ring-white"
156+ title = "Resizable"
157+ aria-hidden = "true"
158+ > </ div >
159+ ) }
160+
161+ < div className = "w-8 h-8 md:w-9 md:h-9 rounded-lg bg-gradient-to-br from-gray-50 to-gray-100 group-hover:from-indigo-100 group-hover:to-purple-100 flex items-center justify-center text-gray-600 group-hover:text-indigo-700 transition-all border border-gray-200 group-hover:border-indigo-300 group-hover:shadow-md" >
162+ < Icon size = { 16 } strokeWidth = { 2 } className = "md:w-[18px] md:h-[18px]" />
163+ </ div >
164+ < span className = "text-[10px] md:text-xs font-semibold text-gray-700 group-hover:text-indigo-800 text-center w-full truncate px-1 transition-colors leading-tight" >
165+ { config . label || type }
166+ </ span >
167+ </ div >
168+ ) ;
169+ } ) ;
170+
171+ ComponentItem . displayName = 'ComponentItem' ;
172+
101173export const ComponentPalette : React . FC < ComponentPaletteProps > = React . memo ( ( { className } ) => {
102174 const { setDraggingType } = useDesigner ( ) ;
103175 const allConfigs = ComponentRegistry . getAllConfigs ( ) ;
@@ -129,33 +201,15 @@ export const ComponentPalette: React.FC<ComponentPaletteProps> = React.memo(({ c
129201 const isResizable = config . resizable || false ;
130202
131203 return (
132- < div
204+ < ComponentItem
133205 key = { type }
134- draggable
135- onDragStart = { ( e ) => handleDragStart ( e , type ) }
206+ type = { type }
207+ config = { config }
208+ Icon = { Icon }
209+ isResizable = { isResizable }
210+ onDragStart = { handleDragStart }
136211 onDragEnd = { handleDragEnd }
137- className = { cn (
138- "group flex flex-col items-center justify-center gap-2 p-3 rounded-xl border-2 border-transparent hover:border-indigo-200 hover:bg-gradient-to-br hover:from-indigo-50 hover:to-purple-50 hover:shadow-lg cursor-grab active:cursor-grabbing transition-all duration-200 bg-white relative overflow-hidden" ,
139- "h-24 hover:scale-105 active:scale-95"
140- ) }
141- aria-label = { `${ config . label || type } ${ isResizable ? ' (resizable)' : '' } ` }
142- >
143- { /* Resizable badge indicator */ }
144- { isResizable && (
145- < div
146- className = "absolute top-1 right-1 w-2 h-2 bg-gradient-to-br from-emerald-400 to-green-500 rounded-full opacity-0 group-hover:opacity-100 transition-opacity shadow-sm ring-2 ring-white"
147- title = "Resizable"
148- aria-hidden = "true"
149- > </ div >
150- ) }
151-
152- < div className = "w-9 h-9 rounded-lg bg-gradient-to-br from-gray-50 to-gray-100 group-hover:from-indigo-100 group-hover:to-purple-100 flex items-center justify-center text-gray-600 group-hover:text-indigo-700 transition-all border border-gray-200 group-hover:border-indigo-300 group-hover:shadow-md" >
153- < Icon size = { 18 } strokeWidth = { 2 } />
154- </ div >
155- < span className = "text-xs font-semibold text-gray-700 group-hover:text-indigo-800 text-center w-full truncate px-1 transition-colors" >
156- { config . label || type }
157- </ span >
158- </ div >
212+ />
159213 ) ;
160214 } , [ handleDragStart , handleDragEnd ] ) ;
161215
@@ -177,15 +231,15 @@ export const ComponentPalette: React.FC<ComponentPaletteProps> = React.memo(({ c
177231 } , [ ] ) ;
178232
179233 return (
180- < div className = { cn ( "flex flex-col h-full bg-gradient-to-b from-gray-50 to-white w-72 overflow-hidden" , className ) } >
181- < div className = "px-4 py-4 border-b border-gray-200/80 bg-white/80 backdrop-blur-sm shrink-0 space-y-3" >
234+ < div className = { cn ( "flex flex-col h-full bg-gradient-to-b from-gray-50 to-white w-full overflow-hidden" , className ) } >
235+ < div className = "px-3 md:px-4 py-3 md: py-4 border-b border-gray-200/80 bg-white/80 backdrop-blur-sm shrink-0 space-y-3" >
182236 < div className = "relative" >
183237 < input
184238 type = "text"
185- placeholder = "Search components ..."
239+ placeholder = "Search..."
186240 value = { searchQuery }
187241 onChange = { ( e ) => setSearchQuery ( e . target . value ) }
188- className = "w-full h-10 px-4 text-sm border-2 border-gray-200 rounded-xl bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-400 transition-all placeholder:text-gray-400 font-medium shadow-sm hover:shadow-md"
242+ className = "w-full h-9 md:h- 10 px-3 md: px-4 text-sm border-2 border-gray-200 rounded-xl bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-400 transition-all placeholder:text-gray-400 font-medium shadow-sm hover:shadow-md"
189243 />
190244 { searchQuery && (
191245 < button
@@ -200,18 +254,18 @@ export const ComponentPalette: React.FC<ComponentPaletteProps> = React.memo(({ c
200254 </ div >
201255
202256 < ScrollArea className = "flex-1 w-full" >
203- < div className = "p-4 space-y-6 pb-20" >
257+ < div className = "p-3 md:p-4 space-y-4 md: space-y-6 pb-20" >
204258 { Object . entries ( CATEGORIES ) . map ( ( [ category , types ] ) => {
205259 const availableTypes = filterBySearch ( getComponentscategory ( types ) ) ;
206260 if ( availableTypes . length === 0 ) return null ;
207261
208262 return (
209263 < div key = { category } >
210- < h3 className = "text-xs font-bold text-transparent bg-clip-text bg-gradient-to-r from-indigo-600 to-purple-600 uppercase tracking-widest mb-3 px-1 flex items-center gap-2" >
211- < span className = "w-1 h-4 bg-gradient-to-b from-indigo-500 to-purple-500 rounded-full" > </ span >
212- { category }
264+ < h3 className = "text-xs font-bold text-transparent bg-clip-text bg-gradient-to-r from-indigo-600 to-purple-600 uppercase tracking-widest mb-2 md:mb- 3 px-1 flex items-center gap-2" >
265+ < span className = "w-1 h-3 md:h- 4 bg-gradient-to-b from-indigo-500 to-purple-500 rounded-full" > </ span >
266+ < span className = "truncate" > { category } </ span >
213267 </ h3 >
214- < div className = "grid grid-cols-2 gap-2.5" >
268+ < div className = "grid grid-cols-2 gap-2 md:gap-2 .5" >
215269 { availableTypes . map ( renderComponentItem ) }
216270 </ div >
217271 </ div >
0 commit comments