Skip to content

Commit b4a6e5a

Browse files
ergunshDevtools-frontend LUCI CQ
authored andcommitted
[Freestyler] Add chevron buttons for the suggestions UI
The behavior should be: * When there are overflowing suggestions, we're showing chevron buttons to scroll forward or backward to the overflowing suggestions. Fixed: 372319494 Change-Id: I065bc8e6e9f03fc8473d188f19f16f11c098dd70 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5979835 Reviewed-by: Nikolay Vitkov <[email protected]> Commit-Queue: Nikolay Vitkov <[email protected]> Auto-Submit: Ergün Erdoğmuş <[email protected]>
1 parent 6d9164e commit b4a6e5a

File tree

3 files changed

+141
-77
lines changed

3 files changed

+141
-77
lines changed

front_end/panels/freestyler/components/UserActionRow.ts

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import * as Common from '../../../core/common/common.js';
56
import * as Host from '../../../core/host/host.js';
67
import * as i18n from '../../../core/i18n/i18n.js';
78
import type * as Platform from '../../../core/platform/platform.js';
@@ -55,12 +56,21 @@ const UIStringsNotTranslate = {
5556
* issue with the AI assistance message.
5657
*/
5758
report: 'Report legal issue',
59+
/**
60+
* @description The title of the button for scrolling to see next suggestions
61+
*/
62+
scrollToNext: 'Scroll to next suggestions',
63+
/**
64+
* @description The title of the button for scrolling to see previous suggestions
65+
*/
66+
scrollToPrevious: 'Scroll to previous suggestions',
5867
};
5968

6069
const lockedString = i18n.i18n.lockedString;
6170

6271
const REPORT_URL = 'https://support.google.com/legal/troubleshooter/1114905?hl=en#ts=1115658%2C13380504' as
6372
Platform.DevToolsPath.UrlString;
73+
const SCROLL_ROUNDING_OFFSET = 1;
6474
export interface UserActionRowProps {
6575
showRateButtons: boolean;
6676
onFeedbackSubmit: (rate: Host.AidaClient.Rating, feedback?: string) => void;
@@ -75,6 +85,11 @@ export class UserActionRow extends HTMLElement {
7585
#isShowingFeedbackForm = false;
7686
#currentRating?: Host.AidaClient.Rating;
7787
#isSubmitButtonDisabled = true;
88+
#suggestionsScrollContainerRef = LitHtml.Directives.createRef<HTMLElement>();
89+
#suggestionsLeftScrollButtonContainerRef = LitHtml.Directives.createRef<HTMLElement>();
90+
#suggestionsRightScrollButtonContainerRef = LitHtml.Directives.createRef<HTMLElement>();
91+
#suggestionsResizeObserver = new ResizeObserver(() => this.#handleSuggestionsScrollOrResize());
92+
#suggestionsEvaluateLayoutThrottler = new Common.Throttler.Throttler(50);
7893

7994
constructor(props: UserActionRowProps) {
8095
super();
@@ -84,13 +99,44 @@ export class UserActionRow extends HTMLElement {
8499
set props(props: UserActionRowProps) {
85100
this.#props = props;
86101
this.#render();
102+
this.#evaluateSuggestionsLayout();
87103
}
88104

89105
connectedCallback(): void {
90106
this.#shadow.adoptedStyleSheets = [userActionRowStyles, Input.textInputStyles];
91107
this.#render();
108+
this.#evaluateSuggestionsLayout();
109+
110+
if (this.#suggestionsScrollContainerRef.value) {
111+
this.#suggestionsResizeObserver.observe(this.#suggestionsScrollContainerRef.value);
112+
}
113+
}
114+
115+
disconnectedCallback(): void {
116+
this.#suggestionsResizeObserver.disconnect();
92117
}
93118

119+
#handleSuggestionsScrollOrResize = (): void => {
120+
void this.#suggestionsEvaluateLayoutThrottler.schedule(() => {
121+
this.#evaluateSuggestionsLayout();
122+
return Promise.resolve();
123+
});
124+
};
125+
126+
#scrollSuggestionsScrollContainer = (direction: 'left'|'right'): void => {
127+
const suggestionsScrollContainer = this.#suggestionsScrollContainerRef.value;
128+
if (!suggestionsScrollContainer) {
129+
return;
130+
}
131+
132+
suggestionsScrollContainer.scroll({
133+
top: 0,
134+
left: direction === 'left' ? suggestionsScrollContainer.scrollLeft - suggestionsScrollContainer.clientWidth :
135+
suggestionsScrollContainer.scrollLeft + suggestionsScrollContainer.clientWidth,
136+
behavior: 'smooth',
137+
});
138+
};
139+
94140
#handleRateClick(rating: Host.AidaClient.Rating): void {
95141
if (this.#currentRating === rating) {
96142
return;
@@ -177,6 +223,22 @@ export class UserActionRow extends HTMLElement {
177223
}
178224
};
179225

