Skip to content

Commit e9a2ce4

Browse files
authored
App Panel, Installed Apps, App Details (onlook-dev#1608)
* Apps panel base created * Detail Panel base added with Open State * Updated panels to handle appended side panels The previous set up only allowed us to append side panels to the LayersPanel index.tsx (if we want to use the blur effect). Using the same backdrop blur on a child when the parent has the blur as well wouldn't work. The new setup lets us append side panels to the specific tab index.tsx and maintain the same background blur. Little confusing - can explain this more if needed. * Updated minor styling * Updated styling & Organized components * Updated tools accordion styles * Updated Styling * Styling updates * Updated spacing and fonts * Fixed panel width problems * Consolidated the AppCard & FeaturedAppCard * Installed tab initial base - needs styling * AppCard Hover/Active effects * Installed apps - base card styled * Updated InstalledCard error states * Updated settings style * Updated InstallCard Error * Updated status indicators * Detail Panel installed error bar * Authenticate by link added * Updated button styles * Added various states * Updated hidden toolbar * Add missing translations
1 parent daa2242 commit e9a2ce4

File tree

13 files changed

+1363
-9
lines changed

13 files changed

+1363
-9
lines changed

apps/studio/src/locales/en/translation.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,8 @@
245245
"name": "Windows",
246246
"emptyState": "Select a window to edit its settings"
247247
},
248-
"brand": "Brand"
248+
"brand": "Brand",
249+
"apps": "Apps"
249250
}
250251
}
251252
},

apps/studio/src/locales/ja/translation.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@
150150
"name": "ウィンドウ",
151151
"emptyState": "編集するウィンドウを選択してください"
152152
},
153-
"brand": "ブランド"
153+
"brand": "ブランド",
154+
"apps": "アプリ"
154155
}
155156
}
156157
},

apps/studio/src/locales/kr/translation.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,8 @@
245245
"name": "윈도우",
246246
"emptyState": "설정을 편집할 창(윈도우)을 선택하세요"
247247
},
248-
"brand": "브랜드"
248+
"brand": "브랜드",
249+
"apps": ""
249250
}
250251
}
251252
},

