Skip to content

Commit 3aa603f

Browse files
BillLeoutsakosvl346Bill Leoutsakos
andauthored
improvement(ui): share expandable home sections (#8067)
* improvement(ui): share expandable home sections * fix(ui): inherit home section client boundary from callers --------- Co-authored-by: Bill Leoutsakos <billleoutsakos@Mac.localdomain>
1 parent dbc5850 commit 3aa603f

3 files changed

Lines changed: 109 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: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { ReactNode } from 'react'
2+
import { cn, Expandable, ExpandableContent } from '@sim/emcn'
3+
import { ChevronDown } from '@sim/emcn/icons'
4+
5+
interface HomeSectionProps {
6+
title: string
7+
expanded: boolean
8+
animationsEnabled: boolean
9+
onToggle: () => void
10+
children: ReactNode
11+
}
12+
13+
/**
14+
* Home-page section with caller-owned expansion and animation timing.
15+
* Inner padding collapses with the content; margin would disappear on unmount and cause a jump.
16+
* Section hover or toggle focus reveals the chevron, preserving keyboard feedback when global
17+
* styles clear outlines. A shared transition keeps its fade and rotation synchronized.
18+
*/
19+
export function HomeSection({
20+
title,
21+
expanded,
22+
animationsEnabled,
23+
onToggle,
24+
children,
25+
}: HomeSectionProps) {
26+
return (
27+
<div className='group/suggested mx-auto mt-7 w-full max-w-chat'>
28+
<button
29+
type='button'
30+
onClick={onToggle}
31+
aria-expanded={expanded}
32+
className='group/toggle flex w-full cursor-pointer items-center gap-2'
33+
>
34+
<span className='text-[var(--text-muted)] text-caption'>{title}</span>
35+
<ChevronDown
36+
className={cn(
37+
'size-[14px] shrink-0 text-[var(--text-icon)] opacity-0 transition-[opacity,transform] duration-150',
38+
'group-hover/suggested:opacity-100 group-focus-visible/toggle:opacity-100',
39+
!expanded && '-rotate-90'
40+
)}
41+
/>
42+
</button>
43+
<Expandable expanded={expanded}>
44+
<ExpandableContent className={cn(!animationsEnabled && 'animate-none!')}>
45+
<div className='flex flex-col pt-1.5'>{children}</div>
46+
</ExpandableContent>
47+
</Expandable>
48+
</div>
49+
)
50+
}

0 commit comments

Comments
 (0)