3
3
import { Button } from '@onlook/ui/button' ;
4
4
import { Icons } from '@onlook/ui/icons' ;
5
5
import { Popover , PopoverContent , PopoverTrigger } from '@onlook/ui/popover' ;
6
- import React , { useCallback , useEffect , useRef , useState } from 'react' ;
6
+ import React , { memo , useState } from 'react' ;
7
7
import { Border } from './dropdowns/border' ;
8
8
import { ColorBackground } from './dropdowns/color-background' ;
9
9
import { Display } from './dropdowns/display' ;
@@ -13,187 +13,94 @@ import { Opacity } from './dropdowns/opacity';
13
13
import { Padding } from './dropdowns/padding' ;
14
14
import { Radius } from './dropdowns/radius' ;
15
15
import { Width } from './dropdowns/width' ;
16
+ import { useMeasureGroup } from './hooks/use-measure-group' ;
16
17
import { InputSeparator } from './separator' ;
17
- import { FontFamilySelector } from './text-inputs/font-family' ;
18
- import { FontSizeSelector } from './text-inputs/font-size' ;
19
- import { FontWeightSelector } from './text-inputs/font-weight' ;
18
+ import { FontFamilySelector } from './text-inputs/font/font -family-selector ' ;
19
+ import { FontSizeSelector } from './text-inputs/font/font -size' ;
20
+ import { FontWeightSelector } from './text-inputs/font/font -weight' ;
20
21
21
22
// Group definitions for the div-selected toolbar
22
23
export const DIV_SELECTED_GROUPS = [
23
24
{
24
25
key : 'dimensions' ,
25
26
label : 'Dimensions' ,
26
- components : [
27
- < Width /> ,
28
- < Height /> ,
29
- ] ,
27
+ components : [ < Width /> , < Height /> ] ,
30
28
} ,
31
29
{
32
30
key : 'base' ,
33
31
label : 'Base' ,
34
- components : [
35
- < ColorBackground /> ,
36
- < Border /> ,
37
- < Radius /> ,
38
- ]
32
+ components : [ < ColorBackground /> , < Border /> , < Radius /> ] ,
39
33
} ,
40
34
{
41
35
key : 'layout' ,
42
36
label : 'Layout' ,
43
- components : [
44
- < Display /> ,
45
- < Padding /> ,
46
- < Margin /> ,
47
- ] ,
37
+ components : [ < Display /> , < Padding /> , < Margin /> ] ,
48
38
} ,
49
39
{
50
40
key : 'typography' ,
51
41
label : 'Typography' ,
52
42
components : [
53
- < FontFamilySelector fontFamily = "Arial" /> ,
54
- < FontWeightSelector fontWeight = "normal" handleFontWeightChange = { ( ) => { } } /> ,
55
- < FontSizeSelector fontSize = { 16 } handleFontSizeChange = { ( ) => { } } /> ,
43
+ < FontFamilySelector /> ,
44
+ < FontWeightSelector /> ,
45
+ < FontSizeSelector /> ,
56
46
] ,
57
47
} ,
58
48
{
59
49
key : 'opacity' ,
60
50
label : 'Opacity' ,
61
- components : [
62
- < Opacity /> ,
63
- ]
51
+ components : [ < Opacity /> ] ,
64
52
} ,
65
53
] ;
66
54
67
- export const DivSelected = ( { availableWidth = 0 } : { availableWidth ?: number } ) => {
68
- const groupRefs = useRef < ( HTMLDivElement | null ) [ ] > ( [ ] ) ;
69
- const [ groupWidths , setGroupWidths ] = useState < number [ ] > ( [ ] ) ;
55
+ export const DivSelected = memo ( ( { availableWidth = 0 } : { availableWidth ?: number } ) => {
70
56
const [ overflowOpen , setOverflowOpen ] = useState ( false ) ;
71
- const [ visibleCount , setVisibleCount ] = useState ( DIV_SELECTED_GROUPS . length ) ;
72
-
73
- // Calculate total width of a group including margins and padding
74
- const calculateGroupWidth = useCallback ( ( element : HTMLElement | null ) : number => {
75
- if ( ! element ) return 0 ;
76
- const style = window . getComputedStyle ( element ) ;
77
- const width = element . offsetWidth ;
78
- const marginLeft = parseFloat ( style . marginLeft ) ;
79
- const marginRight = parseFloat ( style . marginRight ) ;
80
- const paddingLeft = parseFloat ( style . paddingLeft ) ;
81
- const paddingRight = parseFloat ( style . paddingRight ) ;
82
- return width + marginLeft + marginRight + paddingLeft + paddingRight ;
83
- } , [ ] ) ;
84
-
85
- // Measure all group widths
86
- const measureGroups = useCallback ( ( ) => {
87
- const widths = groupRefs . current . map ( ref => calculateGroupWidth ( ref ) ) ;
88
- setGroupWidths ( widths ) ;
89
- } , [ calculateGroupWidth ] ) ;
90
-
91
- // Update visible count based on available width
92
- const updateVisibleCount = useCallback ( ( ) => {
93
- if ( ! groupWidths . length || ! availableWidth ) return ;
94
-
95
- const OVERFLOW_BUTTON_WIDTH = 32 ; // Reduced from 48px
96
- const MIN_GROUP_WIDTH = 80 ; // Reduced from 100px
97
- const SEPARATOR_WIDTH = 8 ; // Width of the InputSeparator
98
- let used = 0 ;
99
- let count = 0 ;
100
-
101
- for ( let i = 0 ; i < groupWidths . length ; i ++ ) {
102
- const width = groupWidths [ i ] ?? 0 ;
103
- if ( width < MIN_GROUP_WIDTH ) continue ;
104
-
105
- // Add separator width if this isn't the first group
106
- const totalWidth = width + ( count > 0 ? SEPARATOR_WIDTH : 0 ) ;
107
-
108
- if ( used + totalWidth <= availableWidth - OVERFLOW_BUTTON_WIDTH ) {
109
- used += totalWidth ;
110
- count ++ ;
111
- } else {
112
- break ;
113
- }
114
- }
115
-
116
- setVisibleCount ( count ) ;
117
- } , [ groupWidths , availableWidth ] ) ;
118
-
119
- // Measure group widths after mount and when groupRefs change
120
- useEffect ( ( ) => {
121
- measureGroups ( ) ;
122
- } , [ measureGroups , availableWidth ] ) ;
123
-
124
- // Update visible count when measurements change
125
- useEffect ( ( ) => {
126
- updateVisibleCount ( ) ;
127
- } , [ updateVisibleCount ] ) ;
57
+ const { visibleCount } = useMeasureGroup ( { availableWidth, count : DIV_SELECTED_GROUPS . length } ) ;
128
58
129
59
const visibleGroups = DIV_SELECTED_GROUPS . slice ( 0 , visibleCount ) ;
130
60
const overflowGroups = DIV_SELECTED_GROUPS . slice ( visibleCount ) ;
131
61
132
62
return (
133
- < >
134
- { /* Hidden measurement container */ }
135
- < div style = { { position : 'absolute' , visibility : 'hidden' , height : 0 , overflow : 'hidden' , pointerEvents : 'none' } } >
136
- { DIV_SELECTED_GROUPS . map ( ( group , groupIdx ) => (
137
- < div
138
- key = { group . key }
139
- className = "flex items-center justify-center gap-0.5"
140
- ref = { el => { groupRefs . current [ groupIdx ] = el ; } }
141
- >
63
+ < div className = "flex items-center justify-center gap-0.5 w-full overflow-hidden" >
64
+ { visibleGroups . map ( ( group , groupIdx ) => (
65
+ < React . Fragment key = { group . key } >
66
+ { groupIdx > 0 && < InputSeparator /> }
67
+ < div className = "flex items-center justify-center gap-0.5" >
142
68
{ group . components . map ( ( comp , idx ) => (
143
- < React . Fragment key = { idx } >
144
- { comp }
145
- </ React . Fragment >
69
+ < React . Fragment key = { idx } > { comp } </ React . Fragment >
146
70
) ) }
147
71
</ div >
148
- ) ) }
149
- </ div >
150
- < div
151
- className = "flex items-center justify-center gap-0.5 w-full overflow-hidden"
152
- >
153
- { DIV_SELECTED_GROUPS . map ( ( group , groupIdx ) => (
154
- groupIdx < visibleCount ? (
155
- < React . Fragment key = { group . key } >
156
- { groupIdx > 0 && < InputSeparator /> }
157
- < div className = "flex items-center justify-center gap-0.5" >
158
- { group . components . map ( ( comp , idx ) => (
159
- < React . Fragment key = { idx } >
160
- { comp }
161
- </ React . Fragment >
162
- ) ) }
163
- </ div >
164
- </ React . Fragment >
165
- ) : null
166
- ) ) }
167
- { overflowGroups . length > 0 && visibleCount > 0 && < InputSeparator /> }
168
- { overflowGroups . length > 0 && (
169
- < Popover open = { overflowOpen } onOpenChange = { setOverflowOpen } >
170
- < PopoverTrigger asChild >
171
- < Button
172
- variant = "ghost"
173
- size = "toolbar"
174
- className = "w-8 h-8 flex items-center justify-center"
175
- aria-label = "Show more toolbar controls"
176
- >
177
- < Icons . DotsHorizontal className = "w-5 h-5" />
178
- </ Button >
179
- </ PopoverTrigger >
180
- < PopoverContent align = "end" className = "flex flex-row gap-1 p-1 px-1 bg-background rounded-lg shadow-xl shadow-black/20 min-w-[fit-content] items-center w-[fit-content]" >
181
- { overflowGroups . map ( ( group , groupIdx ) => (
182
- < React . Fragment key = { group . key } >
183
- { groupIdx > 0 && < InputSeparator /> }
184
- < div className = "flex items-center gap-0.5" >
185
- { group . components . map ( ( comp , idx ) => (
186
- < React . Fragment key = { idx } >
187
- { comp }
188
- </ React . Fragment >
189
- ) ) }
190
- </ div >
191
- </ React . Fragment >
192
- ) ) }
193
- </ PopoverContent >
194
- </ Popover >
195
- ) }
196
- </ div >
197
- </ >
72
+ </ React . Fragment >
73
+ ) ) }
74
+ { overflowGroups . length > 0 && visibleCount > 0 && < InputSeparator /> }
75
+ { overflowGroups . length > 0 && (
76
+ < Popover open = { overflowOpen } onOpenChange = { setOverflowOpen } >
77
+ < PopoverTrigger asChild >
78
+ < Button
79
+ variant = "ghost"
80
+ size = "toolbar"
81
+ className = "w-8 h-8 flex items-center justify-center"
82
+ aria-label = "Show more toolbar controls"
83
+ >
84
+ < Icons . DotsHorizontal className = "w-5 h-5" />
85
+ </ Button >
86
+ </ PopoverTrigger >
87
+ < PopoverContent
88
+ align = "end"
89
+ className = "flex flex-row gap-1 p-1 px-1 bg-background rounded-lg shadow-xl shadow-black/20 min-w-[fit-content] items-center w-[fit-content]"
90
+ >
91
+ { overflowGroups . map ( ( group , groupIdx ) => (
92
+ < React . Fragment key = { group . key } >
93
+ { groupIdx > 0 && < InputSeparator /> }
94
+ < div className = "flex items-center gap-0.5" >
95
+ { group . components . map ( ( comp , idx ) => (
96
+ < React . Fragment key = { idx } > { comp } </ React . Fragment >
97
+ ) ) }
98
+ </ div >
99
+ </ React . Fragment >
100
+ ) ) }
101
+ </ PopoverContent >
102
+ </ Popover >
103
+ ) }
104
+ </ div >
198
105
) ;
199
- } ;
106
+ } ) ;
0 commit comments