Skip to content

Commit

Permalink
Merge pull request #878 from onlook-dev/ui-polish-1
Browse files Browse the repository at this point in the history
UI Sanding + Performance improvement in the layers
  • Loading branch information
Kitenite authored Dec 13, 2024
2 parents 692d928 + a61118b commit 963eec7
Show file tree
Hide file tree
Showing 15 changed files with 348 additions and 231 deletions.
1 change: 1 addition & 0 deletions apps/studio/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ html body #root {

body {
font-family: 'Inter Variable', sans-serif;
user-select: none;
}

*:focus-visible {
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 @@ -130,10 +130,10 @@ const ManualTab = observer(() => {
function renderStyleSections() {
return Object.entries(STYLE_GROUP_MAPPING).map(([groupKey, baseElementStyles]) => (
<AccordionItem key={groupKey} value={groupKey}>
<AccordionTrigger className="mx-0">
<AccordionTrigger className=" mb-[-4px] mt-[-2px]">
{renderAccordianHeader(groupKey)}
</AccordionTrigger>
<AccordionContent>
<AccordionContent className="mt-2px">
{groupKey === StyleGroupKey.Text && <TagDetails />}
{renderGroupValues(baseElementStyles)}
</AccordionContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ const NumberUnitInput = observer(

const renderUnitInput = () => {
return (
<div className="relative w-full">
<div className="relative w-full group">
<select
value={unitValue}
className="p-[6px] w-full px-2 rounded border-none text-foreground-active bg-background-onlook/75 text-start appearance-none focus:outline-none focus:ring-0"
Expand All @@ -142,7 +142,7 @@ const NumberUnitInput = observer(
</option>
))}
</select>
<div className="text-foreground-onlook absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<div className="text-foreground-onlook group-hover:text-foreground-hover absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<Icons.ChevronDown />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ const SelectInput = observer(
if (elementStyle.params.options.length <= 3 || ICON_SELECTION.includes(elementStyle.key)) {
return (
<ToggleGroup
className="w-32 overflow-hidden"
className={`w-32 overflow-hidden ${
ICON_SELECTION.includes(elementStyle.key) ? 'gap-0.75' : ''
}`}
size="sm"
type="single"
value={value}
Expand Down
91 changes: 69 additions & 22 deletions apps/studio/src/routes/editor/LayersPanel/Tree/TreeNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ const TreeNode = observer(
const selected = editorEngine.elements.selected.some((el) => el.domId === node.data.domId);
const instanceId = node.data.instanceId;
const component = node.data.component;
const isParentSelected = parentSelected(node);
const isParentGroupEnd = parentGroupEnd(node);
const isComponentAncestor = hasComponentAncestor(node);
const isText = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(
node.data.tagName.toLowerCase(),
);

function handleHoverNode(e: React.MouseEvent<HTMLDivElement>) {
if (hovered) {
Expand Down Expand Up @@ -138,6 +144,16 @@ const TreeNode = observer(
node.data.isVisible = !node.data.isVisible;
}

function hasComponentAncestor(node: NodeApi<LayerNode>): boolean {
if (!node) {
return false;
}
if (node.data.instanceId) {
return true;
}
return node.parent ? hasComponentAncestor(node.parent) : false;
}

return (
<Tooltip>
<TooltipTrigger asChild>
Expand All @@ -149,43 +165,54 @@ const TreeNode = observer(
onMouseOver={(e) => handleHoverNode(e)}
className={twMerge(
cn('flex flex-row items-center h-6 cursor-pointer w-full pr-1', {
'text-purple-600 dark:text-purple-300':
isComponentAncestor && !instanceId && !hovered,
'text-purple-500 dark:text-purple-200':
isComponentAncestor && !instanceId && hovered,
'text-foreground-onlook':
!isComponentAncestor &&
!instanceId &&
!selected &&
!hovered,
rounded:
(hovered && !parentSelected(node) && !selected) ||
(hovered && !isParentSelected && !selected) ||
(selected && node.isLeaf) ||
(selected && node.isClosed),
'rounded-t': selected && node.isInternal,
'rounded-b': parentSelected(node) && parentGroupEnd(node),
'rounded-none': parentSelected(node) && node.nextSibling,
'rounded-b': isParentSelected && isParentGroupEnd,
'rounded-none': isParentSelected && node.nextSibling,
'bg-background-onlook': hovered,
'bg-[#FA003C] dark:bg-[#FA003C]/90': selected,
'bg-[#FA003C]/10 dark:bg-[#FA003C]/10': parentSelected(node),
'bg-[#FA003C]/10 dark:bg-[#FA003C]/10': isParentSelected,
'bg-[#FA003C]/20 dark:bg-[#FA003C]/20':
hovered && parentSelected(node),
hovered && isParentSelected,
'text-purple-100 dark:text-purple-100': instanceId && selected,
'text-purple-500 dark:text-purple-300': instanceId && !selected,
'text-purple-800 dark:text-purple-200':
instanceId && !selected && hovered,
'bg-purple-700/70 dark:bg-purple-500/50':
instanceId && selected,
'bg-purple-400/30 dark:bg-purple-900/60':
instanceId && !selected && hovered && !parentSelected(node),
instanceId && !selected && hovered && !isParentSelected,
'bg-purple-300/30 dark:bg-purple-900/30':
parentSelected(node)?.data.instanceId,
isParentSelected?.data.instanceId,
'bg-purple-300/50 dark:bg-purple-900/50':
hovered && parentSelected(node)?.data.instanceId,
hovered && isParentSelected?.data.instanceId,
'text-white dark:text-primary': !instanceId && selected,
'text-hover': !instanceId && !selected && hovered,
'text-foreground-onlook': !instanceId && !selected && !hovered,
}),
)}
>
<span className="w-4 h-4 flex-none">
<span className="w-4 h-4 flex-none relative">
{!node.isLeaf && (
<div
className="w-4 h-4 flex items-center justify-center"
onClick={() => node.toggle()}
className="w-4 h-4 flex items-center justify-center absolute z-50"
onMouseDown={(e) => {
node.select();
sendMouseEvent(e, node.data, MouseAction.MOUSE_DOWN);
node.toggle();
}}
>
{treeHovered && (
{hovered && (
<motion.div
initial={false}
animate={{ rotate: node.isOpen ? 90 : 0 }}
Expand All @@ -203,14 +230,34 @@ const TreeNode = observer(
hovered && !selected
? 'text-purple-600 dark:text-purple-200 '
: selected
? 'text-purple-100 dark:text-purple-100'
: 'text-purple-500 dark:text-purple-300',
? 'text-purple-100 dark:text-purple-100'
: 'text-purple-500 dark:text-purple-300',
)}
/>
) : (
<NodeIcon
iconClass={cn('w-3 h-3 ml-1 mr-2 flex-none', {
'fill-white dark:fill-primary': !instanceId && selected,
'[&_path]:!fill-purple-400 [&_path]:!dark:fill-purple-300':
isComponentAncestor &&
!instanceId &&
!selected &&
!hovered &&
!isText,
'[&_path]:!fill-purple-300 [&_path]:!dark:fill-purple-200':
isComponentAncestor &&
!instanceId &&
!selected &&
hovered &&
!isText,
'[&_path]:!fill-white [&_path]:!dark:fill-primary':
isComponentAncestor && !instanceId && selected,
'[&_.letter]:!fill-foreground/50 [&_.level]:!fill-foreground dark:[&_.letter]:!fill-foreground/50 dark:[&_.level]:!fill-foreground':
!isComponentAncestor && !selected && isText,
'[&_.letter]:!fill-purple-400/50 [&_.level]:!fill-purple-400 dark:[&_.letter]:!fill-purple-300/50 dark:[&_.level]:!fill-purple-300':
isComponentAncestor && !selected && !hovered && isText,
'[&_.letter]:!fill-purple-300/50 [&_.level]:!fill-purple-300 dark:[&_.letter]:!fill-purple-200/50 dark:[&_.level]:!fill-purple-200':
isComponentAncestor && !selected && hovered && isText,
})}
node={node.data}
/>
Expand All @@ -222,8 +269,8 @@ const TreeNode = observer(
? selected
? 'text-purple-100 dark:text-purple-100'
: hovered
? 'text-purple-600 dark:text-purple-200'
: 'text-purple-500 dark:text-purple-300'
? 'text-purple-600 dark:text-purple-200'
: 'text-purple-500 dark:text-purple-300'
: '',
!node.data.isVisible && 'opacity-80',
selected && 'mr-5',
Expand All @@ -232,10 +279,10 @@ const TreeNode = observer(
{component
? component
: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'].includes(
node.data.tagName.toLowerCase(),
)
? ''
: node.data.tagName.toLowerCase()}
node.data.tagName.toLowerCase(),
)
? ''
: node.data.tagName.toLowerCase()}
{' ' + node.data.textContent}
</span>
{selected && (
Expand Down
123 changes: 84 additions & 39 deletions apps/studio/src/routes/editor/Toolbar/Terminal/RunButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import { cn } from '@onlook/ui/utils';
import { AnimatePresence, motion } from 'framer-motion';
import { observer } from 'mobx-react-lite';
import { useMemo, useState } from 'react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@onlook/ui/tooltip';

const RunButton = observer(() => {
interface RunButtonProps {
setTerminalHidden: (hidden: boolean) => void;
}

const RunButton = observer(({ setTerminalHidden }: RunButtonProps) => {
const projectsManager = useProjectsManager();
const runner = projectsManager.runner;
const [isLoading, setIsLoading] = useState(false);
Expand Down Expand Up @@ -37,12 +42,15 @@ const RunButton = observer(() => {
}

if (runner.state === RunState.STOPPED) {
runner.start();
startLoadingTimer();
runner.start();
setTerminalHidden(false);
} else if (runner.state === RunState.RUNNING) {
runner.stop();
} else if (runner.state === RunState.ERROR) {
startLoadingTimer();
runner.restart();
setTerminalHidden(false);
} else {
console.error('Unexpected state:', runner.state);
}
Expand Down Expand Up @@ -88,50 +96,87 @@ const RunButton = observer(() => {
const buttonCharacters = useMemo(() => {
const text = getButtonTitle();
const characters = text.split('').map((ch, index) => ({
id: `${ch}${index}`,
id: `runbutton_${ch}${index}`,
label: index === 0 ? ch.toUpperCase() : ch,
}));
return characters;
}, [runner?.state, isLoading]);

const buttonText = getButtonTitle();
const buttonWidth = useMemo(() => {
// Base width for icon + padding
const baseWidth = 44;
return baseWidth + buttonText.length * 7;
}, [buttonText]);

function getTooltipText() {
switch (runner?.state) {
case RunState.STOPPED:
return 'Run your app';
case RunState.RUNNING:
return 'Stop Running your App & Clean Code';
default:
return '';
}
}

return (
<Button
variant="ghost"
className={cn(
'h-11 -my-2 border-transparent rounded-none w-20 px-3 gap-x-1.5 transition-colors duration-300 z-8 relative',
getExtraButtonClasses(),
)}
disabled={
isLoading ||
runner?.state === RunState.SETTING_UP ||
runner?.state === RunState.STOPPING
}
onClick={handleButtonClick}
<motion.div
layout="preserve-aspect"
animate={{ width: buttonWidth }}
transition={{
type: 'spring',
bounce: 0.2,
duration: 0.6,
stiffness: 150,
damping: 20,
}}
>
<div className="z-10">{renderIcon()}</div>
<span className="text-mini z-10 relative">
<AnimatePresence mode="popLayout">
{buttonCharacters.map((character) => (
<motion.span
key={character.id}
layoutId={character.id}
layout="position"
className="inline-block"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
type: 'spring',
bounce: 0.1,
duration: 0.4,
}}
>
{character.label}
</motion.span>
))}
</AnimatePresence>
</span>
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
className={cn(
'h-11 -my-2 border-transparent rounded-none px-3 gap-x-1.5 transition-colors duration-300 z-8 relative whitespace-nowrap overflow-hidden',
getExtraButtonClasses(),
)}
disabled={
isLoading ||
runner?.state === RunState.SETTING_UP ||
runner?.state === RunState.STOPPING
}
onClick={handleButtonClick}
>
<div className="z-10">{renderIcon()}</div>
<span className="text-mini z-10 relative">
<AnimatePresence mode="popLayout">
{buttonCharacters.map((character) => (
<motion.span
key={character.id}
layoutId={character.id}
layout="position"
className="inline-block"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
type: 'spring',
bounce: 0.1,
duration: 0.4,
}}
>
{character.label}
</motion.span>
))}
</AnimatePresence>
</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{getTooltipText()}</p>
</TooltipContent>
</Tooltip>
</motion.div>
);
});

Expand Down
Loading

0 comments on commit 963eec7

Please sign in to comment.