Skip to content

Commit 084482c

Browse files
authored
ui-fix-loading-states (#11361)
* temp fix for the forge banner * feat(ui): replace manual loading skeleton with FullviewLoading component No need for the skeleton here because we don’t know anything about the UI at this point. * fix missing lanes width on loading * style(ui): add border-radius to codegen input container * add codegen loading skeleton * feat(ui): move SkeletonBone component
1 parent d738ac8 commit 084482c

File tree

8 files changed

+274
-19
lines changed

8 files changed

+274
-19
lines changed

apps/desktop/src/components/StackView.svelte

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import CommitView from '$components/CommitView.svelte';
66
import ConfigurableScrollableContainer from '$components/ConfigurableScrollableContainer.svelte';
77
import Drawer from '$components/Drawer.svelte';
8+
import FullviewLoading from '$components/FullviewLoading.svelte';
89
import NewCommitView from '$components/NewCommitView.svelte';
910
import ReduxResult from '$components/ReduxResult.svelte';
1011
import Resizer from '$components/Resizer.svelte';
@@ -535,6 +536,11 @@
535536
projectId={stableProjectId}
536537
result={combineResults(branchesQuery.result, hasRulesToClear.result)}
537538
>
539+
{#snippet loading()}
540+
<div style:width="{$persistedStackWidth}rem" class="lane-skeleton">
541+
<FullviewLoading />
542+
</div>
543+
{/snippet}
538544
{#snippet children([branches, hasRulesToClear])}
539545
<ConfigurableScrollableContainer childrenWrapHeight="100%">
540546
<div

apps/desktop/src/components/UnassignedViewForgePrompt.svelte

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,37 @@
2525
persistedDismissedForgeIntegrationPrompt(projectId)
2626
);
2727
28-
function configureIntegration(forge: AvailableForge): true {
28+
// Delay showing the banner to prevent flickering when auth state changes rapidly
29+
let canShowPrompt = $state(false);
30+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
31+
32+
$effect(() => {
33+
clearTimeout(timeoutId);
34+
35+
const shouldShow =
36+
forgeFactory.determinedForgeType !== 'default' &&
37+
!forgeFactory.current.isLoading &&
38+
!forgeFactory.current.authenticated &&
39+
forgeFactory.canSetupIntegration &&
40+
!$dismissedTheIntegrationPrompt;
41+
42+
if (shouldShow) {
43+
timeoutId = setTimeout(() => (canShowPrompt = true), 100);
44+
} else {
45+
canShowPrompt = false;
46+
}
47+
48+
return () => clearTimeout(timeoutId);
49+
});
50+
51+
function configureIntegration(forge: AvailableForge): void {
2952
switch (forge) {
3053
case 'github':
3154
openGeneralSettings('integrations');
32-
return true;
55+
break;
3356
case 'gitlab':
3457
openProjectSettings(projectId);
35-
return true;
58+
break;
3659
}
3760
}
3861
@@ -41,13 +64,12 @@
4164
}
4265
</script>
4366

44-
{#if forgeFactory.canSetupIntegration && !$dismissedTheIntegrationPrompt}
45-
{@const forgeName = forgeFactory.canSetupIntegration}
67+
{#if canShowPrompt}
68+
{@const forgeName = forgeFactory.canSetupIntegration!}
4669
{@const forgeLabel = availableForgeLabel(forgeName)}
4770
{@const forgeUnit = availableForgeReviewUnit(forgeName)}
4871
{@const integrationDocs = availableForgeDocsLink(forgeName)}
4972

50-
<!-- <div class="forge-prompt__wrap" class:border-bottom={bottomBorder} class:border-top={topBorder}> -->
5173
<div class="forge-prompt">
5274
<div class="forge-prompt__logo">
5375
{@html forgeName === 'github' ? githubLogoSvg : gitlabLogoSvg}
@@ -65,7 +87,6 @@
6587
>
6688
</div>
6789
</div>
68-
<!-- </div> -->
6990
{/if}
7091

7192
<style lang="postcss">

apps/desktop/src/components/WorkspaceView.svelte

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import ConfigurableScrollableContainer from '$components/ConfigurableScrollableContainer.svelte';
3+
import FullviewLoading from '$components/FullviewLoading.svelte';
34
import MainViewport from '$components/MainViewport.svelte';
45
import MultiStackView from '$components/MultiStackView.svelte';
56
import ReduxResult from '$components/ReduxResult.svelte';
@@ -64,20 +65,11 @@
6465
{#snippet middle()}
6566
<ReduxResult {projectId} result={stacksQuery?.result}>
6667
{#snippet loading()}
67-
<div class="stacks-view-skeleton"></div>
68+
<FullviewLoading />
6869
{/snippet}
6970
{#snippet children(stacks, { projectId })}
7071
<MultiStackView {projectId} {stacks} {selectionId} {scrollToStackId} {onScrollComplete} />
7172
{/snippet}
7273
</ReduxResult>
7374
{/snippet}
7475
</MainViewport>
75-
76-
<style>
77-
.stacks-view-skeleton {
78-
width: 100%;
79-
height: 100%;
80-
border: 1px solid var(--clr-border-2);
81-
border-radius: var(--radius-ml);
82-
}
83-
</style>

apps/desktop/src/components/codegen/CodegenInput.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@
386386
flex-direction: column;
387387
padding: 0;
388388
overflow: hidden;
389+
border-radius: var(--radius-m);
389390
cursor: text;
390391
transition: border-color var(--transition-fast);
391392
}

apps/desktop/src/components/codegen/CodegenMessages.svelte

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
KebabButton,
4242
Modal,
4343
Tooltip,
44-
Link
44+
Link,
45+
SkeletonBone
4546
} from '@gitbutler/ui';
4647
4748
import VirtualList from '@gitbutler/ui/components/VirtualList.svelte';
@@ -265,8 +266,70 @@
265266

266267
<div class="chat" use:focusable={{ vertical: true }}>
267268
<ReduxResult result={claudeAvailable.result}>
269+
{#snippet loading()}
270+
<PreviewHeader {onclose}>
271+
{#snippet content()}
272+
<h3 class="text-14 text-semibold truncate">Chat for {branchName}</h3>
273+
{/snippet}
274+
{#snippet actions()}
275+
<div class="flex gap-4 items-center">
276+
<SkeletonBone width="2.5rem" height="1.2rem" />
277+
<SkeletonBone width="1.5rem" height="1.2rem" />
278+
</div>
279+
{/snippet}
280+
</PreviewHeader>
281+
282+
<div class="chat-skeleton">
283+
<div class="chat-skeleton__user">
284+
<SkeletonBone
285+
width="80%"
286+
height="3rem"
287+
color="var(--clr-bg-3)"
288+
opacity={0.4}
289+
radius="var(--radius-ml) var(--radius-ml) 0 var(--radius-ml)"
290+
/>
291+
</div>
292+
<div class="chat-skeleton__assistant">
293+
<SkeletonBone width="100%" height="1rem" />
294+
<SkeletonBone width="90%" height="1rem" />
295+
<SkeletonBone width="95%" height="1rem" />
296+
</div>
297+
<div class="chat-skeleton__user">
298+
<SkeletonBone
299+
width="50%"
300+
height="3rem"
301+
color="var(--clr-bg-3)"
302+
opacity={0.4}
303+
radius="var(--radius-ml) var(--radius-ml) 0 var(--radius-ml)"
304+
/>
305+
</div>
306+
<div class="chat-skeleton__assistant">
307+
<SkeletonBone width="90%" height="1rem" />
308+
<SkeletonBone width="70%" height="1rem" />
309+
</div>
310+
</div>
311+
312+
<div class="dialog-wrapper">
313+
<div class="input-skeleton">
314+
<div class="input-skeleton__text"><SkeletonBone width="60%" height="1rem" /></div>
315+
<div class="input-skeleton__actions">
316+
<SkeletonBone width="4rem" height="var(--size-button)" radius="var(--radius-btn)" />
317+
<div class="flex gap-8">
318+
<SkeletonBone width="4rem" height="var(--size-button)" radius="var(--radius-btn)" />
319+
<SkeletonBone
320+
width="calc(var(--size-button) + 0.188rem)"
321+
height="var(--size-button)"
322+
color="var(--clr-theme-pop-element)"
323+
radius="var(--radius-btn)"
324+
/>
325+
</div>
326+
</div>
327+
</div>
328+
</div>
329+
{/snippet}
268330
{#snippet children(claudeAvailable)}
269331
{@const todos = getTodos(events)}
332+
270333
<!-- TODO: remove this header when we move to the workspace layout -->
271334
<PreviewHeader {onclose}>
272335
{#snippet content()}
@@ -279,7 +342,7 @@
279342

280343
<div class="flex gap-10 items-center">
281344
{#if stats.tokens > 0}
282-
<Tooltip text="Tokens: {stats.tokens.toLocaleString()} / ${stats.cost.toFixed(2)} ">
345+
<Tooltip text="Tokens: {stats.tokens.toLocaleString()} / ${stats.cost.toFixed(2)}">
283346
<span class="text-12 clr-text-2">
284347
{formatCompactNumber(stats.tokens)}
285348
</span>
@@ -736,4 +799,48 @@
736799
transition: stroke-dashoffset 0.3s ease;
737800
}
738801
}
802+
803+
.chat-skeleton {
804+
display: flex;
805+
flex: 1;
806+
flex-direction: column;
807+
padding: 20px;
808+
gap: 20px;
809+
}
810+
811+
.chat-skeleton__user {
812+
display: flex;
813+
flex-direction: column;
814+
align-items: flex-end;
815+
justify-content: center;
816+
}
817+
818+
.chat-skeleton__assistant {
819+
display: flex;
820+
flex-direction: column;
821+
align-items: flex-start;
822+
max-width: 80%;
823+
gap: 8px;
824+
}
825+
826+
.input-skeleton {
827+
display: flex;
828+
flex-direction: column;
829+
width: 100%;
830+
padding: 12px;
831+
border: 1px solid var(--clr-border-2);
832+
border-radius: var(--radius-m);
833+
}
834+
835+
.input-skeleton__text {
836+
width: 100%;
837+
padding-bottom: 30px;
838+
}
839+
840+
.input-skeleton__actions {
841+
display: flex;
842+
justify-content: space-between;
843+
padding-top: 12px;
844+
border-top: 1px solid var(--clr-border-3);
845+
}
739846
</style>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<script lang="ts">
2+
interface Props {
3+
width?: string;
4+
height?: string;
5+
radius?: string;
6+
color?: string;
7+
opacity?: number;
8+
}
9+
10+
const {
11+
width = '100%',
12+
height = '1rem',
13+
radius = 'var(--radius-ml)',
14+
color = 'var(--clr-scale-ntrl-70)',
15+
opacity = 0.2
16+
}: Props = $props();
17+
</script>
18+
19+
<div
20+
style:width
21+
style:height
22+
style:border-radius={radius}
23+
style:background-color={color}
24+
style:--opacity-value={opacity}
25+
class="skeleton-bone"
26+
></div>
27+
28+
<style lang="postcss">
29+
.skeleton-bone {
30+
animation: pulse 0.8s ease-in-out infinite alternate;
31+
}
32+
33+
@keyframes pulse {
34+
0% {
35+
opacity: var(--opacity-value);
36+
}
37+
100% {
38+
opacity: calc(var(--opacity-value) + var(--opacity-value) * 0.7);
39+
}
40+
}
41+
</style>

packages/ui/src/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export { default as SeriesIcon } from '$components/SeriesIcon.svelte';
4343
export { default as SeriesLabelsRow } from '$components/SeriesLabelsRow.svelte';
4444
export { default as SidebarEntry } from '$components/SidebarEntry.svelte';
4545
export { default as SimpleCommitRow } from '$components/SimpleCommitRow.svelte';
46+
export { default as SkeletonBone } from '$components/SkeletonBone.svelte';
4647
export { default as Spacer } from '$components/Spacer.svelte';
4748
export { default as TagInput, type Tag } from '$components/TagInput.svelte';
4849
export { default as Textarea } from '$components/Textarea.svelte';
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<script module lang="ts">
2+
import SkeletonBone from '$components/SkeletonBone.svelte';
3+
import { defineMeta } from '@storybook/addon-svelte-csf';
4+
5+
const { Story } = defineMeta({
6+
title: 'Components / SkeletonBone',
7+
component: SkeletonBone,
8+
args: {
9+
width: '100%',
10+
height: '1rem',
11+
radius: 'var(--radius-ml)',
12+
color: 'var(--clr-text-3)',
13+
opacity: 0.14
14+
}
15+
});
16+
</script>
17+
18+
<Story name="Playground">
19+
{#snippet template(args)}
20+
<div style="padding: 20px; max-width: 400px;">
21+
<SkeletonBone
22+
width={args.width}
23+
height={args.height}
24+
radius={args.radius}
25+
color={args.color}
26+
opacity={args.opacity}
27+
/>
28+
</div>
29+
{/snippet}
30+
</Story>
31+
32+
<Story name="Text Lines">
33+
{#snippet template()}
34+
<div style="padding: 20px; max-width: 600px; display: flex; flex-direction: column; gap: 8px;">
35+
<SkeletonBone width="100%" height="1rem" />
36+
<SkeletonBone width="90%" height="1rem" />
37+
<SkeletonBone width="95%" height="1rem" />
38+
<SkeletonBone width="70%" height="1rem" />
39+
</div>
40+
{/snippet}
41+
</Story>
42+
43+
<Story name="Button">
44+
{#snippet template()}
45+
<div style="padding: 20px;">
46+
<SkeletonBone width="6rem" height="var(--size-button)" radius="var(--radius-btn)" />
47+
</div>
48+
{/snippet}
49+
</Story>
50+
51+
<Story name="Avatar">
52+
{#snippet template()}
53+
<div style="padding: 20px;">
54+
<SkeletonBone width="2.5rem" height="2.5rem" radius="50%" />
55+
</div>
56+
{/snippet}
57+
</Story>
58+
59+
<Story name="Card">
60+
{#snippet template()}
61+
<div style="padding: 20px; max-width: 400px;">
62+
<SkeletonBone width="100%" height="10rem" radius="var(--radius-m)" />
63+
</div>
64+
{/snippet}
65+
</Story>
66+
67+
<Story name="Custom Color">
68+
{#snippet template()}
69+
<div style="padding: 20px; max-width: 400px; display: flex; flex-direction: column; gap: 12px;">
70+
<SkeletonBone
71+
width="80%"
72+
height="3rem"
73+
color="var(--clr-bg-3)"
74+
opacity={0.4}
75+
radius="var(--radius-ml)"
76+
/>
77+
<SkeletonBone
78+
width="100%"
79+
height="2rem"
80+
color="var(--clr-theme-pop-element)"
81+
opacity={0.2}
82+
radius="var(--radius-btn)"
83+
/>
84+
</div>
85+
{/snippet}
86+
</Story>

0 commit comments

Comments
 (0)