Skip to content

Commit 9919386

Browse files
update editor bar component (#1913)
* update editor bar component * refactor mesurement * refactor font family * use display values for width and height
1 parent 791633b commit 9919386

26 files changed

+461
-625
lines changed

apps/web/client/src/app/project/[id]/_components/editor-bar/div-selected.tsx

Lines changed: 54 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { Button } from '@onlook/ui/button';
44
import { Icons } from '@onlook/ui/icons';
55
import { Popover, PopoverContent, PopoverTrigger } from '@onlook/ui/popover';
6-
import React, { useCallback, useEffect, useRef, useState } from 'react';
6+
import React, { memo, useState } from 'react';
77
import { Border } from './dropdowns/border';
88
import { ColorBackground } from './dropdowns/color-background';
99
import { Display } from './dropdowns/display';
@@ -13,187 +13,94 @@ import { Opacity } from './dropdowns/opacity';
1313
import { Padding } from './dropdowns/padding';
1414
import { Radius } from './dropdowns/radius';
1515
import { Width } from './dropdowns/width';
16+
import { useMeasureGroup } from './hooks/use-measure-group';
1617
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';
2021

2122
// Group definitions for the div-selected toolbar
2223
export const DIV_SELECTED_GROUPS = [
2324
{
2425
key: 'dimensions',
2526
label: 'Dimensions',
26-
components: [
27-
<Width />,
28-
<Height />,
29-
],
27+
components: [<Width />, <Height />],
3028
},
3129
{
3230
key: 'base',
3331
label: 'Base',
34-
components: [
35-
<ColorBackground />,
36-
<Border />,
37-
<Radius />,
38-
]
32+
components: [<ColorBackground />, <Border />, <Radius />],
3933
},
4034
{
4135
key: 'layout',
4236
label: 'Layout',
43-
components: [
44-
<Display />,
45-
<Padding />,
46-
<Margin />,
47-
],
37+
components: [<Display />, <Padding />, <Margin />],
4838
},
4939
{
5040
key: 'typography',
5141
label: 'Typography',
5242
components: [
53-
<FontFamilySelector fontFamily="Arial" />,
54-
<FontWeightSelector fontWeight="normal" handleFontWeightChange={() => { }} />,
55-
<FontSizeSelector fontSize={16} handleFontSizeChange={() => { }} />,
43+
<FontFamilySelector />,
44+
<FontWeightSelector />,
45+
<FontSizeSelector />,
5646
],
5747
},
5848
{
5949
key: 'opacity',
6050
label: 'Opacity',
61-
components: [
62-
<Opacity />,
63-
]
51+
components: [<Opacity />],
6452
},
6553
];
6654

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 }) => {
7056
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 });
12858

12959
const visibleGroups = DIV_SELECTED_GROUPS.slice(0, visibleCount);
13060
const overflowGroups = DIV_SELECTED_GROUPS.slice(visibleCount);
13161

13262
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">
14268
{group.components.map((comp, idx) => (
143-
<React.Fragment key={idx}>
144-
{comp}
145-
</React.Fragment>
69+
<React.Fragment key={idx}>{comp}</React.Fragment>
14670
))}
14771
</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>
198105
);
199-
};
106+
});

apps/web/client/src/app/project/[id]/_components/editor-bar/dropdowns/border.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import { HoverOnlyTooltip } from "../hover-tooltip";
1515
import { InputColor } from "../inputs/input-color";
1616
import { InputRange } from "../inputs/input-range";
1717
import { SpacingInputs } from "../inputs/spacing-inputs";
18+
import { observer } from "mobx-react-lite";
1819

19-
export const Border = () => {
20+
export const Border = observer(() => {
2021
const editorEngine = useEditorEngine();
2122
const initialColor = editorEngine.style.selectedStyle?.styles.computed.borderColor;
2223
const [activeTab, setActiveTab] = useState('all');
@@ -133,4 +134,4 @@ export const Border = () => {
133134
</DropdownMenuContent>
134135
</DropdownMenu>
135136
);
136-
};
137+
});

apps/web/client/src/app/project/[id]/_components/editor-bar/dropdowns/color-background.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import { Popover, PopoverContent, PopoverTrigger } from '@onlook/ui/popover';
66
import { useMemo } from 'react';
77
import { ColorPickerContent } from '../inputs/color-picker';
88
import { useColorUpdate } from '../hooks/use-color-update';
9+
import { observer } from 'mobx-react-lite';
910

1011
interface ColorBackgroundProps {
1112
className?: string;
1213
}
1314

14-
export const ColorBackground = ({ className }: ColorBackgroundProps) => {
15+
export const ColorBackground = observer(({ className }: ColorBackgroundProps) => {
1516
const editorEngine = useEditorEngine();
1617
const initialColor = editorEngine.style.selectedStyle?.styles.computed.backgroundColor;
1718

@@ -64,4 +65,4 @@ export const ColorBackground = ({ className }: ColorBackgroundProps) => {
6465
</Popover>
6566
</div>
6667
);
67-
};
68+
});

apps/web/client/src/app/project/[id]/_components/editor-bar/dropdowns/display/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { HorizontalAlignInput, VerticalAlignInput } from './align';
1010
import { DirectionInput } from './direction';
1111
import { GapInput } from './gap';
1212
import { TypeInput } from './type';
13+
import { observer } from 'mobx-react-lite';
1314

