Skip to content

Commit 9b72b52

Browse files
waleedlatif1claude
andauthored
feat(blog): v0.5 release post (#2953)
* feat(blog): v0.5 post * improvement(blog): simplify title and remove code block header - Simplified blog title from "Introducing Sim Studio v0.5" to "Introducing Sim v0.5" - Removed language label header and copy button from code blocks for cleaner appearance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ack PR comments * small styling improvements * created system to create post-specific components * updated componnet * cache invalidation --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 1467862 commit 9b72b52

File tree

18 files changed

+458
-51
lines changed

18 files changed

+458
-51
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { ArrowLeft, ChevronLeft } from 'lucide-react'
5+
import Link from 'next/link'
6+
7+
export function BackLink() {
8+
const [isHovered, setIsHovered] = useState(false)
9+
10+
return (
11+
<Link
12+
href='/studio'
13+
className='group flex items-center gap-1 text-gray-600 text-sm hover:text-gray-900'
14+
onMouseEnter={() => setIsHovered(true)}
15+
onMouseLeave={() => setIsHovered(false)}
16+
>
17+
<span className='group-hover:-translate-x-0.5 inline-flex transition-transform duration-200'>
18+
{isHovered ? (
19+
<ArrowLeft className='h-4 w-4' aria-hidden='true' />
20+
) : (
21+
<ChevronLeft className='h-4 w-4' aria-hidden='true' />
22+
)}
23+
</span>
24+
Back to Sim Studio
25+
</Link>
26+
)
27+
}

apps/sim/app/(landing)/studio/[slug]/page.tsx

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/emcn'
55
import { FAQ } from '@/lib/blog/faq'
66
import { getAllPostMeta, getPostBySlug, getRelatedPosts } from '@/lib/blog/registry'
77
import { buildArticleJsonLd, buildBreadcrumbJsonLd, buildPostMetadata } from '@/lib/blog/seo'
8+
import { getBaseUrl } from '@/lib/core/utils/urls'
89
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
10+
import { BackLink } from '@/app/(landing)/studio/[slug]/back-link'
11+
import { ShareButton } from '@/app/(landing)/studio/[slug]/share-button'
912

1013
export async function generateStaticParams() {
1114
const posts = await getAllPostMeta()
@@ -48,9 +51,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string
4851
/>
4952
<header className='mx-auto max-w-[1450px] px-6 pt-8 sm:px-8 sm:pt-12 md:px-12 md:pt-16'>
5053
<div className='mb-6'>
51-
<Link href='/studio' className='text-gray-600 text-sm hover:text-gray-900'>
52-
← Back to Sim Studio
53-
</Link>
54+
<BackLink />
5455
</div>
5556
<div className='flex flex-col gap-8 md:flex-row md:gap-12'>
5657
<div className='w-full flex-shrink-0 md:w-[450px]'>
@@ -75,28 +76,31 @@ export default async function Page({ params }: { params: Promise<{ slug: string
7576
>
7677
{post.title}
7778
</h1>
78-
<div className='mt-4 flex items-center gap-3'>
79-
{(post.authors || [post.author]).map((a, idx) => (
80-
<div key={idx} className='flex items-center gap-2'>
81-
{a?.avatarUrl ? (
82-
<Avatar className='size-6'>
83-
<AvatarImage src={a.avatarUrl} alt={a.name} />
84-
<AvatarFallback>{a.name.slice(0, 2)}</AvatarFallback>
85-
</Avatar>
86-
) : null}
87-
<Link
88-
href={a?.url || '#'}
89-
target='_blank'
90-
rel='noopener noreferrer author'
91-
className='text-[14px] text-gray-600 leading-[1.5] hover:text-gray-900 sm:text-[16px]'
92-
itemProp='author'
93-
itemScope
94-
itemType='https://schema.org/Person'
95-
>
96-
<span itemProp='name'>{a?.name}</span>
97-
</Link>
98-
</div>
99-
))}
79+
<div className='mt-4 flex items-center justify-between'>
80+
<div className='flex items-center gap-3'>
81+
{(post.authors || [post.author]).map((a, idx) => (
82+
<div key={idx} className='flex items-center gap-2'>
83+
{a?.avatarUrl ? (
84+
<Avatar className='size-6'>
85+
<AvatarImage src={a.avatarUrl} alt={a.name} />
86+
<AvatarFallback>{a.name.slice(0, 2)}</AvatarFallback>
87+
</Avatar>
88+
) : null}
89+
<Link
90+
href={a?.url || '#'}
91+
target='_blank'
92+
rel='noopener noreferrer author'
93+
className='text-[14px] text-gray-600 leading-[1.5] hover:text-gray-900 sm:text-[16px]'
94+
itemProp='author'
95+
itemScope
96+
itemType='https://schema.org/Person'
97+
>
98+
<span itemProp='name'>{a?.name}</span>
99+
</Link>
100+
</div>
101+
))}
102+
</div>
103+
<ShareButton url={`${getBaseUrl()}/studio/${slug}`} title={post.title} />
100104
</div>
101105
</div>
102106
</div>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { Share2 } from 'lucide-react'
5+
import { Popover, PopoverContent, PopoverItem, PopoverTrigger } from '@/components/emcn'
6+
7+
interface ShareButtonProps {
8+
url: string
9+
title: string
10+
}
11+
12+
export function ShareButton({ url, title }: ShareButtonProps) {
13+
const [open, setOpen] = useState(false)
14+
const [copied, setCopied] = useState(false)
15+
16+
const handleCopyLink = async () => {
17+
try {
18+
await navigator.clipboard.writeText(url)
19+
setCopied(true)
20+
setTimeout(() => {
21+
setCopied(false)
22+
setOpen(false)
23+
}, 1000)
24+
} catch {
25+
setOpen(false)
26+
}
27+
}
28+
29+
const handleShareTwitter = () => {
30+
const tweetUrl = `https://twitter.com/intent/tweet?url=${encodeURIComponent(url)}&text=${encodeURIComponent(title)}`
31+
window.open(tweetUrl, '_blank', 'noopener,noreferrer')
32+
setOpen(false)
33+
}
34+
35+
const handleShareLinkedIn = () => {
36+
const linkedInUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`
37+
window.open(linkedInUrl, '_blank', 'noopener,noreferrer')
38+
setOpen(false)
39+
}
40+
41+
return (
42+
<Popover
43+
open={open}
44+
onOpenChange={setOpen}
45+
variant='secondary'
46+
size='sm'
47+
colorScheme='inverted'
48+
>
49+
<PopoverTrigger asChild>
50+
<button
51+
className='flex items-center gap-1.5 text-gray-600 text-sm hover:text-gray-900'
52+
aria-label='Share this post'
53+
>
54+
<Share2 className='h-4 w-4' />
55+
<span>Share</span>
56+
</button>
57+
</PopoverTrigger>
58+
<PopoverContent align='end' minWidth={140}>
59+
<PopoverItem onClick={handleCopyLink}>{copied ? 'Copied!' : 'Copy link'}</PopoverItem>
60+
<PopoverItem onClick={handleShareTwitter}>Share on X</PopoverItem>
61+
<PopoverItem onClick={handleShareLinkedIn}>Share on LinkedIn</PopoverItem>
62+
</PopoverContent>
63+
</Popover>
64+
)
65+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { DiffControlsDemo } from './components/diff-controls-demo'
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
5+
export function DiffControlsDemo() {
6+
const [rejectHover, setRejectHover] = useState(false)
7+
const [acceptHover, setAcceptHover] = useState(false)
8+
9+
return (
10+
<div style={{ display: 'flex', justifyContent: 'center', margin: '24px 0' }}>
11+
<div
12+
style={{
13+
position: 'relative',
14+
display: 'flex',
15+
height: '30px',
16+
overflow: 'hidden',
17+
borderRadius: '4px',
18+
isolation: 'isolate',
19+
}}
20+
>
21+
{/* Reject button */}
22+
<button
23+
onClick={() => {}}
24+
onMouseEnter={() => setRejectHover(true)}
25+
onMouseLeave={() => setRejectHover(false)}
26+
title='Reject changes'
27+
style={{
28+
position: 'relative',
29+
display: 'flex',
30+
height: '100%',
31+
alignItems: 'center',
32+
border: '1px solid #e0e0e0',
33+
backgroundColor: rejectHover ? '#f0f0f0' : '#f5f5f5',
34+
paddingRight: '20px',
35+
paddingLeft: '12px',
36+
fontWeight: 500,
37+
fontSize: '13px',
38+
color: rejectHover ? '#2d2d2d' : '#404040',
39+
clipPath: 'polygon(0 0, calc(100% + 10px) 0, 100% 100%, 0 100%)',
40+
borderRadius: '4px 0 0 4px',
41+
cursor: 'default',
42+
transition: 'color 150ms, background-color 150ms, border-color 150ms',
43+
}}
44+
>
45+
Reject
46+
</button>
47+
{/* Slanted divider - split gray/green */}
48+
<div
49+
style={{
50+
pointerEvents: 'none',
51+
position: 'absolute',
52+
top: 0,
53+
bottom: 0,
54+
left: '66px',
55+
width: '2px',
56+
transform: 'skewX(-18.4deg)',
57+
background: 'linear-gradient(to right, #e0e0e0 50%, #238458 50%)',
58+
zIndex: 10,
59+
}}
60+
/>
61+
{/* Accept button */}
62+
<button
63+
onClick={() => {}}
64+
onMouseEnter={() => setAcceptHover(true)}
65+
onMouseLeave={() => setAcceptHover(false)}
66+
title='Accept changes (⇧⌘⏎)'
67+
style={{
68+
position: 'relative',
69+
display: 'flex',
70+
height: '100%',
71+
alignItems: 'center',
72+
border: '1px solid rgba(0, 0, 0, 0.15)',
73+
backgroundColor: '#32bd7e',
74+
paddingRight: '12px',
75+
paddingLeft: '20px',
76+
fontWeight: 500,
77+
fontSize: '13px',
78+
color: '#ffffff',
79+
clipPath: 'polygon(10px 0, 100% 0, 100% 100%, 0 100%)',
80+
borderRadius: '0 4px 4px 0',
81+
marginLeft: '-10px',
82+
cursor: 'default',
83+
filter: acceptHover ? 'brightness(1.1)' : undefined,
84+
transition: 'background-color 150ms, border-color 150ms',
85+
}}
86+
>
87+
Accept
88+
<kbd
89+
style={{
90+
marginLeft: '8px',
91+
borderRadius: '4px',
92+
border: '1px solid rgba(255, 255, 255, 0.2)',
93+
backgroundColor: 'rgba(255, 255, 255, 0.1)',
94+
paddingLeft: '6px',
95+
paddingRight: '6px',
96+
paddingTop: '2px',
97+
paddingBottom: '2px',
98+
fontWeight: 500,
99+
fontFamily:
100+
'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
101+
fontSize: '10px',
102+
color: '#ffffff',
103+
}}
104+
>
105+
⇧⌘<span style={{ display: 'inline-block', transform: 'translateY(-1px)' }}></span>
106+
</kbd>
107+
</button>
108+
</div>
109+
</div>
110+
)
111+
}

0 commit comments

Comments
 (0)