226+
#evaluateSuggestionsLayout = (): void => {
227+
const suggestionsScrollContainer = this.#suggestionsScrollContainerRef.value;
228+
const leftScrollButtonContainer = this.#suggestionsLeftScrollButtonContainerRef.value;
229+
const rightScrollButtonContainer = this.#suggestionsRightScrollButtonContainerRef.value;
230+
if (!suggestionsScrollContainer || !leftScrollButtonContainer || !rightScrollButtonContainer) {
231+
return;
232+
}
233+
234+
const shouldShowLeftButton = suggestionsScrollContainer.scrollLeft > SCROLL_ROUNDING_OFFSET;
235+
const shouldShowRightButton =
236+
suggestionsScrollContainer.scrollLeft + suggestionsScrollContainer.offsetWidth + SCROLL_ROUNDING_OFFSET <
237+
suggestionsScrollContainer.scrollWidth;
238+
leftScrollButtonContainer.classList.toggle('hidden', !shouldShowLeftButton);
239+
rightScrollButtonContainer.classList.toggle('hidden', !shouldShowRightButton);
240+
};
241+
180242
#renderFeedbackForm(): LitHtml.LitTemplate {
181243
// clang-format off
182244
return html`
@@ -232,6 +294,54 @@ export class UserActionRow extends HTMLElement {
232294
// clang-format on
233295
}
234296

297+
#renderSuggestions(): LitHtml.LitTemplate {
298+
// clang-format off
299+
if (!this.#props.suggestions) {
300+
return LitHtml.nothing;
301+
}
302+
303+
return html`<div class="suggestions-container">
304+
<div class="scroll-button-container left hidden" ${LitHtml.Directives.ref(this.#suggestionsLeftScrollButtonContainerRef)}>
305+
<devtools-button
306+
class='scroll-button'
307+
.data=${{
308+
variant: Buttons.Button.Variant.ICON,
309+
size: Buttons.Button.Size.SMALL,
310+
iconName: 'chevron-left',
311+
title: lockedString(UIStringsNotTranslate.scrollToPrevious),
312+
jslogContext: 'chevron-left',
313+
} as Buttons.Button.ButtonData}
314+
@click=${() => this.#scrollSuggestionsScrollContainer('left')}
315+
></devtools-button>
316+
</div>
317+
<div class="suggestions-scroll-container" @scroll=${this.#handleSuggestionsScrollOrResize} ${LitHtml.Directives.ref(this.#suggestionsScrollContainerRef)}>
318+
${this.#props.suggestions?.map(suggestion => html`<devtools-button
319+
class='suggestion'
320+
.data=${{
321+
variant: Buttons.Button.Variant.OUTLINED,
322+
title: suggestion,
323+
jslogContext: 'suggestion',
324+
} as Buttons.Button.ButtonData}
325+
@click=${() => this.#props.handleSuggestionClick(suggestion)}
326+
>${suggestion}</devtools-button>`)}
327+
</div>
328+
<div class="scroll-button-container right hidden" ${LitHtml.Directives.ref(this.#suggestionsRightScrollButtonContainerRef)}>
329+
<devtools-button
330+
class='scroll-button'
331+
.data=${{
332+
variant: Buttons.Button.Variant.ICON,
333+
size: Buttons.Button.Size.SMALL,
334+
iconName: 'chevron-right',
335+
title: lockedString(UIStringsNotTranslate.scrollToNext),
336+
jslogContext: 'chevron-right',
337+
} as Buttons.Button.ButtonData}
338+
@click=${() => this.#scrollSuggestionsScrollContainer('right')}
339+
></devtools-button>
340+
</div>
341+
</div>`;
342+
// clang-format on
343+
}
344+
235345
#render(): void {
236346
// clang-format off
237347
LitHtml.render(
@@ -240,17 +350,7 @@ export class UserActionRow extends HTMLElement {
240350
<div class="rate-buttons">
241351
${this.#props.showRateButtons ? this.#renderButtons() : LitHtml.nothing}
242352
</div>
243-
${this.#props.suggestions ?
244-
html`<div class="suggestions">
245-
${this.#props.suggestions?.map(suggestion => html`<devtools-button
246-
.data=${{
247-
variant: Buttons.Button.Variant.OUTLINED,
248-
title: suggestion,
249-
jslogContext: 'suggestion',
250-
} as Buttons.Button.ButtonData}
251-
@click=${() => this.#props.handleSuggestionClick(suggestion)}
252-
>${suggestion}</devtools-button>`)}
253-
</div>` : LitHtml.nothing}
353+
${this.#props.suggestions ? this.#renderSuggestions() : LitHtml.nothing}
254354
</div>
255355
${this.#isShowingFeedbackForm
256356
? this.#renderFeedbackForm()

front_end/panels/freestyler/components/userActionRow.css

Lines changed: 28 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -71,79 +71,41 @@
7171
display: inline-block;
7272
}
7373

74-
/*
75-
Scroll driven animation below is used for generating shadows
76-
when the `.suggestions` area is scrollable.
77-
*/
78-
.suggestions {
79-
display: flex;
80-
overflow-y: hidden;
81-
overflow-x: auto;
82-
scrollbar-width: none;
83-
gap: var(--sys-size-3);
84-
scroll-timeline: --scroll-timeline x; /* stylelint-disable-line property-no-unknown */
85-
animation: detect-scroll;
86-
animation-timeline: --scroll-timeline; /* stylelint-disable-line property-no-unknown */
87-
animation-fill-mode: none;
74+
.suggestions-container {
75+
overflow: hidden;
8876
position: relative;
89-
}
90-
91-
.suggestions::before,
92-
.suggestions::after {
93-
content: "";
94-
display: block;
95-
position: sticky;
96-
min-width: var(--sys-size-3);
97-
height: var(--sys-size-11);
98-
left: 0;
99-
right: 0;
100-
z-index: 999;
101-
animation-name: reveal;
102-
animation-timeline: --scroll-timeline; /* stylelint-disable-line property-no-unknown */
103-
animation-fill-mode: both;
104-
}
77+
display: flex;
10578

106-
.suggestions::before {
107-
top: 0;
108-
visibility: var(--visibility-if-can-scroll, hidden);
109-
animation-range: var(--sys-size-6) var(--sys-size-11); /* stylelint-disable-line property-no-unknown */
110-
background:
111-
radial-gradient(
112-
farthest-side at 0 50%,
113-
var(--app-color-scroll-area-shadow-start),
114-
transparent
115-
);
116-
}
79+
.suggestions-scroll-container {
80+
display: flex;
81+
overflow: auto hidden;
82+
scrollbar-width: none;
83+
gap: var(--sys-size-3);
84+
padding-right: var(--sys-size-1);
85+
}
11786

118-
.suggestions::after {
119-
bottom: 0;
120-
visibility: var(--visibility-if-can-scroll, hidden);
121-
animation-direction: reverse;
122-
/* stylelint-disable-next-line property-no-unknown */
123-
animation-range:
124-
calc(100% - var(--sys-size-11))
125-
calc(100% - var(--sys-size-6));
126-
background:
127-
radial-gradient(
128-
farthest-side at 100% 50%,
129-
var(--app-color-scroll-area-shadow-start),
130-
transparent
131-
);
132-
}
87+
.scroll-button-container {
88+
position: absolute;
89+
top: 0;
90+
height: 100%;
91+
display: flex;
92+
align-items: center;
93+
width: var(--sys-size-15);
94+
z-index: 999;
95+
}
13396

134-
@keyframes reveal {
135-
0% {
136-
opacity: 0%;
97+
.scroll-button-container.hidden {
98+
display: none;
13799
}
138100

139-
100% {
140-
opacity: 100%;
101+
.scroll-button-container.left {
102+
left: 0;
103+
background: linear-gradient(90deg, var(--sys-color-cdt-base-container) 0%, var(--sys-color-cdt-base-container) 50%, transparent);
141104
}
142-
}
143105

144-
@keyframes detect-scroll {
145-
from,
146-
to {
147-
--visibility-if-can-scroll: visible;
106+
.scroll-button-container.right {
107+
right: 0;
108+
background: linear-gradient(90deg, transparent, var(--sys-color-cdt-base-container) 50%);
109+
justify-content: flex-end;
148110
}
149111
}

front_end/ui/visual_logging/KnownContextValues.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,8 @@ export const knownContextValues = new Set([
618618
'changes.reveal-source',
619619
'changes.revert',
620620
'checked',
621+
'chevron-left',
622+
'chevron-right',
621623
'chrome-ai',
622624
'chrome-android-mobile',
623625
'chrome-android-mobile-high-end',

0 commit comments

Comments
 (0)