Skip to content

Commit 8326b2a

Browse files
committed
Refactor the Timeline component to display actions on hover, support collapse functionality via ListHeader, and standardize item click handling
1 parent 0f69a12 commit 8326b2a

6 files changed

Lines changed: 128 additions & 84 deletions

File tree

packages/ui/src/components/editor/panel/ListHeader/ListHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const ListHeader: React.FC<ListHeader.Props> = (props) => {
2626
/>
2727
<span>{props.title}</span>
2828
</div>
29-
{props.actions && (
29+
{props.actions && !props.is_collapsed && (
3030
<div className={styles.header__right}>{props.actions}</div>
3131
)}
3232
</div>

packages/ui/src/components/editor/panel/Timeline/Timeline.module.scss

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,19 @@
66
.item {
77
display: flex;
88
position: relative;
9+
padding: 0 12px;
10+
cursor: pointer;
11+
12+
&:hover {
13+
background-color: var(--vscode-list-hoverBackground);
14+
15+
.item__actions {
16+
display: flex;
17+
}
18+
}
919

1020
&:last-child {
11-
.timelineItem__line {
21+
.item__line {
1222
display: none;
1323
}
1424

@@ -21,11 +31,11 @@
2131
font-size: 11px;
2232
color: var(--cwc-text-color-dimmed);
2333
white-space: nowrap;
24-
flex-shrink: 0;
2534
width: 26vw;
2635
text-align: right;
2736
overflow: hidden;
2837
text-overflow: ellipsis;
38+
padding-top: 2px;
2939
}
3040

3141
&__connector {
@@ -43,9 +53,6 @@
4353
align-items: center;
4454
justify-content: center;
4555
z-index: 1;
46-
background-color: var(--vscode-sideBar-background);
47-
cursor: pointer;
48-
transform: translateY(-2px);
4956
position: relative;
5057
color: var(--vscode-button-background);
5158
}
@@ -57,19 +64,9 @@
5764
border-radius: 50%;
5865
}
5966

60-
&__star-hover {
61-
position: absolute;
62-
opacity: 0;
63-
}
64-
65-
&__marker:hover {
66-
.item__dot {
67-
opacity: 0;
68-
}
69-
70-
.item__star-hover {
71-
opacity: 1;
72-
}
67+
&__star-prefix {
68+
margin-right: 4px;
69+
vertical-align: text-bottom;
7370
}
7471

7572
&__line {
@@ -82,15 +79,24 @@
8279
&__content {
8380
position: relative;
8481
display: flex;
82+
flex: 1;
83+
min-width: 0;
8584
flex-direction: column;
8685
gap: 2px;
87-
margin-bottom: 12px;
86+
padding: 1px 0;
8887
overflow: hidden;
8988

90-
&__label {
91-
cursor: pointer;
92-
&:hover {
93-
text-decoration: underline;
89+
&__title-bar {
90+
display: flex;
91+
justify-content: space-between;
92+
align-items: center;
93+
min-height: 20px;
94+
95+
> span:first-child {
96+
min-width: 0;
97+
overflow: hidden;
98+
text-overflow: ellipsis;
99+
white-space: nowrap;
94100
}
95101
}
96102

@@ -102,6 +108,14 @@
102108
-webkit-box-orient: vertical;
103109
overflow: hidden;
104110
word-wrap: break-word;
111+
margin-bottom: 12px;
105112
}
106113
}
114+
115+
&__actions {
116+
display: none;
117+
align-items: center;
118+
margin-left: 8px;
119+
flex-shrink: 0;
120+
}
107121
}

packages/ui/src/components/editor/panel/Timeline/Timeline.stories.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const Default = () => {
6161
<Timeline
6262
items={items}
6363
on_toggle_starred={handle_toggle_starred}
64-
on_label_click={(id) => alert(`Restore checkpoint: ${id}`)}
64+
on_item_click={(id) => alert(`Restore checkpoint: ${id}`)}
6565
/>
6666
</div>
6767
)
@@ -77,7 +77,7 @@ export const SingleItem = () => (
7777
<Timeline
7878
items={[initialItems[0]]}
7979
on_toggle_starred={() => {}}
80-
on_label_click={(id) => alert(`Restore checkpoint: ${id}`)}
80+
on_item_click={(id) => alert(`Restore checkpoint: ${id}`)}
8181
/>
8282
</div>
8383
)
@@ -99,7 +99,7 @@ export const NoDescription = () => (
9999
}
100100
]}
101101
on_toggle_starred={() => {}}
102-
on_label_click={(id) => alert(`Restore checkpoint: ${id}`)}
102+
on_item_click={(id) => alert(`Restore checkpoint: ${id}`)}
103103
/>
104104
</div>
105105
)

packages/ui/src/components/editor/panel/Timeline/Timeline.tsx

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import dayjs from 'dayjs'
44
import relativeTime from 'dayjs/plugin/relativeTime'
55
import localizedFormat from 'dayjs/plugin/localizedFormat'
66
import { use_periodic_re_render } from '../../../../hooks/use-periodic-re-render'
7+
import { IconButton } from '../IconButton/IconButton'
78

89
dayjs.extend(relativeTime)
910
dayjs.extend(localizedFormat)
1011

1112
export type TimelineItemProps = {
12-
id: string | number
13+
id: number
1314
timestamp: number
1415
label: string
1516
description?: string
@@ -18,75 +19,86 @@ export type TimelineItemProps = {
1819

1920
type Props = {
2021
items: TimelineItemProps[]
21-
on_toggle_starred: (id: string | number) => void
22-
on_label_click: (id: string | number) => void
23-
on_create_click?: () => void
22+
on_toggle_starred: (id: number) => void
23+
on_item_click: (id: number) => void
24+
on_delete?: (id: number) => void
25+
on_edit?: (id: number) => void
2426
}
2527

2628
export const Timeline: React.FC<Props> = ({
2729
items,
2830
on_toggle_starred,
29-
on_label_click,
30-
on_create_click
31+
on_item_click,
32+
on_delete,
33+
on_edit
3134
}) => {
3235
// Re-render every minute to update the relative time of the timeline items.
3336
use_periodic_re_render(60 * 1000)
3437

3538
return (
3639
<div className={styles.timeline}>
37-
{on_create_click && (
38-
<div className={styles.item}>
39-
<div className={styles.item__time} />
40-
<div className={styles.item__connector}>
41-
<div className={styles.item__line} />
42-
</div>
43-
<div className={styles.item__content}>
44-
<div
45-
className={styles.item__content__label}
46-
onClick={on_create_click}
47-
>
48-
New checkpoint...
49-
</div>
50-
</div>
51-
</div>
52-
)}
5340
{items.map((item) => (
54-
<div key={item.id} className={styles.item}>
41+
<div
42+
key={item.id}
43+
className={styles.item}
44+
onClick={() => on_item_click(item.id)}
45+
>
5546
<div
5647
className={styles.item__time}
5748
title={dayjs(item.timestamp).format('LLLL')}
5849
>
5950
{dayjs(item.timestamp).fromNow()}
6051
</div>
6152
<div className={styles.item__connector}>
62-
<div
63-
className={styles.item__marker}
64-
onClick={() => on_toggle_starred?.(item.id)}
65-
>
66-
{item.is_starred ? (
67-
<span className={cn('codicon', 'codicon-star-full')} />
68-
) : (
69-
<>
70-
<div className={styles.item__dot} />
53+
<div className={styles.item__marker}>
54+
<div className={styles.item__dot} />
55+
</div>
56+
{item.description && <div className={styles.item__line} />}
57+
</div>
58+
<div className={styles.item__content}>
59+
<div className={styles['item__content__title-bar']}>
60+
<span title="Restore checkpoint">
61+
{item.is_starred && (
7162
<span
7263
className={cn(
7364
'codicon',
74-
'codicon-star',
75-
styles['item__star-hover']
65+
'codicon-star-full',
66+
styles['item__star-prefix']
7667
)}
7768
/>
78-
</>
79-
)}
80-
</div>
81-
<div className={styles.item__line} />
82-
</div>
83-
<div className={styles.item__content}>
84-
<div
85-
className={styles.item__content__label}
86-
onClick={() => on_label_click(item.id)}
87-
title="Restore checkpoint"
88-
>
89-
{item.label}
69+
)}
70+
{item.label}
71+
</span>
72+
<div className={styles.item__actions}>
73+
<IconButton
74+
codicon_icon={item.is_starred ? 'star-full' : 'star-empty'}
75+
title={item.is_starred ? 'Unstar' : 'Star'}
76+
on_click={(e) => {
77+
e.stopPropagation()
78+
on_toggle_starred?.(item.id)
79+
}}
80+
/>
81+
{on_edit && (
82+
<IconButton
83+
codicon_icon="edit"
84+
title="Edit description"
85+
on_click={(e) => {
86+
e.stopPropagation()
87+
on_edit(item.id)
88+
}}
89+
/>
90+
)}
91+
{on_delete && (
92+
<IconButton
93+
codicon_icon="trash"
94+
title="Delete"
95+
on_click={(e) => {
96+
e.stopPropagation()
97+
on_delete(item.id)
98+
}}
99+
/>
100+
)}
101+
</div>
90102
</div>
91103
{item.description && (
92104
<div

packages/vscode/src/views/panel/frontend/Home/Home.module.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
justify-content: space-between;
7070
gap: 24px;
7171
height: 100%;
72-
padding: 4px 12px 0 12px;
72+
padding-top: 4px;
7373
}
7474

7575
.inner {
@@ -78,17 +78,17 @@
7878
width: 100%;
7979

8080
&__responses {
81-
margin: 0 -12px;
81+
margin: 0;
8282
}
8383

8484
&__mode {
8585
display: flex;
86-
width: 100%;
8786
position: relative;
8887
background-color: var(--vscode-sideBar-background);
8988
border-radius: 10px;
9089
border: 1px solid var(--cwc-border-color);
9190
overflow: hidden;
91+
margin: 0 12px;
9292

9393
> button {
9494
flex: 1;

packages/vscode/src/views/panel/frontend/Home/Home.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import { Checkpoint, FrontendMessage } from '@/views/panel/types/messages'
88
import { Responses as UiResponses } from '@ui/components/editor/panel/Responses'
99
import { ResponseHistoryItem } from '@shared/types/response-history-item'
1010
import { Separator } from '@ui/components/editor/panel/Separator'
11+
import { ListHeader } from '@ui/components/editor/panel/ListHeader'
1112
import { use_translation } from '@/views/i18n/use-translation'
13+
import { useState } from 'react'
14+
import { IconButton } from '@ui/components/editor/panel/IconButton'
1215

1316
type Props = {
1417
vscode: any
@@ -28,6 +31,7 @@ type Props = {
2831

2932
export const Home: React.FC<Props> = (props) => {
3033
const { t } = use_translation()
34+
const [is_timeline_collapsed, set_is_timeline_collapsed] = useState(false)
3135

3236
const handle_settings_click = () => {
3337
post_message(props.vscode, {
@@ -98,8 +102,25 @@ export const Home: React.FC<Props> = (props) => {
98102
on_click={props.on_api_calls_click}
99103
/>
100104
</div>
101-
<Separator height={16} />
102-
<div className={styles.inner__timeline}>
105+
<Separator height={8} />
106+
<ListHeader
107+
title="Timeline"
108+
is_collapsed={is_timeline_collapsed}
109+
on_toggle_collapsed={() =>
110+
set_is_timeline_collapsed(!is_timeline_collapsed)
111+
}
112+
actions={
113+
<IconButton
114+
codicon_icon="add"
115+
title="New checkpoint"
116+
on_click={(e) => {
117+
e.stopPropagation()
118+
handle_create_checkpoint_click()
119+
}}
120+
/>
121+
}
122+
/>
123+
{!is_timeline_collapsed && (
103124
<Timeline
104125
items={props.checkpoints.map((c) => ({
105126
id: c.timestamp,
@@ -109,14 +130,11 @@ export const Home: React.FC<Props> = (props) => {
109130
is_starred: c.is_starred
110131
}))}
111132
on_toggle_starred={(id) =>
112-
props.on_toggle_checkpoint_starred(id as number)
133+
props.on_toggle_checkpoint_starred(id)
113134
}
114-
on_label_click={(id) =>
115-
props.on_restore_checkpoint(id as number)
116-
}
117-
on_create_click={handle_create_checkpoint_click}
135+
on_item_click={(id) => props.on_restore_checkpoint(id)}
118136
/>
119-
</div>
137+
)}
120138
</div>
121139

122140
<div className={styles.bottom}>

0 commit comments

Comments
 (0)