1415
export interface CssValue {
1516
value: string;
@@ -23,7 +24,7 @@ export const layoutTypeOptions: Record<string, CssValue> = {
2324
grid: { value: "grid", label: "Grid" },
2425
};
2526

26-
export const Display = () => {
27+
export const Display = observer(() => {
2728
const editorEngine = useEditorEngine();
2829
const [layoutType, setLayoutType] = useState(
2930
editorEngine.style.selectedStyle?.styles.computed.display ?? 'block',
@@ -60,4 +61,4 @@ export const Display = () => {
6061
</DropdownMenuContent>
6162
</DropdownMenu>
6263
);
63-
};
64+
});

apps/web/client/src/app/project/[id]/_components/editor-bar/dropdowns/height.tsx

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { Button } from '@onlook/ui/button';
44
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@onlook/ui/dropdown-menu';
55
import { Icons } from '@onlook/ui/icons';
66
import { LayoutMode } from '@onlook/utility';
7+
import { observer } from 'mobx-react-lite';
78
import { useDimensionControl } from '../hooks/use-dimension-control';
89
import { HoverOnlyTooltip } from '../hover-tooltip';
910
import { InputDropdown } from '../inputs/input-dropdown';
1011

11-
export const Height = () => {
12+
export const Height = observer(() => {
1213
const { dimensionState, handleDimensionChange, handleUnitChange, handleLayoutChange } =
1314
useDimensionControl('height');
1415

@@ -22,16 +23,9 @@ export const Height = () => {
2223
className="text-muted-foreground border-border/0 hover:bg-background-tertiary/20 hover:border-border data-[state=open]:bg-background-tertiary/20 data-[state=open]:border-border flex cursor-pointer items-center gap-1 border hover:border hover:text-white focus:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none active:border-0 data-[state=open]:border data-[state=open]:text-white"
2324
>
2425
<Icons.Height className="h-4 min-h-4 w-4 min-w-4" />
25-
{(dimensionState.height.unit === 'px'
26-
? dimensionState.height.num !== undefined
27-
: (dimensionState.height.value && dimensionState.height.value !== "auto")
28-
) && (
29-
<span className="text-small">
30-
{dimensionState.height.unit === 'px'
31-
? Math.round(dimensionState.height.num ?? 0)
32-
: dimensionState.height.value}
33-
</span>
34-
)}
26+
<span className="text-small">
27+
{dimensionState.height.value}
28+
</span>
3529
</Button>
3630
</DropdownMenuTrigger>
3731
</HoverOnlyTooltip>
@@ -43,7 +37,7 @@ export const Height = () => {
4337
<div className="flex items-center justify-between">
4438
<span className="text-muted-white text-sm">Height</span>
4539
<InputDropdown
46-
value={dimensionState.height.num?.toString() ?? '--'}
40+
value={dimensionState.height.num ?? 0}
4741
unit={dimensionState.height.unit}
4842
dropdownValue={dimensionState.height.dropdownValue}
4943
dropdownOptions={Object.values(LayoutMode)}
@@ -55,7 +49,7 @@ export const Height = () => {
5549
<div className="flex items-center justify-between">
5650
<span className="text-muted-foreground text-sm">Min</span>
5751
<InputDropdown
58-
value={dimensionState.minHeight.num?.toString() ?? '--'}
52+
value={dimensionState.minHeight.num ?? 0}
5953
unit={dimensionState.minHeight.unit}
6054
dropdownValue={dimensionState.minHeight.dropdownValue}
6155
dropdownOptions={Object.values(LayoutMode)}
@@ -67,7 +61,7 @@ export const Height = () => {
6761
<div className="flex items-center justify-between">
6862
<span className="text-muted-foreground text-sm">Max</span>
6963
<InputDropdown
70-
value={dimensionState.maxHeight.num?.toString() ?? '--'}
64+
value={dimensionState.maxHeight.num ?? 0}
7165
unit={dimensionState.maxHeight.unit}
7266
dropdownValue={dimensionState.maxHeight.dropdownValue}
7367
dropdownOptions={Object.values(LayoutMode)}
@@ -80,4 +74,4 @@ export const Height = () => {
8074
</DropdownMenuContent>
8175
</DropdownMenu>
8276
);
83-
};
77+
});

apps/web/client/src/app/project/[id]/_components/editor-bar/dropdowns/margin.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import { useBoxControl } from "../hooks/use-box-control";
1212
import { HoverOnlyTooltip } from "../hover-tooltip";
1313
import { InputRange } from "../inputs/input-range";
1414
import { SpacingInputs } from "../inputs/spacing-inputs";
15+
import { observer } from "mobx-react-lite";
1516

16-
export const Margin = () => {
17+
export const Margin = observer(() => {
1718
const [activeTab, setActiveTab] = useState("all");
18-
const [hovered, setHovered] = useState(false);
1919
const { boxState, handleBoxChange, handleUnitChange, handleIndividualChange } = useBoxControl('margin');
2020

2121
return (
@@ -83,4 +83,4 @@ export const Margin = () => {
8383
</DropdownMenuContent>
8484
</DropdownMenu>
8585
);
86-
};
86+
});

0 commit comments

Comments
 (0)