Skip to content

Commit

Permalink
[feat] New Flexbox Controls #567 (#758)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Alexandre Antunes Mendes <alexandre.antunes4@etu.univ-lorraine.fr>
Co-authored-by: Jimmy L <jimmygam@icloud.com>
  • Loading branch information
3 people authored Nov 25, 2024
1 parent 4ddebb3 commit 3fc132c
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 93 deletions.
84 changes: 33 additions & 51 deletions apps/studio/src/lib/editor/styles/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,19 @@ export const LayoutGroup = [
new SingleStyleImpl('flexDirection', 'row', 'Direction', StyleType.Select, {
options: ['row', 'column'],
}),
new SingleStyleImpl('justifyContent', 'flex-start', 'Justify', StyleType.Select, {

new SingleStyleImpl('justifyContent', 'flex-start', 'Horizontal', StyleType.Select, {
options: ['flex-start', 'center', 'flex-end', 'space-between'],
}),
new SingleStyleImpl('alignItems', 'flex-start', 'Align', StyleType.Select, {
options: ['flex-start', 'center', 'flex-end', 'space-between'],

new SingleStyleImpl('alignItems', 'flex-start', 'Vertical', StyleType.Select, {
options: ['flex-start', 'center', 'flex-end', 'stretch'],
}),

new SingleStyleImpl('gridTemplateColumns', '', 'Columns', StyleType.Text),

new SingleStyleImpl('gridTemplateRows', '', 'Rows', StyleType.Text),

new SingleStyleImpl('gap', '0px', 'Gap', StyleType.Number, {
units: ELEMENT_STYLE_UNITS,
min: 0,
Expand Down Expand Up @@ -122,6 +127,7 @@ export const LayoutGroup = [
min: STYLE_CONSTRAINTS.padding.min,
max: STYLE_CONSTRAINTS.padding.max,
}),

new SingleStyleImpl('paddingRight', '', 'Right', StyleType.Number, {
units: ELEMENT_STYLE_UNITS,
min: STYLE_CONSTRAINTS.padding.min,
Expand Down Expand Up @@ -152,57 +158,29 @@ export const StyleGroup = [
max: STYLE_CONSTRAINTS.border.radius.max,
}),
[
new SingleStyleImpl(
'borderTopLeftRadius',
'',
'Top Left',
StyleType.Number,

{
units: ELEMENT_STYLE_UNITS,
min: STYLE_CONSTRAINTS.border.radius.min,
max: STYLE_CONSTRAINTS.border.radius.max,
},
),

new SingleStyleImpl(
'borderTopRightRadius',
'',
'Top Right',
StyleType.Number,

{
units: ELEMENT_STYLE_UNITS,
min: STYLE_CONSTRAINTS.border.radius.min,
max: STYLE_CONSTRAINTS.border.radius.max,
},
),

new SingleStyleImpl(
'borderBottomLeftRadius',
'',
'Bottom Left',
StyleType.Number,
new SingleStyleImpl('borderTopLeftRadius', '', 'Top Left', StyleType.Number, {
units: ELEMENT_STYLE_UNITS,
min: STYLE_CONSTRAINTS.border.radius.min,
max: STYLE_CONSTRAINTS.border.radius.max,
}),

{
units: ELEMENT_STYLE_UNITS,
min: STYLE_CONSTRAINTS.border.radius.min,
max: STYLE_CONSTRAINTS.border.radius.max,
},
),
new SingleStyleImpl('borderTopRightRadius', '', 'Top Right', StyleType.Number, {
units: ELEMENT_STYLE_UNITS,
min: STYLE_CONSTRAINTS.border.radius.min,
max: STYLE_CONSTRAINTS.border.radius.max,
}),

new SingleStyleImpl(
'borderBottomRightRadius',
'',
'Bottom Right',
StyleType.Number,
new SingleStyleImpl('borderBottomLeftRadius', '', 'Bottom Left', StyleType.Number, {
units: ELEMENT_STYLE_UNITS,
min: STYLE_CONSTRAINTS.border.radius.min,
max: STYLE_CONSTRAINTS.border.radius.max,
}),

{
units: ELEMENT_STYLE_UNITS,
min: STYLE_CONSTRAINTS.border.radius.min,
max: STYLE_CONSTRAINTS.border.radius.max,
},
),
new SingleStyleImpl('borderBottomRightRadius', '', 'Bottom Right', StyleType.Number, {
units: ELEMENT_STYLE_UNITS,
min: STYLE_CONSTRAINTS.border.radius.min,
max: STYLE_CONSTRAINTS.border.radius.max,
}),
],
),
new CompoundStyleImpl(
Expand All @@ -229,6 +207,7 @@ export const TextGroup = [
min: 1,
max: 1000,
}),

new SingleStyleImpl('fontWeight', 'normal', 'Weight', StyleType.Select, {
options: [
'lighter',
Expand All @@ -245,14 +224,17 @@ export const TextGroup = [
'900',
],
}),

new SingleStyleImpl('letterSpacing', '0px', 'Letter', StyleType.Number, {
units: ELEMENT_STYLE_UNITS,
max: 100,
}),

new SingleStyleImpl('lineHeight', '100%', 'Line Height', StyleType.Number, {
units: ['%', 'px'],
max: 1000,
}),

new SingleStyleImpl('textAlign', 'start', 'Align', StyleType.Select, {
options: ['start', 'center', 'end'],
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ const DisplayInput = observer(({ compoundStyle }: { compoundStyle: CompoundStyle
) {
const flexDirection = getFlexDirection() as 'row' | 'column';
if (elementStyle.key === 'justifyContent') {
return flexDirection === 'row' ? 'X Align' : 'Y Align';
return flexDirection === 'row' ? 'Horizontal' : 'Vertical';
} else {
return flexDirection === 'row' ? 'Y Align' : 'X Align';
return flexDirection === 'row' ? 'Vertical' : 'Horizontal';
}
}

Expand Down
4 changes: 2 additions & 2 deletions apps/studio/src/routes/editor/EditPanel/StylesTab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,14 @@ const ManualTab = observer(() => {
className={cn(
'text-xs flex transition-all items-center group',
editorEngine.style.mode === StyleMode.Instance &&
'gap-1 text-purple-600 dark:text-purple-300 hover:text-purple-500 dark:hover:text-purple-200',
'gap-1 text-purple-600 dark:text-purple-300 hover:text-purple-500 dark:hover:text-purple-200',
)}
>
<Icons.ComponentInstance
className={cn(
'transition-all w-0',
editorEngine.style.mode === StyleMode.Instance &&
'w-3 h-3 text-purple-600 dark:text-purple-300 group-hover:text-purple-500 dark:group-hover:text-purple-200',
'w-3 h-3 text-purple-600 dark:text-purple-300 group-hover:text-purple-500 dark:group-hover:text-purple-200',
)}
/>
{groupKey}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useEditorEngine } from '@/components/Context';
import type { SingleStyle } from '@/lib/editor/styles/models';
import { Icons } from '@onlook/ui/icons';
import { ToggleGroup, ToggleGroupItem } from '@onlook/ui/toggle-group';
import type { SingleStyle } from '@/lib/editor/styles/models';
import { observer } from 'mobx-react-lite';
import { useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';

const OVERRIDE_OPTIONS: Record<string, string | undefined> = {
'flex-start': 'start',
Expand All @@ -16,7 +16,7 @@ const OVERRIDE_OPTIONS: Record<string, string | undefined> = {
'flex-end flex-end': 'evenly',
};

const OVERRIDE_ICONS: Record<string, any> = {
const OVERRIDE_ICONS: Record<string, JSX.Element | string | Record<string, JSX.Element>> = {
'flex-start': <Icons.ArrowRight />,
'flex-end': <Icons.ArrowDown />,
'space-between': <Icons.ArrowRight />,
Expand All @@ -34,8 +34,24 @@ const OVERRIDE_ICONS: Record<string, any> = {
row: <Icons.ArrowRight />,
column: <Icons.ArrowDown />,
block: '--',
justifyContent: {
'flex-start': <Icons.AlignLeft />,
center: <Icons.AlignCenterHorizontally />,
'flex-end': <Icons.AlignRight />,
'space-between': <Icons.SpaceBetweenHorizontally />,
stretch: <Icons.SpaceBetweenHorizontally />,
},
alignItems: {
'flex-start': <Icons.AlignTop />,
center: <Icons.AlignCenterVertically />,
'flex-end': <Icons.AlignBottom />,
'space-between': <Icons.SpaceBetweenVertically />,
stretch: <Icons.SpaceBetweenVertically />,
},
};

const ICON_SELECTION = ['justifyContent', 'alignItems'];

const SelectInput = observer(
({
elementStyle,
Expand Down Expand Up @@ -64,11 +80,37 @@ const SelectInput = observer(
onValueChange && onValueChange(elementStyle.key, newValue);
};

function rendeUpToThreeOptions() {
if (!elementStyle.params?.options || elementStyle.params.options.length > 3) {
const getFlexDirection = () => {
const selectedStyle = editorEngine.style.selectedStyle;
if (!selectedStyle) {
return 'row'; // default to row
}
return selectedStyle.styles['flexDirection'] ?? 'row'; // fallback to row if undefined
};

const getIcon = (option: string) => {
const flexDirection = getFlexDirection();
if (elementStyle.key === 'justifyContent') {
return flexDirection === 'row'
? (OVERRIDE_ICONS.justifyContent as Record<string, JSX.Element>)[option]
: (OVERRIDE_ICONS.alignItems as Record<string, JSX.Element>)[option];
} else if (elementStyle.key === 'alignItems') {
return flexDirection === 'row'
? (OVERRIDE_ICONS.alignItems as Record<string, JSX.Element>)[option]
: (OVERRIDE_ICONS.justifyContent as Record<string, JSX.Element>)[option];
}
const icon = OVERRIDE_ICONS[option];
if (typeof icon === 'object' && !React.isValidElement(icon)) {
return null;
}
return icon || option;
};

if (!elementStyle.params?.options) {
return null;
}

if (elementStyle.params.options.length <= 3 || ICON_SELECTION.includes(elementStyle.key)) {
return (
<ToggleGroup
className="w-32 overflow-hidden"
Expand All @@ -83,46 +125,33 @@ const SelectInput = observer(
value={option}
key={option}
>
{OVERRIDE_ICONS[option] ?? option}
{getIcon(option)}
</ToggleGroupItem>
))}
</ToggleGroup>
);
}

function renderMoreThanThreeOptions() {
if (!elementStyle.params?.options || elementStyle.params.options.length <= 3) {
return null;
}

return (
<div className="relative w-32">
<select
name={elementStyle.displayName}
value={value}
className="p-[6px] w-full px-2 text-start rounded border-none text-xs text-active bg-background-onlook/75 appearance-none focus:outline-none focus:ring-0 capitalize"
onChange={(event) => handleValueChange(event.currentTarget.value)}
>
{!elementStyle.params.options.includes(value) && (
<option value={value}>{value}</option>
)}
{elementStyle.params.options.map((option) => (
<option value={option} key={option}>
{OVERRIDE_OPTIONS[option] ?? option}
</option>
))}
</select>
<div className="text-foreground-onlook absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<Icons.ChevronDown />
</div>
</div>
);
}

return (
<div>
{rendeUpToThreeOptions()}
{renderMoreThanThreeOptions()}
<div className="relative w-32">
<select
name={elementStyle.displayName}
value={value}
className="p-[6px] w-full px-2 text-start rounded border-none text-xs text-active bg-background-onlook/75 appearance-none focus:outline-none focus:ring-0 capitalize"
onChange={(event) => handleValueChange(event.currentTarget.value)}
>
{!elementStyle.params.options.includes(value) && (
<option value={value}>{value}</option>
)}
{elementStyle.params.options.map((option) => (
<option value={option} key={option}>
{OVERRIDE_OPTIONS[option] ?? option}
</option>
))}
</select>
<div className="text-foreground-onlook absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<Icons.ChevronDown />
</div>
</div>
);
},
Expand Down
16 changes: 16 additions & 0 deletions packages/ui/src/components/icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ import {
ViewGridIcon,
ViewHorizontalIcon,
ViewVerticalIcon,
AlignLeftIcon,
AlignCenterHorizontallyIcon,
AlignRightIcon,
SpaceBetweenHorizontallyIcon,
AlignTopIcon,
AlignCenterVerticallyIcon,
AlignBottomIcon,
SpaceBetweenVerticallyIcon
} from '@radix-ui/react-icons';
import { cn } from '../../utils';
import H1Icon from './header-level-icons/h1Icon';
Expand Down Expand Up @@ -945,4 +953,12 @@ export const Icons = {
LockOpen: LockOpen1Icon,
LockClosed: LockClosedIcon,
DragHandleDots: DragHandleDots2Icon,
AlignLeft: AlignLeftIcon,
AlignCenterHorizontally: AlignCenterHorizontallyIcon,
AlignRight: AlignRightIcon,
SpaceBetweenHorizontally: SpaceBetweenHorizontallyIcon,
AlignTop: AlignTopIcon,
AlignCenterVertically: AlignCenterVerticallyIcon,
AlignBottom: AlignBottomIcon,
SpaceBetweenVertically: SpaceBetweenVerticallyIcon,
} satisfies { [key: string]: React.FC<IconProps> };

0 comments on commit 3fc132c

Please sign in to comment.