mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Add scroll fade indicators to artifact lists and plan steps
- Add fade gradient to artifacts list that hides when scrolled to bottom - Update plan area fades to use scroll position instead of always showing - Add hover effect to artifact cards with glowing ellipse indicator - Fix indentation in UserMessage attachment styles
This commit is contained in:
parent
0b54cdcc10
commit
6b5d51df39
3 changed files with 97 additions and 23 deletions
|
|
@ -29,6 +29,24 @@
|
|||
}
|
||||
return tally;
|
||||
}
|
||||
|
||||
let isScrolledToBottom = true;
|
||||
|
||||
function updateScrollFade(element: HTMLElement) {
|
||||
const { scrollTop, scrollHeight, clientHeight } = element;
|
||||
isScrolledToBottom = scrollHeight - scrollTop - clientHeight < 1;
|
||||
}
|
||||
|
||||
function artifactsListScrollAction(element: HTMLElement) {
|
||||
updateScrollFade(element);
|
||||
const handleScroll = () => updateScrollFade(element);
|
||||
element.addEventListener("scroll", handleScroll);
|
||||
return {
|
||||
destroy() {
|
||||
element.removeEventListener("scroll", handleScroll);
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="message-container assistant">
|
||||
|
|
@ -50,18 +68,24 @@
|
|||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="artifacts-list-container">
|
||||
{#each message.artifacts as artifact}
|
||||
<div class="artifact-card" aria-label="{artifact.filePath}">
|
||||
<span class="artifact-ellipse artifact-{artifact.action}"></span>
|
||||
<div class="artifacts-list-wrapper">
|
||||
<div class="artifacts-list-container" use:artifactsListScrollAction>
|
||||
{#each message.artifacts as artifact}
|
||||
<div
|
||||
class="artifact-icon"
|
||||
use:setElementIcon={artifact.getIconName()}
|
||||
></div>
|
||||
<span class="artifact-name">{basename(artifact.filePath)}</span>
|
||||
<span class="artifact-action artifact-action-{artifact.action}">{artifactActionToCopy(artifact.action)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
class="artifact-card"
|
||||
aria-label="{artifact.filePath}"
|
||||
>
|
||||
<span class="artifact-ellipse artifact-ellipse-{artifact.action}"></span>
|
||||
<div
|
||||
class="artifact-icon"
|
||||
use:setElementIcon={artifact.getIconName()}
|
||||
></div>
|
||||
<span class="artifact-name">{basename(artifact.filePath)}</span>
|
||||
<span class="artifact-action artifact-action-{artifact.action}">{artifactActionToCopy(artifact.action)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="artifacts-list-fade" class:hidden={isScrolledToBottom}></div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -169,9 +193,14 @@
|
|||
margin-left: var(--size-4-1);
|
||||
}
|
||||
|
||||
.artifacts-list-container {
|
||||
.artifacts-list-wrapper {
|
||||
grid-row: 2;
|
||||
grid-column: 1 / 3;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.artifacts-list-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: scroll;
|
||||
|
|
@ -184,6 +213,22 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.artifacts-list-fade {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: var(--size-4-8);
|
||||
background-image: linear-gradient(to bottom, transparent, var(--background-secondary));
|
||||
pointer-events: none;
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease-out;
|
||||
}
|
||||
|
||||
.artifacts-list-fade.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.artifact-card {
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
|
|
@ -195,6 +240,7 @@
|
|||
background-color: var(--background-secondary-alt);
|
||||
border-radius: var(--size-4-2);
|
||||
padding: 0 var(--size-4-1) 0 var(--size-4-2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.artifact-ellipse {
|
||||
|
|
@ -207,16 +253,25 @@
|
|||
align-self: center;
|
||||
}
|
||||
|
||||
.artifact-card:hover .artifact-ellipse {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
box-shadow: 0px 0px 4px 1px currentColor;
|
||||
}
|
||||
|
||||
.artifact-ellipse-create {
|
||||
background: var(--color-green);
|
||||
color: var(--color-green);
|
||||
}
|
||||
|
||||
.artifact-ellipse-modify {
|
||||
background: var(--color-blue);
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
.artifact-ellipse-delete {
|
||||
background: var(--color-red);
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
.artifact-icon {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
let stepElements: (HTMLDivElement | null)[] = [];
|
||||
let isTransitioning = false;
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
let isScrolledToTop = true;
|
||||
let isScrolledToBottom = true;
|
||||
|
||||
$: steps = $executionPlanState.plan?.executionSteps;
|
||||
$: activeStepIndex = $executionPlanState.currentStepIndex;
|
||||
|
|
@ -65,6 +67,8 @@
|
|||
|
||||
const stepsToShow = Math.min(3, steps.length);
|
||||
collapsedHeight = (stepsToShow * stepHeight) + (Math.max(0, stepsToShow - 1) * separatorHeight);
|
||||
|
||||
updateScrollFade();
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
|
|
@ -73,6 +77,15 @@
|
|||
}
|
||||
});
|
||||
|
||||
function updateScrollFade() {
|
||||
if (!wrapperDiv) {
|
||||
return;
|
||||
}
|
||||
const { scrollTop, scrollHeight, clientHeight } = wrapperDiv;
|
||||
isScrolledToTop = scrollTop < 1;
|
||||
isScrolledToBottom = scrollHeight - scrollTop - clientHeight < 1;
|
||||
}
|
||||
|
||||
async function scrollToActiveStep() {
|
||||
if (!wrapperDiv || !$executionPlanState.plan || activeStepIndex === -1) {
|
||||
return;
|
||||
|
|
@ -124,7 +137,7 @@
|
|||
aria-label={expanded ? "Collapse planned steps" : "Expand planned steps"}
|
||||
role="button"
|
||||
tabindex=0>
|
||||
<div id="chat-plan-steps-wrapper" style:height="{expanded ? expandedHeight : collapsedHeight}px" bind:this={wrapperDiv}>
|
||||
<div id="chat-plan-steps-wrapper" style:height="{expanded ? expandedHeight : collapsedHeight}px" bind:this={wrapperDiv} on:scroll={updateScrollFade}>
|
||||
<div id="chat-plan-steps" bind:this={contentDiv}>
|
||||
{#each steps as step, index }
|
||||
<div class="chat-plan-step" bind:this={stepElements[index]}>
|
||||
|
|
@ -153,8 +166,8 @@
|
|||
</div>
|
||||
</div>
|
||||
{#if steps.length > 2}
|
||||
<div class="chat-plan-fade top-fade" transition:fade></div>
|
||||
<div class="chat-plan-fade bottom-fade" transition:fade></div>
|
||||
<div class="chat-plan-fade top-fade" class:hidden={isScrolledToTop}></div>
|
||||
<div class="chat-plan-fade bottom-fade" class:hidden={isScrolledToBottom}></div>
|
||||
{/if}
|
||||
<div id="chat-plan-chevron"
|
||||
class="transparent-button"
|
||||
|
|
@ -251,6 +264,12 @@
|
|||
border-radius: var(--radius-m);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease-out;
|
||||
}
|
||||
|
||||
.chat-plan-fade.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.top-fade {
|
||||
|
|
|
|||
|
|
@ -119,16 +119,16 @@
|
|||
}
|
||||
|
||||
.message-attachment-name {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-size: var(--font-smaller);
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-size: var(--font-smaller);
|
||||
}
|
||||
|
||||
.message-attachment-size {
|
||||
padding: 0;
|
||||
font-size: var(--font-smallest);
|
||||
color: var(--text-muted);
|
||||
padding: 0;
|
||||
font-size: var(--font-smallest);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in a new issue