Skip to content

Commit ab15c9a

Browse files
add ability to use arrow in input (#1903)
* add ability to use arrow in input * refactor font size input * refactor dimension input * refactor input color
1 parent 411c0e3 commit ab15c9a

File tree

4 files changed

+131
-76
lines changed

4 files changed

+131
-76
lines changed

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

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,45 +49,60 @@ export const useDimensionControl = <T extends DimensionType>(dimension: T) => {
4949
const editorEngine = useEditorEngine();
5050

5151
const getInitialState = useCallback((): DimensionStateMap<T> => {
52-
const computedStyles = editorEngine.style.selectedStyle?.styles.computed;
53-
if (!computedStyles) {
52+
const definedStyles = editorEngine.style.selectedStyle?.styles.defined;
53+
if (!definedStyles) {
5454
return createDefaultState(dimension);
5555
}
56-
const { num, unit } = stringToParsedValue(computedStyles[dimension]?.toString() ?? '--');
57-
const { num: maxNum, unit: maxUnit } = stringToParsedValue(
58-
computedStyles[
59-
`max${dimension.charAt(0).toUpperCase() + dimension.slice(1)}` as keyof CSSProperties
60-
]?.toString() ?? '--',
61-
);
62-
const { num: minNum, unit: minUnit } = stringToParsedValue(
63-
computedStyles[
64-
`min${dimension.charAt(0).toUpperCase() + dimension.slice(1)}` as keyof CSSProperties
65-
]?.toString() ?? '--',
66-
);
56+
57+
const dimensionValue = definedStyles[dimension]?.toString() ?? '--';
58+
const { num, unit } = stringToParsedValue(dimensionValue);
59+
60+
const maxDimensionKey = `max${dimension.charAt(0).toUpperCase() + dimension.slice(1)}` as keyof CSSProperties;
61+
const maxDimensionValue = definedStyles[maxDimensionKey]?.toString() ?? '--';
62+
const { num: maxNum, unit: maxUnit } = stringToParsedValue(maxDimensionValue);
63+
64+
const minDimensionKey = `min${dimension.charAt(0).toUpperCase() + dimension.slice(1)}` as keyof CSSProperties;
65+
const minDimensionValue = definedStyles[minDimensionKey]?.toString() ?? '--';
66+
const { num: minNum, unit: minUnit } = stringToParsedValue(minDimensionValue);
6767

6868
const defaultState = createDefaultState(dimension);
69-
const capitalized = (dimension.charAt(0).toUpperCase() +
70-
dimension.slice(1)) as Capitalize<T>;
69+
const capitalized = (dimension.charAt(0).toUpperCase() + dimension.slice(1)) as Capitalize<T>;
70+
71+
const getDropdownValue = (value: string) => {
72+
const { mode } = parseModeAndValue(value);
73+
switch (mode) {
74+
case LayoutMode.Fit:
75+
return 'Hug';
76+
case LayoutMode.Fill:
77+
return 'Fill';
78+
case LayoutMode.Relative:
79+
return 'Relative';
80+
case LayoutMode.Fixed:
81+
return 'Fixed';
82+
default:
83+
return 'Fixed';
84+
}
85+
};
7186

7287
return {
7388
...defaultState,
7489
[dimension]: {
7590
num: num,
7691
unit: unit,
7792
value: num ? `${num}${unit}` : 'auto',
78-
dropdownValue: num ? 'Fixed' : 'Hug',
93+
dropdownValue: getDropdownValue(dimensionValue),
7994
},
8095
[`max${capitalized}`]: {
8196
num: maxNum,
8297
unit: maxUnit,
8398
value: maxNum ? `${maxNum}${maxUnit}` : '--',
84-
dropdownValue: 'Fixed',
99+
dropdownValue: getDropdownValue(maxDimensionValue),
85100
},
86101
[`min${capitalized}`]: {
87102
num: minNum,
88103
unit: minUnit,
89104
value: minNum ? `${minNum}${minUnit}` : '--',
90-
dropdownValue: 'Fixed',
105+
dropdownValue: getDropdownValue(minDimensionValue),
91106
},
92107
} as DimensionStateMap<T>;
93108
}, [dimension, editorEngine.style.selectedStyle]);

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

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,26 @@ export const InputColor = ({ color, elementStyleKey, onColorChange }: InputColor
3232

3333
return (
3434
<div className="flex h-9 w-full items-center">
35-
<div className="bg-background-tertiary/50 mr-[1px] flex h-full flex-1 items-center rounded-md px-3 py-1.5 pl-1.5">
35+
<div className="bg-background-tertiary/50 mr-[1px] flex h-full flex-1 items-center rounded-l-md px-3 py-1.5 pl-1.5">
3636
<Popover onOpenChange={setIsOpen}>
3737
<PopoverAnchor className="absolute bottom-0 left-0" />
38-
3938
<PopoverTrigger>
40-
<div
41-
className="mr-2 aspect-square h-5 w-5 rounded-sm"
42-
style={{ backgroundColor: tempColor.toHex() }}
43-
onClick={() => setIsOpen(!isOpen)}
44-
/>
39+
<div className="flex items-center">
40+
<div
41+
className="mr-2 aspect-square h-5 w-5 rounded-sm"
42+
style={{ backgroundColor: tempColor.toHex() }}
43+
onClick={() => setIsOpen(!isOpen)}
44+
/>
45+
<input
46+
type="text"
47+
value={tempColor.toHex6()}
48+
onChange={handleInputChange}
49+
className="h-full w-full bg-transparent text-sm text-white focus:outline-none"
50+
/>
51+
</div>
4552
</PopoverTrigger>
4653
<PopoverContent
47-
className="w-[280px] overflow-hidden rounded-lg p-0 shadow-xl backdrop-blur-lg"
54+
className="w-[224px] overflow-hidden rounded-lg p-0 shadow-xl backdrop-blur-lg"
4855
side="bottom"
4956
align="start"
5057
>
@@ -55,13 +62,9 @@ export const InputColor = ({ color, elementStyleKey, onColorChange }: InputColor
5562
/>
5663
</PopoverContent>
5764
</Popover>
58-
59-
<input
60-
type="text"
61-
value={tempColor.toHex()}
62-
onChange={handleInputChange}
63-
className="h-full w-full bg-transparent text-sm text-white focus:outline-none"
64-
/>
65+
</div>
66+
<div className="text-xs text-white bg-background-tertiary/50 flex h-full items-center rounded-r-md px-3 py-1.5">
67+
{Math.round(tempColor.rgb.a * 100).toString()}%
6568
</div>
6669
</div>
6770
);

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { Icons } from '@onlook/ui/icons';
1111
import { debounce } from 'lodash';
1212
import { useEffect, useMemo, useState } from 'react';
13+
import type { KeyboardEvent } from 'react';
1314

1415
const UNITS = ['PX', '%', 'EM', 'REM'];
1516

@@ -56,6 +57,24 @@ export const InputDropdown = ({
5657
};
5758
}, [debouncedOnChange]);
5859

60+
const handleIncrement = (step: number) => {
61+
const currentValue = parseFloat(localValue);
62+
if (!isNaN(currentValue)) {
63+
const newValue = (currentValue + step).toString();
64+
setLocalValue(newValue);
65+
debouncedOnChange(newValue);
66+
}
67+
};
68+
69+
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
70+
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
71+
e.preventDefault();
72+
const step = e.shiftKey ? 10 : 1;
73+
const direction = e.key === 'ArrowUp' ? 1 : -1;
74+
handleIncrement(step * direction);
75+
}
76+
};
77+
5978
return (
6079
<div className="flex items-center">
6180
<div className="flex flex-1 items-center bg-background-tertiary/50 justify-between rounded-l-md px-2.5 h-[36px] min-w-[72px]">
@@ -67,7 +86,9 @@ export const InputDropdown = ({
6786
setLocalValue(newValue);
6887
debouncedOnChange(newValue);
6988
}}
89+
onKeyDown={handleKeyDown}
7090
className="w-[32px] bg-transparent text-sm text-white focus:outline-none text-left"
91+
aria-label="Value input"
7192
/>
7293
<DropdownMenu>
7394
<DropdownMenuTrigger className="text-sm text-muted-foreground focus:outline-none cursor-pointer hover:text-white transition-colors">

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

Lines changed: 59 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
'use client';
22

3+
import { useEditorEngine } from '@/components/store/editor';
34
import { VARIANTS } from '@onlook/fonts';
5+
import { BrandTabValue, LeftPanelTabValue } from '@onlook/models';
6+
import type { Font } from '@onlook/models/assets';
47
import { Button } from '@onlook/ui/button';
58
import {
69
DropdownMenu,
@@ -11,31 +14,26 @@ import {
1114
import { Icons } from '@onlook/ui/icons';
1215
import { Popover, PopoverContent, PopoverTrigger } from '@onlook/ui/popover';
1316
import { Tooltip, TooltipContent, TooltipTrigger } from '@onlook/ui/tooltip';
14-
import { Color, convertFontWeight, toNormalCase } from '@onlook/utility';
15-
import { memo, useEffect, useState, useRef, useCallback } from 'react';
16-
import { useTextControl, type TextAlign } from './hooks/use-text-control';
17-
import { ColorPickerContent } from './inputs/color-picker';
18-
import { ViewButtons } from './panels/panel-bar/bar';
19-
import { InputSeparator } from './separator';
20-
import { useEditorEngine } from '@/components/store/editor';
21-
import type { Font } from '@onlook/models/assets';
22-
import { useColorUpdate } from './hooks/use-color-update';
17+
import { convertFontWeight, toNormalCase } from '@onlook/utility';
18+
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
19+
import { FontFamily } from '../left-panel/brand-tab/font-panel/font-family';
20+
import { Border } from './dropdowns/border';
21+
import { ColorBackground } from './dropdowns/color-background';
22+
import { Display } from './dropdowns/display';
2323
import { Height } from './dropdowns/height';
24-
import { Width } from './dropdowns/width';
24+
import { Margin } from './dropdowns/margin';
2525
import { Opacity } from './dropdowns/opacity';
26-
import { Display } from './dropdowns/display';
2726
import { Padding } from './dropdowns/padding';
28-
import { Margin } from './dropdowns/margin';
2927
import { Radius } from './dropdowns/radius';
30-
import { Border } from './dropdowns/border';
31-
import { ColorBackground } from './dropdowns/color-background';
32-
import React from 'react';
33-
import { baseKeymap } from 'prosemirror-commands';
34-
import { FontFamily } from '../left-panel/brand-tab/font-panel/font-family';
35-
import { BrandTabValue, LeftPanelTabValue } from '@onlook/models';
28+
import { Width } from './dropdowns/width';
29+
import { useColorUpdate } from './hooks/use-color-update';
30+
import { useTextControl, type TextAlign } from './hooks/use-text-control';
31+
import { ColorPickerContent } from './inputs/color-picker';
32+
import { InputColor } from './inputs/input-color';
3633
import { InputIcon } from './inputs/input-icon';
3734
import { InputRadio } from './inputs/input-radio';
38-
import { InputColor } from './inputs/input-color';
35+
import { ViewButtons } from './panels/panel-bar/bar';
36+
import { InputSeparator } from './separator';
3937

4038
const FONT_SIZES = [12, 14, 16, 18, 20, 24, 30, 36, 48, 60, 72, 96];
4139

@@ -240,8 +238,8 @@ const FontWeightSelector = memo(
240238
key={weight.value}
241239
onClick={() => handleFontWeightChange(weight.value)}
242240
className={`text-muted-foreground data-[highlighted]:bg-background-tertiary/10 border-border/0 data-[highlighted]:border-border flex items-center justify-between rounded-md border px-2 py-1.5 text-sm data-[highlighted]:text-white cursor-pointer transition-colors duration-150 hover:bg-background-tertiary/20 hover:text-foreground ${fontWeight === weight.value
243-
? 'bg-background-tertiary/20 border-border border text-white'
244-
: ''
241+
? 'bg-background-tertiary/20 border-border border text-white'
242+
: ''
245243
}`}
246244
>
247245
{weight.name}
@@ -265,12 +263,24 @@ const FontSizeSelector = memo(
265263
fontSize: number;
266264
handleFontSizeChange: (size: number) => void;
267265
}) => {
266+
const [open, setOpen] = useState(false);
267+
const inputRef = useRef<HTMLInputElement>(null);
268+
268269
const adjustFontSize = (amount: number) => {
269270
handleFontSizeChange(Math.max(1, fontSize + amount));
270271
};
271272

273+
const handleInputClick = () => {
274+
setOpen(true);
275+
// Use setTimeout to ensure the input is focused after the popover opens
276+
setTimeout(() => {
277+
inputRef.current?.focus();
278+
inputRef.current?.select();
279+
}, 0);
280+
};
281+
272282
return (
273-
<DropdownMenu>
283+
<Popover open={open} onOpenChange={setOpen}>
274284
<Tooltip>
275285
<div>
276286
<TooltipTrigger asChild>
@@ -283,8 +293,9 @@ const FontSizeSelector = memo(
283293
>
284294
<Icons.Minus className="h-4 w-4" />
285295
</Button>
286-
<DropdownMenuTrigger asChild>
296+
<PopoverTrigger asChild>
287297
<input
298+
ref={inputRef}
288299
type="number"
289300
value={fontSize}
290301
onChange={(e) => {
@@ -293,10 +304,10 @@ const FontSizeSelector = memo(
293304
handleFontSizeChange(value);
294305
}
295306
}}
296-
onClick={(e) => e.stopPropagation()}
307+
onClick={handleInputClick}
297308
className="border-border/0 text-muted-foreground hover:bg-background-tertiary/20 hover:border-border data-[state=open]:bg-background-tertiary/20 data-[state=open]:border-border focus:bg-background-tertiary/20 focus:ring-border h-8 max-w-[40px] min-w-[40px] [appearance:textfield] rounded-lg border px-1 text-center text-sm hover:border hover:text-white focus:ring-1 focus:outline-none data-[state=open]:border data-[state=open]:text-white [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
298309
/>
299-
</DropdownMenuTrigger>
310+
</PopoverTrigger>
300311

301312
<Button
302313
variant="ghost"
@@ -313,24 +324,29 @@ const FontSizeSelector = memo(
313324
</TooltipContent>
314325
</div>
315326
</Tooltip>
316-
<DropdownMenuContent
327+
<PopoverContent
317328
align="center"
318329
className="mt-1 w-[48px] min-w-[48px] rounded-lg p-1"
319330
>
320-
{FONT_SIZES.map((size) => (
321-
<DropdownMenuItem
322-
key={size}
323-
onClick={() => handleFontSizeChange(size)}
324-
className={`cursor-pointer text-muted-foreground data-[highlighted]:bg-background-tertiary/10 border-border/0 data-[highlighted]:border-border justify-center rounded-md border px-2 py-1 text-sm data-[highlighted]:text-white ${size === fontSize
331+
<div className="grid grid-cols-1 gap-1">
332+
{FONT_SIZES.map((size) => (
333+
<button
334+
key={size}
335+
onClick={() => {
336+
handleFontSizeChange(size);
337+
setOpen(false);
338+
}}
339+
className={`cursor-pointer text-muted-foreground data-[highlighted]:bg-background-tertiary/10 border-border/0 data-[highlighted]:border-border justify-center rounded-md border px-2 py-1 text-sm data-[highlighted]:text-white ${size === fontSize
325340
? 'bg-background-tertiary/20 border-border border text-white'
326341
: ''
327-
}`}
328-
>
329-
{size}
330-
</DropdownMenuItem>
331-
))}
332-
</DropdownMenuContent>
333-
</DropdownMenu>
342+
}`}
343+
>
344+
{size}
345+
</button>
346+
))}
347+
</div>
348+
</PopoverContent>
349+
</Popover>
334350
);
335351
},
336352
);
@@ -353,7 +369,7 @@ const TextAlignSelector = memo(
353369
<Button
354370
variant="ghost"
355371
size="toolbar"
356-
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 max-w-9 min-w-9 cursor-pointer items-center justify-center gap-2 rounded-lg border px-2 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"
372+
className="text-white border-border/0 hover:bg-background-tertiary/20 hover:border-border data-[state=open]:bg-background-tertiary/20 data-[state=open]:border-border flex max-w-9 min-w-9 cursor-pointer items-center justify-center gap-2 rounded-lg border px-2 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"
357373
>
358374
{(() => {
359375
switch (textAlign) {
@@ -389,10 +405,9 @@ const TextAlignSelector = memo(
389405
<DropdownMenuItem
390406
key={value}
391407
onClick={() => handleTextAlignChange(value)}
392-
className={`text-muted-foreground data-[highlighted]:bg-background-tertiary/10 border-border/0 data-[highlighted]:border-border rounded-md border px-2 py-1.5 data-[highlighted]:text-foreground cursor-pointer transition-colors duration-150 hover:bg-background-tertiary/20 hover:text-foreground ${
393-
textAlign === value
408+
className={`text-muted-foreground data-[highlighted]:bg-background-tertiary/10 border-border/0 data-[highlighted]:border-border rounded-md border px-2 py-1.5 data-[highlighted]:text-foreground cursor-pointer transition-colors duration-150 hover:bg-background-tertiary/20 hover:text-foreground ${textAlign === value
394409
? 'bg-background-tertiary/20 border-border border text-white'
395-
: ''
410+
: ''
396411
}`}
397412
>
398413
<Icon className="h-4 w-4" />
@@ -749,4 +764,5 @@ export const TextSelected = ({ availableWidth = 0 }: { availableWidth?: number }
749764
);
750765
};
751766

752-
export { FontFamilySelector, FontWeightSelector, FontSizeSelector, TextColor, TextAlignSelector };
767+
export { FontFamilySelector, FontSizeSelector, FontWeightSelector, TextAlignSelector, TextColor };
768+

0 commit comments

Comments
 (0)