Skip to content

Commit 3833aa5

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
improvement(ui): share expandable home sections
1 parent ea31ca7 commit 3833aa5

3 files changed

Lines changed: 111 additions & 121 deletions

File tree

‎apps/sim/app/o/[organizationId]/home/components/get-started/get-started.tsx‎

Lines changed: 27 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
'use client'
22

33
import { useEffect, useState } from 'react'
4-
import { cn, Expandable, ExpandableContent } from '@sim/emcn'
5-
import { ArrowRight, ChevronDown } from '@sim/emcn/icons'
4+
import { cn } from '@sim/emcn'
5+
import { ArrowRight } from '@sim/emcn/icons'
66
import Link from 'next/link'
7+
import { HomeSection } from '@/components/home/home-section'
78
import { OAUTH_SEARCH_READ_SCOPE, oauthScopeSatisfies } from '@/lib/auth/oauth-provider'
89
import type { ResourceScope } from '@/lib/core/resource-scope'
910
import { organizationRoutes } from '@/lib/navigation/paths'
@@ -132,64 +133,29 @@ export function GetStarted() {
132133
}
133134

134135
return (
135-
<div className='group/suggested mx-auto mt-7 w-full max-w-chat'>
136-
{/* Full width so the whole line toggles, not just the label and chevron. */}
137-
<button
138-
type='button'
139-
onClick={handleToggleExpanded}
140-
aria-expanded={expanded}
141-
className='group/toggle flex w-full cursor-pointer items-center gap-2'
142-
>
143-
<span className='text-[var(--text-muted)] text-caption'>Get started</span>
144-
{/*
145-
* Revealed by hovering anywhere in the section — the group sits on the
146-
* section wrapper rather than this row, so the rows below arm it just as
147-
* the header does. Focus is keyed off the toggle instead, the only element
148-
* here that can hold it, and matters because globals clear focus outlines.
149-
* One transition covers the fade and the rotation so the two cannot drift
150-
* apart. Mirrors the sidebar's section headers.
151-
*/}
152-
<ChevronDown
153-
className={cn(
154-
'size-[14px] shrink-0 text-[var(--text-icon)] opacity-0 transition-[opacity,transform] duration-150',
155-
'group-hover/suggested:opacity-100 group-focus-visible/toggle:opacity-100',
156-
!expanded && '-rotate-90'
157-
)}
158-
/>
159-
</button>
160-
<Expandable expanded={expanded}>
161-
<ExpandableContent className={cn(!animationsEnabled && 'animate-none!')}>
162-
{/* 6px, matching a sidebar section header to its first item — both headers
163-
are an 18px box around 12px text, so equal padding reads as equal
164-
distance. Padding an inner wrapper rather than the animated element:
165-
`collapsible-up`/`-down` interpolate height alone, so a margin here
166-
would hold its full value through the close and then vanish on unmount,
167-
snapping the content below up. */}
168-
<div className='flex flex-col pt-1.5'>
169-
{steps.map((step, i) => {
170-
const complete = completed[step.id]
171-
return (
172-
<Link
173-
key={step.id}
174-
href={hrefs[step.id]}
175-
className={cn(ROW_CLASS, i > 0 && 'border-t')}
176-
>
177-
<StepMark complete={complete} />
178-
<span
179-
className={cn(
180-
'flex-1 truncate text-sm',
181-
complete ? 'text-[var(--brand-blue)]' : 'text-[var(--text-body)]'
182-
)}
183-
>
184-
{step.label}
185-
</span>
186-
<ArrowRight className='size-[16px] shrink-0 text-[var(--text-icon)]' />
187-
</Link>
188-
)
189-
})}
190-
</div>
191-
</ExpandableContent>
192-
</Expandable>
193-
</div>
136+
<HomeSection
137+
title='Get started'
138+
expanded={expanded}
139+
animationsEnabled={animationsEnabled}
140+
onToggle={handleToggleExpanded}
141+
>
142+
{steps.map((step, i) => {
143+
const complete = completed[step.id]
144+
return (
145+
<Link key={step.id} href={hrefs[step.id]} className={cn(ROW_CLASS, i > 0 && 'border-t')}>
146+
<StepMark complete={complete} />
147+
<span
148+
className={cn(
149+
'flex-1 truncate text-sm',
150+
complete ? 'text-[var(--brand-blue)]' : 'text-[var(--text-body)]'
151+
)}
152+
>
153+
{step.label}
154+
</span>
155+
<ArrowRight className='size-[16px] shrink-0 text-[var(--text-icon)]' />
156+
</Link>
157+
)
158+
})}
159+
</HomeSection>
194160
)
195161
}

