Skip to content

Commit 27c7ea0

Browse files
Add advanced font styling (#1893)
* refactor font select * change position * advance type * handle NaN case
1 parent 57a675a commit 27c7ea0

File tree

8 files changed

+387
-154
lines changed

8 files changed

+387
-154
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { ImageBackground } from './dropdowns/img-background';
88
import { Margin } from './dropdowns/margin';
99
import { Padding } from './dropdowns/padding';
1010
import { Radius } from './dropdowns/radius';
11-
import { StateDropdown } from './dropdowns/state-dropdown';
1211
import { Width } from './dropdowns/width';
1312
import { ViewButtons } from './panels/panel-bar/bar';
1413
import { InputSeparator } from './separator';

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

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,24 @@ import { HoverOnlyTooltip } from "../HoverOnlyTooltip";
1818

1919
export const Border = () => {
2020
const editorEngine = useEditorEngine();
21+
const initialColor = editorEngine.style.selectedStyle?.styles.computed.borderColor;
2122
const [activeTab, setActiveTab] = useState('all');
2223
const { boxState, handleBoxChange, handleUnitChange, handleIndividualChange } =
2324
useBoxControl('border');
24-
const [borderColor, setBorderColor] = useState<string>('#080808');
25-
26-
useEffect(() => {
27-
const color = editorEngine.style.selectedStyle?.styles.computed.borderColor;
28-
if (color) {
29-
setBorderColor(
30-
Color.from(
31-
color ?? '#080808',
32-
).toHex(),
33-
);
34-
}
35-
}, [editorEngine.style.selectedStyle?.styles.computed.borderColor]);
25+
const [borderColor, setBorderColor] = useState<string>(Color.from(initialColor ?? '#080808').toHex());
3626

3727
const handleColorChange = (color: string) => {
3828
setBorderColor(color);
3929
};
4030

31+
useEffect(() => {
32+
setBorderColor(Color.from(initialColor ?? '#080808').toHex());
33+
}, [initialColor]);
34+
4135
const borderStyle = {
4236
borderWidth: boxState.borderWidth.num ? `1px` : '0px',
4337
borderStyle: 'solid',
38+
borderColor: initialColor,
4439
};
4540

4641
return (

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

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,59 +3,52 @@
33
import { useEditorEngine } from '@/components/store/editor';
44
import { Icons } from '@onlook/ui/icons';
55
import { Popover, PopoverContent, PopoverTrigger } from '@onlook/ui/popover';
6-
import { Color } from '@onlook/utility';
7-
import { useEffect, useState } from 'react';
6+
import { useMemo } from 'react';
87
import { ColorPickerContent } from '../inputs/color-picker';
98
import { useColorUpdate } from '../hooks/use-color-update';
10-
import type { TailwindColor } from '@onlook/models';
11-
export const ColorBackground = () => {
12-
const [tempColor, setTempColor] = useState<Color>(Color.from('#000000'));
139

14-
const { handleColorUpdate } = useColorUpdate({
15-
elementStyleKey: 'backgroundColor'
16-
});
10+
interface ColorBackgroundProps {
11+
className?: string;
12+
}
1713

14+
export const ColorBackground = ({ className }: ColorBackgroundProps) => {
1815
const editorEngine = useEditorEngine();
16+
const initialColor = editorEngine.style.selectedStyle?.styles.computed.backgroundColor;
17+
1918

20-
useEffect(() => {
21-
const color = editorEngine.style.selectedStyle?.styles.computed.backgroundColor;
22-
if (color) {
23-
setTempColor(Color.from(color));
24-
}
25-
}, [editorEngine.style.selectedStyle?.styles.computed.backgroundColor]);
19+
const { handleColorUpdate, handleColorUpdateEnd, tempColor } = useColorUpdate({
20+
elementStyleKey: 'backgroundColor',
21+
initialColor: initialColor,
22+
});
2623

27-
const handleColorChange = (newColor: Color | TailwindColor) => {
28-
try {
29-
setTempColor(newColor instanceof Color ? newColor : Color.from(newColor.lightColor));
30-
} catch (error) {
31-
console.error('Error converting color:', error);
32-
}
33-
};
24+
const colorHex = useMemo(() => tempColor?.toHex(), [tempColor]);
3425

35-
const handleColorChangeEnd = (newColor: Color | TailwindColor) => {
36-
try {
37-
if (newColor instanceof Color) {
38-
setTempColor(newColor);
39-
} else {
40-
setTempColor(Color.from(newColor.lightColor));
41-
}
42-
handleColorUpdate(newColor);
43-
} catch (error) {
44-
console.error('Error converting color:', error);
45-
}
46-
};
26+
const ColorTrigger = useMemo(() => (
27+
<div
28+
className="text-muted-foreground border-border/0 hover:bg-background-tertiary/20 hover:border-border active:bg-background-tertiary/20 active:border-border flex h-9 w-9 cursor-pointer flex-col items-center justify-center rounded-md border hover:border hover:text-white focus:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none active:border active:text-white"
29+
role="button"
30+
tabIndex={0}
31+
aria-label="Change background color"
32+
onKeyDown={(e) => {
33+
if (e.key === 'Enter' || e.key === ' ') {
34+
e.preventDefault();
35+
e.currentTarget.click();
36+
}
37+
}}
38+
>
39+
<Icons.PaintBucket className="h-2 w-2" />
40+
<div
41+
className="h-[4px] w-6 rounded-full bg-current"
42+
style={{ backgroundColor: colorHex }}
43+
/>
44+
</div>
45+
), [colorHex]);
4746

4847
return (
49-
<div className="flex flex-col gap-2">
48+
<div className={`flex flex-col gap-2 ${className ?? ''}`}>
5049
<Popover>
51-
<PopoverTrigger>
52-
<div className="text-muted-foreground border-border/0 hover:bg-background-tertiary/20 hover:border-border active:bg-background-tertiary/20 active:border-border flex h-9 w-9 cursor-pointer flex-col items-center justify-center rounded-md border hover:border hover:text-white focus:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none active:border active:text-white">
53-
<Icons.PaintBucket className="h-2 w-2" />
54-
<div
55-
className="h-[4px] w-6 rounded-full bg-current"
56-
style={{ backgroundColor: tempColor?.toHex() }}
57-
/>
58-
</div>
50+
<PopoverTrigger asChild>
51+
{ColorTrigger}
5952
</PopoverTrigger>
6053
<PopoverContent
6154
className="w-[220px] overflow-hidden rounded-lg p-0 shadow-xl backdrop-blur-lg"
@@ -64,8 +57,8 @@ export const ColorBackground = () => {
6457
>
6558
<ColorPickerContent
6659
color={tempColor}
67-
onChange={handleColorChange}
68-
onChangeEnd={handleColorChangeEnd}
60+
onChange={handleColorUpdate}
61+
onChangeEnd={handleColorUpdateEnd}
6962
/>
7063
</PopoverContent>
7164
</Popover>

apps/web/client/src/app/project/[id]/_components/editor-bar/hooks/use-color-update.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@ import { useEditorEngine } from '@/components/store/editor';
22
import { DEFAULT_COLOR_NAME } from '@onlook/constants';
33
import type { TailwindColor } from '@onlook/models/style';
44
import { Color } from '@onlook/utility';
5-
import { useCallback } from 'react';
5+
import { useCallback, useState } from 'react';
66

77
interface ColorUpdateOptions {
88
elementStyleKey: string;
9+
initialColor?: string;
910
onValueChange?: (key: string, value: string) => void;
1011
}
1112

12-
13-
export const useColorUpdate = ({ elementStyleKey, onValueChange }: ColorUpdateOptions) => {
13+
export const useColorUpdate = ({
14+
elementStyleKey,
15+
initialColor,
16+
onValueChange,
17+
}: ColorUpdateOptions) => {
1418
const editorEngine = useEditorEngine();
19+
const [tempColor, setTempColor] = useState<Color>(Color.from(initialColor ?? '#000000'));
1520

16-
const handleColorUpdate = useCallback(
21+
const handleColorUpdateEnd = useCallback(
1722
(newValue: Color | TailwindColor) => {
1823
try {
1924
if (newValue instanceof Color) {
@@ -24,7 +29,7 @@ export const useColorUpdate = ({ elementStyleKey, onValueChange }: ColorUpdateOp
2429
} else {
2530
// Handle custom color updates
2631
let colorValue = newValue.originalKey;
27-
32+
2833
// Handle default color case
2934
if (colorValue.endsWith(DEFAULT_COLOR_NAME)) {
3035
colorValue = colorValue.split(`-${DEFAULT_COLOR_NAME}`)?.[0] ?? '';
@@ -39,10 +44,20 @@ export const useColorUpdate = ({ elementStyleKey, onValueChange }: ColorUpdateOp
3944
// You might want to add error handling UI feedback here
4045
}
4146
},
42-
[editorEngine.style, elementStyleKey, onValueChange]
47+
[editorEngine.style, elementStyleKey, onValueChange],
4348
);
4449

50+
const handleColorUpdate = useCallback((newColor: Color | TailwindColor) => {
51+
try {
52+
setTempColor(newColor instanceof Color ? newColor : Color.from(newColor.lightColor));
53+
} catch (error) {
54+
console.error('Error converting color:', error);
55+
}
56+
}, []);
57+
4558
return {
46-
handleColorUpdate
59+
tempColor,
60+
handleColorUpdate,
61+
handleColorUpdateEnd,
4762
};
48-
};
63+
};

apps/web/client/src/app/project/[id]/_components/editor-bar/hooks/use-text-control.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ interface TextState {
1111
fontWeight: string;
1212
textAlign: TextAlign;
1313
textColor: string;
14+
letterSpacing: string;
15+
capitalization: string;
16+
textDecorationLine: string;
17+
lineHeight: string;
1418
}
1519

1620
const DefaultState: TextState = {
@@ -19,6 +23,10 @@ const DefaultState: TextState = {
1923
fontWeight: '400',
2024
textAlign: 'left',
2125
textColor: '#000000',
26+
letterSpacing: '0',
27+
capitalization: 'none',
28+
textDecorationLine: 'none',
29+
lineHeight: '1.5',
2230
};
2331

2432
export const useTextControl = () => {
@@ -41,6 +49,18 @@ export const useTextControl = () => {
4149
DefaultState.textAlign) as TextAlign,
4250
textColor:
4351
editorEngine.style.selectedStyle?.styles.computed.color ?? DefaultState.textColor,
52+
letterSpacing:
53+
editorEngine.style.selectedStyle?.styles.computed.letterSpacing?.toString() ??
54+
DefaultState.letterSpacing,
55+
capitalization:
56+
editorEngine.style.selectedStyle?.styles.computed.textTransform?.toString() ??
57+
DefaultState.capitalization,
58+
textDecorationLine:
59+
editorEngine.style.selectedStyle?.styles.computed.textDecorationLine?.toString() ??
60+
DefaultState.textDecorationLine,
61+
lineHeight:
62+
editorEngine.style.selectedStyle?.styles.computed.lineHeight?.toString() ??
63+
DefaultState.lineHeight,
4464
};
4565
};
4666

@@ -86,12 +106,48 @@ export const useTextControl = () => {
86106
}));
87107
};
88108

109+
const handleLetterSpacingChange = (letterSpacing: string) => {
110+
setTextState((prev) => ({
111+
...prev,
112+
letterSpacing,
113+
}));
114+
editorEngine.style.update('letterSpacing', `${letterSpacing}px`);
115+
};
116+
117+
const handleCapitalizationChange = (capitalization: string) => {
118+
setTextState((prev) => ({
119+
...prev,
120+
capitalization,
121+
}));
122+
editorEngine.style.update('textTransform', capitalization);
123+
};
124+
125+
const handleTextDecorationChange = (textDecorationLine: string) => {
126+
setTextState((prev) => ({
127+
...prev,
128+
textDecorationLine,
129+
}));
130+
editorEngine.style.update('textDecorationLine', textDecorationLine);
131+
};
132+
133+
const handleLineHeightChange = (lineHeight: string) => {
134+
setTextState((prev) => ({
135+
...prev,
136+
lineHeight,
137+
}));
138+
editorEngine.style.update('lineHeight', lineHeight);
139+
};
140+
89141
return {
90142
textState,
91143
handleFontFamilyChange,
92144
handleFontSizeChange,
93145
handleFontWeightChange,
94146
handleTextAlignChange,
95147
handleTextColorChange,
148+
handleLetterSpacingChange,
149+
handleCapitalizationChange,
150+
handleTextDecorationChange,
151+
handleLineHeightChange,
96152
};
97153
};

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

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,27 @@ import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from '@onlook/
44
import { Color } from '@onlook/utility';
55
import { useCallback, useState } from 'react';
66
import { ColorPickerContent } from './color-picker';
7-
import type { TailwindColor } from '@onlook/models';
87
import { useColorUpdate } from '../hooks/use-color-update';
98

109
interface InputColorProps {
1110
color: string;
1211
elementStyleKey: string;
13-
onColorChange: (color: string) => void;
12+
onColorChange?: (color: string) => void;
1413
}
1514

1615
export const InputColor = ({ color, elementStyleKey, onColorChange }: InputColorProps) => {
1716
const [isOpen, setIsOpen] = useState(false);
18-
const [tempColor, setTempColor] = useState<Color>(Color.from(color));
1917

20-
const { handleColorUpdate } = useColorUpdate({
18+
const { handleColorUpdateEnd, handleColorUpdate, tempColor } = useColorUpdate({
2119
elementStyleKey,
22-
onValueChange: (_, value) => onColorChange(value),
20+
onValueChange: (_, value) => onColorChange?.(value),
21+
initialColor: color,
2322
});
2423

25-
const handleColorChange = useCallback((newColor: Color | TailwindColor) => {
26-
setTempColor(newColor instanceof Color ? newColor : Color.from(newColor.lightColor));
27-
}, []);
28-
29-
const handleColorChangeEnd = useCallback(
30-
(newColor: Color | TailwindColor) => {
31-
try {
32-
if (newColor instanceof Color) {
33-
setTempColor(newColor);
34-
} else {
35-
setTempColor(Color.from(newColor.lightColor));
36-
}
37-
handleColorUpdate(newColor);
38-
} catch (error) {
39-
console.error('Error updating color:', error);
40-
}
41-
},
42-
[handleColorUpdate],
43-
);
44-
4524
const handleInputChange = useCallback(
4625
(e: React.ChangeEvent<HTMLInputElement>) => {
4726
const value = e.target.value;
48-
setTempColor(Color.from(value));
27+
handleColorUpdateEnd(Color.from(value));
4928
onColorChange?.(value);
5029
},
5130
[onColorChange],
@@ -71,8 +50,8 @@ export const InputColor = ({ color, elementStyleKey, onColorChange }: InputColor
7150
>
7251
<ColorPickerContent
7352
color={tempColor}
74-
onChange={handleColorChange}
75-
onChangeEnd={handleColorChangeEnd}
53+
onChange={handleColorUpdate}
54+
onChangeEnd={handleColorUpdateEnd}
7655
/>
7756
</PopoverContent>
7857
</Popover>

0 commit comments

Comments
 (0)