apps/studio/src/locales/zh/translation.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,8 @@
245245
"name": "窗口",
246246
"emptyState": "选择一个窗口以编辑其设置"
247247
},
248-
"brand": "品牌"
248+
"brand": "品牌",
249+
"apps": "应用"
249250
}
250251
}
251252
},
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import React from 'react';
2+
import type { AppData } from './index';
3+
import { cn } from '@onlook/ui/utils';
4+
5+
// Company brand colors
6+
const BRAND_COLORS: Record<string, string> = {
7+
Stripe: '#635BFF',
8+
MongoDB: '#47A248',
9+
Figma: '#F24E1E',
10+
GitHub: '#181717',
11+
Slack: '#4A154B',
12+
Notion: '#151515',
13+
Salesforce: '#00A1E0',
14+
Airtable: '#18BFFF',
15+
Twilio: '#F22F46',
16+
};
17+
18+
export interface AppCardProps {
19+
app: AppData;
20+
onClick: (app: AppData) => void;
21+
className?: string;
22+
isActive?: boolean;
23+
anyAppActive?: boolean;
24+
isHovered?: boolean;
25+
anyCardHovered?: boolean;
26+
listId?: string;
27+
hideDivider?: boolean;
28+
onMouseEnter?: () => void;
29+
onMouseLeave?: () => void;
30+
}
31+
32+
const AppCard: React.FC<AppCardProps> = ({
33+
app,
34+
onClick,
35+
className,
36+
isActive = false,
37+
anyAppActive = false,
38+
isHovered = false,
39+
anyCardHovered = false,
40+
listId,
41+
hideDivider = false,
42+
onMouseEnter,
43+
onMouseLeave,
44+
}) => {
45+
// Never dim active cards or hovered cards
46+
// Only dim cards that are neither active nor hovered when either:
47+
// - There's an active card in the list, or
48+
// - There's a hovered card in the list
49+
const isDimmed = !isActive && !isHovered && (anyAppActive || anyCardHovered);
50+
51+
return (
52+
<button
53+
className={cn(
54+
'group w-full text-left flex flex-col cursor-pointer flex-grow relative overflow-hidden',
55+
'transition-all duration-100',
56+
isActive && 'bg-background-secondary/50',
57+
className,
58+
)}
59+
style={{ opacity: isDimmed ? 0.65 : 1 }}
60+
onClick={() => onClick(app)}
61+
onMouseEnter={onMouseEnter}
62+
onMouseLeave={onMouseLeave}
63+
>
64+
{/* Animated background that scales up on hover */}
65+
<div className="absolute inset-0 bg-background-secondary/50 opacity-0 transition-all duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] pointer-events-none transform scale-90 group-hover:scale-100 group-hover:opacity-100"></div>
66+
67+
<div className="w-full relative">
68+
<div className="flex items-center w-full">
69+
<div
70+
className={cn(
71+
'flex-shrink-0 w-9 h-9 flex items-center justify-center rounded-md overflow-hidden mr-3 border',
72+
'transition-all duration-100',
73+
isActive ? 'border-white/20' : 'border-white/[0.07]',
74+
'group-hover:border-white/20',
75+
)}
76+
style={{ backgroundColor: BRAND_COLORS[app.name] || '#ffffff' }}
77+
>
78+
{app.icon ? (
79+
<img
80+
src={app.icon}
81+
alt={`${app.name} logo`}
82+
className="w-5 h-5 object-contain"
83+
style={{ filter: 'brightness(0) invert(1)' }} // Make SVG white
84+
/>
85+
) : (
86+
<div className="w-full h-full flex items-center justify-center text-white text-xl font-semibold">
87+
{app.name.charAt(0)}
88+
</div>
89+
)}
90+
</div>
91+
<div className="flex-1 min-w-0 flex items-center">
92+
<h3 className="text-base font-normal text-white">{app.name}</h3>
93+
</div>
94+
</div>
95+
<p className="text-sm text-gray-400 mt-2 line-clamp-2">{app.description}</p>
96+
</div>
97+
98+
{/* Bottom divider line - only shown if hideDivider is false */}
99+
{!hideDivider && (
100+
<div className="absolute bottom-0 left-4 right-4 h-[0.5px] bg-border"></div>
101+
)}
102+
</button>
103+
);
104+
};
105+
106+
export default AppCard;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import React, { useState } from 'react';
2+
import { Icons } from '@onlook/ui/icons';
3+
import { cn } from '@onlook/ui/utils';
4+
5+
// Import the AppIcon component or create a placeholder
6+
interface AppIconProps {
7+
size?: 'sm' | 'md' | 'lg';
8+
}
9+
10+
const AppIcon: React.FC<AppIconProps> = ({ size = 'md' }) => {
11+
const sizeClasses = {
12+
sm: 'w-6 h-6 text-xl',
13+
md: 'w-8 h-8 text-2xl',
14+
lg: 'w-[60px] h-[60px] text-[32px]',
15+
};
16+
17+
return (
18+
<div
19+
className={`flex items-center justify-center rounded-md bg-background-secondary text-white font-semibold border border-white/[0.07] ${sizeClasses[size]}`}
20+
></div>
21+
);
22+
};
23+
24+
export interface ToolInputProps {
25+
label: string;
26+
type: string;
27+
description: string;
28+
}
29+
30+
export interface ToolProps {
31+
name: string;
32+
description?: string;
33+
inputs?: ToolInputProps[];
34+
icon?: React.ReactNode;
35+
}
36+
37+
const ToolCard: React.FC<ToolProps> = ({ name, description, inputs, icon }) => {
38+
const [isExpanded, setIsExpanded] = useState(false);
39+
40+
return (
41+
<div className="border-b border-border last:border-b-0">
42+
<div
43+
className="flex items-center py-3 px-3 cursor-pointer"
44+
onClick={() => setIsExpanded(!isExpanded)}
45+
>
46+
<div className="mr-3">{icon || <AppIcon size="sm" />}</div>
47+
<div className="flex-1">
48+
<h3 className="text-base font-normal text-white">{name}</h3>
49+
</div>
50+
<div>
51+
<Icons.ChevronDown
52+
className={cn(
53+
'h-5 w-5 text-gray-400 transition-transform',
54+
isExpanded ? 'transform rotate-180' : '',
55+
)}
56+
/>
57+
</div>
58+
</div>
59+
60+
{isExpanded && (
61+
<div className="px-3 pb-5">
62+
{description && (
63+
<p className="text-sm font-normal text-muted-foreground mb-4 ml-[42px]">
64+
{description}
65+
</p>
66+
)}
67+
68+
{inputs && inputs.length > 0 && (
69+
<div className="rounded-md overflow-hidden border border-border">
70+
<div className="bg-background-secondary/60 px-3 py-2">
71+
<div className="flex text-sm font-normal">
72+
<div className="w-1/3 text-gray-400">Input</div>
73+
<div className="w-2/3 text-gray-400">Description</div>
74+
</div>
75+
</div>
76+
77+
{inputs.map((input, index) => (
78+
<div key={index} className="border-t border-border">
79+
<div className="flex px-3 py-3">
80+
<div className="w-1/3 flex flex-col gap-[2px]">
81+
<div className="text-white text-sm">{input.label}</div>
82+
<div className="text-muted-foreground text-xs">
83+
{input.type}
84+
</div>
85+
</div>
86+
<div className="w-2/3 text-sm text-white">
87+
{input.description}
88+
</div>
89+
</div>
90+
</div>
91+
))}
92+
</div>
93+
)}
94+
</div>
95+
)}
96+
</div>
97+
);
98+
};
99+
100+
export default ToolCard;

0 commit comments

Comments
 (0)