Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
'use client'

import { useEffect, useState } from 'react'
import { cn, Expandable, ExpandableContent } from '@sim/emcn'
import { ArrowRight, ChevronDown } from '@sim/emcn/icons'
import { cn } from '@sim/emcn'
import { ArrowRight } from '@sim/emcn/icons'
import Link from 'next/link'
import { HomeSection } from '@/components/home/home-section'
import { OAUTH_SEARCH_READ_SCOPE, oauthScopeSatisfies } from '@/lib/auth/oauth-provider'
import type { ResourceScope } from '@/lib/core/resource-scope'
import { organizationRoutes } from '@/lib/navigation/paths'
Expand Down Expand Up @@ -132,64 +133,29 @@ export function GetStarted() {
}

return (
<div className='group/suggested mx-auto mt-7 w-full max-w-chat'>
{/* Full width so the whole line toggles, not just the label and chevron. */}
<button
type='button'
onClick={handleToggleExpanded}
aria-expanded={expanded}
className='group/toggle flex w-full cursor-pointer items-center gap-2'
>
<span className='text-[var(--text-muted)] text-caption'>Get started</span>
{/*
* Revealed by hovering anywhere in the section — the group sits on the
* section wrapper rather than this row, so the rows below arm it just as
* the header does. Focus is keyed off the toggle instead, the only element
* here that can hold it, and matters because globals clear focus outlines.
* One transition covers the fade and the rotation so the two cannot drift
* apart. Mirrors the sidebar's section headers.
*/}
<ChevronDown
className={cn(
'size-[14px] shrink-0 text-[var(--text-icon)] opacity-0 transition-[opacity,transform] duration-150',
'group-hover/suggested:opacity-100 group-focus-visible/toggle:opacity-100',
!expanded && '-rotate-90'
)}
/>
</button>
<Expandable expanded={expanded}>
<ExpandableContent className={cn(!animationsEnabled && 'animate-none!')}>
{/* 6px, matching a sidebar section header to its first item — both headers
are an 18px box around 12px text, so equal padding reads as equal
distance. Padding an inner wrapper rather than the animated element:
`collapsible-up`/`-down` interpolate height alone, so a margin here
would hold its full value through the close and then vanish on unmount,
snapping the content below up. */}
<div className='flex flex-col pt-1.5'>
{steps.map((step, i) => {
const complete = completed[step.id]
return (
<Link
key={step.id}
href={hrefs[step.id]}
className={cn(ROW_CLASS, i > 0 && 'border-t')}
>
<StepMark complete={complete} />
<span
className={cn(
'flex-1 truncate text-sm',
complete ? 'text-[var(--brand-blue)]' : 'text-[var(--text-body)]'
)}
>
{step.label}
</span>
<ArrowRight className='size-[16px] shrink-0 text-[var(--text-icon)]' />
</Link>
)
})}
</div>
</ExpandableContent>
</Expandable>
</div>
<HomeSection
title='Get started'
expanded={expanded}
animationsEnabled={animationsEnabled}
onToggle={handleToggleExpanded}
>
{steps.map((step, i) => {
const complete = completed[step.id]
return (
<Link key={step.id} href={hrefs[step.id]} className={cn(ROW_CLASS, i > 0 && 'border-t')}>
<StepMark complete={complete} />
<span
className={cn(
'flex-1 truncate text-sm',
complete ? 'text-[var(--brand-blue)]' : 'text-[var(--text-body)]'
)}
>
{step.label}
</span>
<ArrowRight className='size-[16px] shrink-0 text-[var(--text-icon)]' />
</Link>
)
})}
</HomeSection>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import { useMemo, useState } from 'react'
import { INTEGRATION_METADATA } from '@sim/deployment-config/integration-metadata'
import { ArrowRight, ChevronDown, cn, Expandable, ExpandableContent, OverflowText } from '@sim/emcn'
import { ArrowRight, cn, OverflowText } from '@sim/emcn'
import { Table } from '@sim/emcn/icons'
import { stripVersionSuffix } from '@sim/utils/string'
import { useParams } from 'next/navigation'
import { usePostHog } from 'posthog-js/react'
import { HomeSection } from '@/components/home/home-section'
import { GmailIcon, SlackIcon } from '@/components/icons'
import {
resolveOAuthServiceForIntegration,
Expand Down Expand Up @@ -317,65 +318,36 @@ export function SuggestedActions({ onSelectPrompt }: SuggestedActionsProps) {
}

return (
<div className='group/suggested mx-auto mt-7 w-full max-w-chat'>
{/* Full width so the whole line toggles, not just the label and chevron. */}
<button
type='button'
onClick={handleToggleExpanded}
aria-expanded={expanded}
className='group/toggle flex w-full cursor-pointer items-center gap-2'
<>
<HomeSection
title='Suggested actions'
expanded={expanded}
animationsEnabled={animationsEnabled}
onToggle={handleToggleExpanded}
>
<span className='text-[var(--text-muted)] text-caption'>Suggested actions</span>
{/*
* Revealed by hovering anywhere in the section — the group sits on the
* section wrapper rather than this row, so the action rows below arm it just
* as the header does. Focus is keyed off the toggle instead, the only element
* here that can hold it, and matters because globals clear focus outlines.
* One transition covers the fade and the rotation so the two cannot drift
* apart. Mirrors the sidebar's section headers.
*/}
<ChevronDown
className={cn(
'size-[14px] shrink-0 text-[var(--text-icon)] opacity-0 transition-[opacity,transform] duration-150',
'group-hover/suggested:opacity-100 group-focus-visible/toggle:opacity-100',
!expanded && '-rotate-90'
)}
/>
</button>
<Expandable expanded={expanded}>
<ExpandableContent className={cn(!animationsEnabled && 'animate-none!')}>
{/* 6px, matching a sidebar section header to its first item — both headers
are an 18px box around 12px text, so equal padding reads as equal
distance. Padding an inner wrapper rather than the animated element:
`collapsible-up`/`-down` interpolate height alone, so a margin here
would hold its full value through the close and then vanish on unmount,
snapping the content below up. */}
<div className='flex flex-col pt-1.5'>
{actions.map((action, i) => {
const Icon = action.icon
return (
<button
key={action.id}
type='button'
onClick={() => handleSelect(action, i)}
className={cn(
'flex items-center gap-2 border-[var(--border)] px-2 py-2 text-left transition-colors hover-hover:bg-[var(--surface-5)]',
i > 0 && 'border-t'
)}
>
<BrandIcon icon={Icon} className='size-[16px] shrink-0' />
<OverflowText
label={action.label}
className='flex-1 text-[var(--text-body)] text-sm'
focusTarget='nearest-interactive'
/>
<ArrowRight className='size-[16px] shrink-0 text-[var(--text-icon)]' />
</button>
)
})}
</div>
</ExpandableContent>
</Expandable>
{actions.map((action, i) => {
const Icon = action.icon
return (
<button
key={action.id}
type='button'
onClick={() => handleSelect(action, i)}
className={cn(
'flex items-center gap-2 border-[var(--border)] px-2 py-2 text-left transition-colors hover-hover:bg-[var(--surface-5)]',
i > 0 && 'border-t'
)}
>
<BrandIcon icon={Icon} className='size-[16px] shrink-0' />
<OverflowText
label={action.label}
className='flex-1 text-[var(--text-body)] text-sm'
focusTarget='nearest-interactive'
/>
<ArrowRight className='size-[16px] shrink-0 text-[var(--text-icon)]' />
</button>
)
})}
</HomeSection>
{oauthTarget && workspaceId && (
<ConnectOAuthModal
mode='connect'
Expand All @@ -391,6 +363,6 @@ export function SuggestedActions({ onSelectPrompt }: SuggestedActionsProps) {
serviceIcon={oauthTarget.serviceIcon}
/>
)}
</div>
</>
)
}
50 changes: 50 additions & 0 deletions apps/sim/components/home/home-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { ReactNode } from 'react'
import { cn, Expandable, ExpandableContent } from '@sim/emcn'
import { ChevronDown } from '@sim/emcn/icons'

interface HomeSectionProps {
title: string
expanded: boolean
animationsEnabled: boolean
onToggle: () => void
children: ReactNode
}

/**
* Home-page section with caller-owned expansion and animation timing.
* Inner padding collapses with the content; margin would disappear on unmount and cause a jump.
* Section hover or toggle focus reveals the chevron, preserving keyboard feedback when global
* styles clear outlines. A shared transition keeps its fade and rotation synchronized.
*/
export function HomeSection({
title,
expanded,
animationsEnabled,
onToggle,
children,
}: HomeSectionProps) {
return (
<div className='group/suggested mx-auto mt-7 w-full max-w-chat'>
<button
type='button'
onClick={onToggle}
aria-expanded={expanded}
className='group/toggle flex w-full cursor-pointer items-center gap-2'
>
<span className='text-[var(--text-muted)] text-caption'>{title}</span>
<ChevronDown
className={cn(
'size-[14px] shrink-0 text-[var(--text-icon)] opacity-0 transition-[opacity,transform] duration-150',
'group-hover/suggested:opacity-100 group-focus-visible/toggle:opacity-100',
!expanded && '-rotate-90'
)}
/>
</button>
<Expandable expanded={expanded}>
<ExpandableContent className={cn(!animationsEnabled && 'animate-none!')}>
<div className='flex flex-col pt-1.5'>{children}</div>
</ExpandableContent>
</Expandable>
</div>
)
}
Loading