‎apps/sim/app/workspace/[workspaceId]/home/components/suggested-actions/suggested-actions.tsx‎

Lines changed: 32 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
import { useMemo, useState } from 'react'
44
import { INTEGRATION_METADATA } from '@sim/deployment-config/integration-metadata'
5-
import { ArrowRight, ChevronDown, cn, Expandable, ExpandableContent, OverflowText } from '@sim/emcn'
5+
import { ArrowRight, cn, OverflowText } from '@sim/emcn'
66
import { Table } from '@sim/emcn/icons'
77
import { stripVersionSuffix } from '@sim/utils/string'
88
import { useParams } from 'next/navigation'
99
import { usePostHog } from 'posthog-js/react'
10+
import { HomeSection } from '@/components/home/home-section'
1011
import { GmailIcon, SlackIcon } from '@/components/icons'
1112
import {
1213
resolveOAuthServiceForIntegration,
@@ -317,65 +318,36 @@ export function SuggestedActions({ onSelectPrompt }: SuggestedActionsProps) {
317318
}
318319

319320
return (
320-
<div className='group/suggested mx-auto mt-7 w-full max-w-chat'>
321-
{/* Full width so the whole line toggles, not just the label and chevron. */}
322-
<button
323-
type='button'
324-
onClick={handleToggleExpanded}
325-
aria-expanded={expanded}
326-
className='group/toggle flex w-full cursor-pointer items-center gap-2'
321+
<>
322+
<HomeSection
323+
title='Suggested actions'
324+
expanded={expanded}
325+
animationsEnabled={animationsEnabled}
326+
onToggle={handleToggleExpanded}
327327
>
328-
<span className='text-[var(--text-muted)] text-caption'>Suggested actions</span>
329-
{/*
330-
* Revealed by hovering anywhere in the section — the group sits on the
331-
* section wrapper rather than this row, so the action rows below arm it just
332-
* as the header does. Focus is keyed off the toggle instead, the only element
333-
* here that can hold it, and matters because globals clear focus outlines.
334-
* One transition covers the fade and the rotation so the two cannot drift
335-
* apart. Mirrors the sidebar's section headers.
336-
*/}
337-
<ChevronDown
338-
className={cn(
339-
'size-[14px] shrink-0 text-[var(--text-icon)] opacity-0 transition-[opacity,transform] duration-150',
340-
'group-hover/suggested:opacity-100 group-focus-visible/toggle:opacity-100',
341-
!expanded && '-rotate-90'
342-
)}
343-
/>
344-
</button>
345-
<Expandable expanded={expanded}>
346-
<ExpandableContent className={cn(!animationsEnabled && 'animate-none!')}>
347-
{/* 6px, matching a sidebar section header to its first item — both headers
348-
are an 18px box around 12px text, so equal padding reads as equal
349-
distance. Padding an inner wrapper rather than the animated element:
350-
`collapsible-up`/`-down` interpolate height alone, so a margin here
351-
would hold its full value through the close and then vanish on unmount,
352-
snapping the content below up. */}
353-
<div className='flex flex-col pt-1.5'>
354-
{actions.map((action, i) => {
355-
const Icon = action.icon
356-
return (
357-
<button
358-
key={action.id}
359-
type='button'
360-
onClick={() => handleSelect(action, i)}
361-
className={cn(
362-
'flex items-center gap-2 border-[var(--border)] px-2 py-2 text-left transition-colors hover-hover:bg-[var(--surface-5)]',
363-
i > 0 && 'border-t'
364-
)}
365-
>
366-
<BrandIcon icon={Icon} className='size-[16px] shrink-0' />
367-
<OverflowText
368-
label={action.label}
369-
className='flex-1 text-[var(--text-body)] text-sm'
370-
focusTarget='nearest-interactive'
371-
/>
372-
<ArrowRight className='size-[16px] shrink-0 text-[var(--text-icon)]' />
373-
</button>
374-
)
375-
})}
376-
</div>
377-
</ExpandableContent>
378-
</Expandable>
328+
{actions.map((action, i) => {
329+
const Icon = action.icon
330+
return (
331+
<button
332+
key={action.id}
333+
type='button'
334+
onClick={() => handleSelect(action, i)}
335+
className={cn(
336+
'flex items-center gap-2 border-[var(--border)] px-2 py-2 text-left transition-colors hover-hover:bg-[var(--surface-5)]',
337+
i > 0 && 'border-t'
338+
)}
339+
>
340+
<BrandIcon icon={Icon} className='size-[16px] shrink-0' />
341+
<OverflowText
342+
label={action.label}
343+
className='flex-1 text-[var(--text-body)] text-sm'
344+
focusTarget='nearest-interactive'
345+
/>
346+
<ArrowRight className='size-[16px] shrink-0 text-[var(--text-icon)]' />
347+
</button>
348+
)
349+
})}
350+
</HomeSection>
379351
{oauthTarget && workspaceId && (
380352
<ConnectOAuthModal
381353
mode='connect'
@@ -391,6 +363,6 @@ export function SuggestedActions({ onSelectPrompt }: SuggestedActionsProps) {
391363
serviceIcon={oauthTarget.serviceIcon}
392364
/>
393365
)}
394-
</div>
366+
</>
395367
)
396368
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use client'
2+
3+
import type { ReactNode } from 'react'
4+
import { cn, Expandable, ExpandableContent } from '@sim/emcn'
5+
import { ChevronDown } from '@sim/emcn/icons'
6+
7+
interface HomeSectionProps {
8+
title: string
9+
expanded: boolean
10+
animationsEnabled: boolean
11+
onToggle: () => void
12+
children: ReactNode
13+
}
14+
15+
/**
16+
* Home-page section with caller-owned expansion and animation timing.
17+
* Inner padding collapses with the content; margin would disappear on unmount and cause a jump.
18+
* Section hover or toggle focus reveals the chevron, preserving keyboard feedback when global
19+
* styles clear outlines. A shared transition keeps its fade and rotation synchronized.
20+
*/
21+
export function HomeSection({
22+
title,
23+
expanded,
24+
animationsEnabled,
25+
onToggle,
26+
children,
27+
}: HomeSectionProps) {
28+
return (
29+
<div className='group/suggested mx-auto mt-7 w-full max-w-chat'>
30+
<button
31+
type='button'
32+
onClick={onToggle}
33+
aria-expanded={expanded}
34+
className='group/toggle flex w-full cursor-pointer items-center gap-2'
35+
>
36+
<span className='text-[var(--text-muted)] text-caption'>{title}</span>
37+
<ChevronDown
38+
className={cn(
39+
'size-[14px] shrink-0 text-[var(--text-icon)] opacity-0 transition-[opacity,transform] duration-150',
40+
'group-hover/suggested:opacity-100 group-focus-visible/toggle:opacity-100',
41+
!expanded && '-rotate-90'
42+
)}
43+
/>
44+
</button>
45+
<Expandable expanded={expanded}>
46+
<ExpandableContent className={cn(!animationsEnabled && 'animate-none!')}>
47+
<div className='flex flex-col pt-1.5'>{children}</div>
48+
</ExpandableContent>
49+
</Expandable>
50+
</div>
51+
)
52+
}

0 commit comments

Comments
 (0)