Move dummy UI and minor code refactor (#5061)

* update: storybook styles

* fix: Link button prop

* move borderless textarea to UI

* add: BorderlessTextarea story

* remove unused `autoHeight`

* simplify "Props" names

* migrate to Svelte 5: EmptyState component

* fix: autofocus when open the branches search

* eslint

* lint fixes

* lint fixes

---------

Co-authored-by: estib <stron@me.com>
This commit is contained in:
Pavel Laptev 2024-10-09 17:00:35 +02:00 committed by GitHub
parent 531cbde1ff
commit e05c1a4a08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 150 additions and 85 deletions

View File

@ -200,10 +200,12 @@
<Dropzones>
<div class="new-branch">
<EmptyStatePlaceholder image={laneNewSvg} width={180} bottomMargin={48}>
<svelte:fragment slot="title">This is a new branch</svelte:fragment>
<svelte:fragment slot="caption">
{#snippet title()}
This is a new branch
{/snippet}
{#snippet caption()}
You can drag and drop files or parts of files here.
</svelte:fragment>
{/snippet}
</EmptyStatePlaceholder>
</div>
</Dropzones>
@ -211,9 +213,9 @@
<Dropzones>
<div class="no-changes">
<EmptyStatePlaceholder image={noChangesSvg} width={180}>
<svelte:fragment slot="caption"
>No uncommitted changes on this branch</svelte:fragment
>
{#snippet caption()}
No uncommitted changes on this branch
{/snippet}
</EmptyStatePlaceholder>
</div>
</Dropzones>

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { autoSelectBranchNameFeature } from '$lib/config/uiFeatureFlags';
import { resizeObserver } from '$lib/utils/resizeObserver';
import { resizeObserver } from '@gitbutler/ui/utils/resizeObserver';
interface Props {
name: string;

View File

@ -13,17 +13,17 @@
import { isFailure } from '$lib/result';
import DropDownButton from '$lib/shared/DropDownButton.svelte';
import { User } from '$lib/stores/user';
import { autoHeight } from '$lib/utils/autoHeight';
import { splitMessage } from '$lib/utils/commitMessage';
import { getContext, getContextStore } from '$lib/utils/context';
import { KeyName } from '$lib/utils/hotkeys';
import { resizeObserver } from '$lib/utils/resizeObserver';
import { isWhiteSpaceString } from '$lib/utils/string';
import { SelectedOwnership } from '$lib/vbranches/ownership';
import { VirtualBranch, LocalFile } from '$lib/vbranches/types';
import Checkbox from '@gitbutler/ui/Checkbox.svelte';
import Icon from '@gitbutler/ui/Icon.svelte';
import Tooltip from '@gitbutler/ui/Tooltip.svelte';
import { autoHeight } from '@gitbutler/ui/utils/autoHeight';
import { resizeObserver } from '@gitbutler/ui/utils/resizeObserver';
import { createEventDispatcher, onMount, tick } from 'svelte';
import { fly } from 'svelte/transition';

View File

@ -1,9 +1,9 @@
<script lang="ts">
import { MessageRole } from '$lib/ai/types';
import Markdown from '$lib/components/Markdown.svelte';
import { autoHeight } from '$lib/utils/autoHeight';
import Button from '@gitbutler/ui/Button.svelte';
import Icon from '@gitbutler/ui/Icon.svelte';
import { autoHeight } from '@gitbutler/ui/utils/autoHeight';
import { createEventDispatcher } from 'svelte';
export let disableRemove = false;

View File

@ -1,8 +1,8 @@
<script lang="ts">
import { clickOutside } from '$lib/clickOutside';
import { createKeybind } from '$lib/utils/hotkeys';
import { resizeObserver } from '$lib/utils/resizeObserver';
import { portal } from '@gitbutler/ui/utils/portal';
import { resizeObserver } from '@gitbutler/ui/utils/resizeObserver';
import { type Snippet } from 'svelte';
interface Props {

View File

@ -47,7 +47,6 @@
const selectedFiles = fileIdSelection.files;
// eslint-disable-next-line @typescript-eslint/promise-function-async
const draggableFiles = $derived.by(() => {
if ($selectedFiles?.some((selectedFile) => selectedFile.id === file.id)) {
return $selectedFiles || [];

View File

@ -127,11 +127,13 @@
<!-- EMPTY STATE -->
{#if $snapshots.length === 0 && !$loading}
<EmptyStatePlaceholder image={emptyFolderSvg} bottomMargin={48}>
<svelte:fragment slot="title">No snapshots yet</svelte:fragment>
<svelte:fragment slot="caption">
{#snippet title()}
No snapshots yet
{/snippet}
{#snippet caption()}
Gitbutler saves your work, including file changes, so your progress is always secure.
Adjust snapshot settings in project settings.
</svelte:fragment>
{/snippet}
</EmptyStatePlaceholder>
{/if}

View File

@ -35,9 +35,9 @@
function openSearch() {
searching = true;
if (searchEl) {
setTimeout(() => {
searchEl.focus();
}
}, 0);
}
function toggleSearch() {
@ -155,7 +155,9 @@
</ScrollableContainer>
{:else}
<EmptyStatePlaceholder image={noBranchesSvg} width={180} bottomMargin={48}>
<svelte:fragment slot="caption">No branches<br />match your filter</svelte:fragment>
{#snippet caption()}
No branches<br />match your filter
{/snippet}
</EmptyStatePlaceholder>
{/if}
{:else}
@ -260,7 +262,7 @@
.search-input {
width: 100%;
height: 100%;
visibility: hidden;
display: none;
padding-left: 8px;
border-radius: var(--radius-s);
border: 1px solid var(--clr-border-2);
@ -289,7 +291,7 @@
}
& .search-input {
visibility: visible;
display: block;
}
}

View File

@ -2,9 +2,9 @@
import { ProjectService } from '$lib/backend/projects';
import ScrollableContainer from '$lib/scroll/ScrollableContainer.svelte';
import { getContext } from '$lib/utils/context';
import { resizeObserver } from '$lib/utils/resizeObserver';
import Icon from '@gitbutler/ui/Icon.svelte';
import { portal } from '@gitbutler/ui/utils/portal';
import { resizeObserver } from '@gitbutler/ui/utils/resizeObserver';
import type iconsJson from '@gitbutler/ui/data/icons.json';
import { goto } from '$app/navigation';
import { page } from '$app/stores';

View File

@ -21,11 +21,9 @@
import { showError, showToast } from '$lib/notifications/toasts';
import { isFailure } from '$lib/result';
import ScrollableContainer from '$lib/scroll/ScrollableContainer.svelte';
import BorderlessTextarea from '$lib/shared/BorderlessTextarea.svelte';
import TextBox from '$lib/shared/TextBox.svelte';
import Toggle from '$lib/shared/Toggle.svelte';
import { User } from '$lib/stores/user';
import { autoHeight } from '$lib/utils/autoHeight';
import { getBranchNameFromRef } from '$lib/utils/branch';
import { getContext, getContextStore } from '$lib/utils/context';
import { KeyName, onMetaEnter } from '$lib/utils/hotkeys';
@ -34,6 +32,7 @@
import { openExternalUrl } from '$lib/utils/url';
import { BranchController } from '$lib/vbranches/branchController';
import { DetailedCommit, VirtualBranch } from '$lib/vbranches/types';
import BorderlessTextarea from '@gitbutler/ui/BorderlessTextarea.svelte';
import Button from '@gitbutler/ui/Button.svelte';
import Modal from '@gitbutler/ui/Modal.svelte';
import Segment from '@gitbutler/ui/segmentControl/Segment.svelte';
@ -89,8 +88,6 @@
let isDraft = $state<boolean>($preferredPRAction === PRAction.CreateDraft);
let modal = $state<ReturnType<typeof Modal>>();
// let inputTitleElem = $state<HTMLInputElement | null>(null);
let bodyTextArea = $state<HTMLTextAreaElement | null>(null);
let isEditing = $state<boolean>(true);
let isLoading = $state<boolean>(false);
let pullRequestTemplateBody = $state<string | undefined>(undefined);
@ -147,10 +144,6 @@
}
});
function updateFieldsHeight() {
if (bodyTextArea) autoHeight(bodyTextArea);
}
async function createPr(params: CreatePrParams): Promise<PullRequest | undefined> {
if (!$gitHost) {
error('Pull request service not available');
@ -249,7 +242,6 @@
}
inputBody += t;
inputBody = '';
updateFieldsHeight();
}
});
@ -262,8 +254,6 @@
inputBody = descriptionResult.value;
aiIsLoading = false;
await tick();
updateFieldsHeight();
}
function handleModalKeydown(e: KeyboardEvent) {
@ -380,8 +370,9 @@
autofocus
padding={{ top: 12, right: 12, bottom: 0, left: 12 }}
placeholder="Add description…"
oninput={(e) => {
inputBody = e.currentTarget.value;
oninput={(e: InputEvent) => {
const target = e.target as HTMLTextAreaElement;
inputBody = target.value;
}}
/>
@ -395,8 +386,9 @@
padding={{ top: 12, right: 12, bottom: 0, left: 12 }}
placeholder={aiService.prSummaryMainDirective}
onkeydown={onMetaEnter(handleAIButtonPressed)}
oninput={(e) => {
aiDescriptionDirective = e.currentTarget.value;
oninput={(e: InputEvent) => {
const target = e.target as HTMLTextAreaElement;
aiDescriptionDirective = target.value;
}}
/>
<div class="pr-ai__actions">

View File

@ -313,10 +313,4 @@
gap: 8px;
padding: 0 14px 12px 14px;
}
.floating-button {
position: absolute;
right: 6px;
top: 6px;
}
</style>

View File

@ -12,7 +12,7 @@
import TextBox from '../shared/TextBox.svelte';
import ScrollableContainer from '$lib/scroll/ScrollableContainer.svelte';
import { KeyName } from '$lib/utils/hotkeys';
import { resizeObserver } from '$lib/utils/resizeObserver';
import { resizeObserver } from '@gitbutler/ui/utils/resizeObserver';
import { type Snippet } from 'svelte';
interface SelectProps {

View File

@ -102,15 +102,15 @@
</Select>
{:else}
<EmptyStatePlaceholder image={notFoundSvg} topBottomPadding={20}>
<svelte:fragment slot="caption"
>No templates found in the project
{#snippet caption()}
No templates found in the project
<span class="text-11">
<Link
href="https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository"
>How to create a template</Link
></span
>
</svelte:fragment>
{/snippet}
</EmptyStatePlaceholder>
{/if}
</SectionCard>

View File

@ -6,7 +6,7 @@
import type { ComponentColor, ComponentStyleKind } from '@gitbutler/ui/utils/colorTypes';
import type { Snippet } from 'svelte';
interface DropDownButtonProps {
interface Props {
icon?: keyof typeof iconsJson;
style?: ComponentColor;
kind?: ComponentStyleKind;
@ -34,7 +34,7 @@
children,
contextMenuSlot,
onclick
}: DropDownButtonProps = $props();
}: Props = $props();
let contextMenu = $state<ReturnType<typeof ContextMenu>>();
let iconEl = $state<HTMLElement>();

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { autoHeight } from '$lib/utils/autoHeight';
import { resizeObserver } from '$lib/utils/resizeObserver';
import { autoHeight } from '@gitbutler/ui/utils/autoHeight';
import { pxToRem } from '@gitbutler/ui/utils/pxToRem';
import { resizeObserver } from '@gitbutler/ui/utils/resizeObserver';
import { createEventDispatcher } from 'svelte';
export let value: string | undefined;

View File

@ -192,10 +192,12 @@
<Dropzones>
<div class="new-branch card">
<EmptyStatePlaceholder image={laneNewSvg} width={180} bottomMargin={48}>
<svelte:fragment slot="title">This is a new branch</svelte:fragment>
<svelte:fragment slot="caption">
{#snippet title()}
This is a new branch
{/snippet}
{#snippet caption()}
You can drag and drop files or parts of files here.
</svelte:fragment>
{/snippet}
</EmptyStatePlaceholder>
</div>
</Dropzones>
@ -203,9 +205,9 @@
<Dropzones>
<div class="no-changes card">
<EmptyStatePlaceholder image={noChangesSvg} width={180}>
<svelte:fragment slot="caption">
{#snippet caption()}
No uncommitted changes on this branch
</svelte:fragment>
{/snippet}
</EmptyStatePlaceholder>
</div>
</Dropzones>

View File

@ -1,3 +1,7 @@
body {
color: var(--clr-text-1);
}
.sbdocs {
hr {
margin: 50px 0 30px;

View File

@ -1,7 +1,7 @@
<script lang="ts" module>
import type { ComponentColor, ComponentStyleKind } from '$lib/utils/colorTypes';
export interface BadgeProps {
export interface Props {
label: string | number;
style?: ComponentColor;
kind?: ComponentStyleKind;
@ -9,7 +9,7 @@
</script>
<script lang="ts">
const { label, style = 'neutral', kind = 'solid' }: BadgeProps = $props();
const { label, style = 'neutral', kind = 'solid' }: Props = $props();
</script>
<div class="badge {style} {kind} text-10 text-semibold">

View File

@ -1,10 +1,5 @@
<script lang="ts">
import { autoHeight } from '$lib/utils/autoHeight';
import { resizeObserver } from '$lib/utils/resizeObserver';
import { pxToRem } from '@gitbutler/ui/utils/pxToRem';
import { onMount } from 'svelte';
interface Props {
<script lang="ts" module>
export interface Props {
ref?: HTMLTextAreaElement;
value: string | undefined;
placeholder?: string;
@ -18,10 +13,17 @@
bottom: number;
left: number;
};
oninput: (e: Event & { currentTarget: EventTarget & HTMLTextAreaElement }) => void;
oninput?: (e: Event & { currentTarget: EventTarget & HTMLTextAreaElement }) => void;
onfocus?: (e: Event & { currentTarget: EventTarget & HTMLTextAreaElement }) => void;
onkeydown?: (e: KeyboardEvent) => void;
}
</script>
<script lang="ts">
import { autoHeight } from '$lib/utils/autoHeight';
import { pxToRem } from '$lib/utils/pxToRem';
import { resizeObserver } from '$lib/utils/resizeObserver';
import { onMount } from 'svelte';
let {
ref = $bindable(),
@ -62,7 +64,7 @@
{readonly}
oninput={(e) => {
autoHeight(e.currentTarget);
oninput(e);
oninput?.(e);
}}
onfocus={(e) => {
autoHeight(e.currentTarget);

View File

@ -1,5 +1,5 @@
<script lang="ts" module>
export interface ButtonProps {
export interface Props {
el?: HTMLElement;
// Interaction props
disabled?: boolean;
@ -79,7 +79,7 @@
oncontextmenu,
onkeydown,
children
}: ButtonProps = $props();
}: Props = $props();
function handleAction(e: MouseEvent) {
if (loading || disabled || !clickable) {

View File

@ -1,6 +1,6 @@
<script lang="ts" module>
export type CheckboxStyle = 'default' | 'neutral';
export interface CheckboxProps {
export interface Props {
name?: string;
small?: boolean;
disabled?: boolean;
@ -30,7 +30,7 @@
style = 'default',
onclick,
onchange
}: CheckboxProps = $props();
}: Props = $props();
$effect(() => {
if (input) input.indeterminate = indeterminate;

View File

@ -1,10 +1,27 @@
<script lang="ts" module>
export interface Props {
image: string;
width?: number;
bottomMargin?: number;
topBottomPadding?: number;
leftRightPadding?: number;
title?: Snippet;
caption?: Snippet;
}
</script>
<script lang="ts">
import { pxToRem } from '@gitbutler/ui/utils/pxToRem';
export let image: string;
export let width: number = 256;
export let bottomMargin: number = 0;
export let topBottomPadding: number = 48;
export let leftRightPadding: number = 0;
import type { Snippet } from 'svelte';
const {
image = '',
width = 256,
bottomMargin = 0,
topBottomPadding = 48,
leftRightPadding = 0,
title,
caption
}: Props = $props();
</script>
<div class="empty-state-container">
@ -19,14 +36,14 @@
</div>
<div class="empty-state__content">
{#if $$slots.title}
{#if title}
<h2 class="empty-state__title text-15 text-body text-semibold">
<slot name="title" />
{@render title()}
</h2>
{/if}
{#if $$slots.caption}
{#if caption}
<p class="empty-state__caption text-13 text-body">
<slot name="caption" />
{@render caption()}
</p>
{/if}
</div>

View File

@ -9,7 +9,7 @@
children: Snippet;
}
const { icon, onclick, children }: Props = $props();
const { icon = 'open-link', onclick, children }: Props = $props();
</script>
<button

View File

@ -15,7 +15,6 @@
branchDetails?: { commitCount: number; linesAdded: number; linesRemoved: number };
remotes?: string[];
local?: boolean;
authorAvatars: Snippet;
}
@ -30,7 +29,6 @@
branchDetails,
remotes = [],
local = false,
authorAvatars
}: Props = $props();

View File

@ -1,5 +1,5 @@
<script lang="ts">
import Badge, { type BadgeProps } from '$lib/Badge.svelte';
import Badge, { type Props as BadgeProps } from '$lib/Badge.svelte';
const props: BadgeProps = $props();
</script>

View File

@ -0,0 +1,18 @@
import BorderlessTextarea from './DemoBorderlessTextarea.svelte';
import type { Meta, StoryObj } from '@storybook/svelte';
const meta = {
title: 'Inputs / BorderlessTextarea',
component: BorderlessTextarea
} satisfies Meta<BorderlessTextarea>;
export default meta;
type Story = StoryObj<typeof meta>;
export const CheckboxStory: Story = {
name: 'BorderlessTextarea',
args: {
value: 'Hello, World!',
placeholder: 'Type here...'
}
};

View File

@ -0,0 +1,33 @@
<script lang="ts">
import BorderlessTextarea, {
type Props as BorderlessTextareaProps
} from '$lib/BorderlessTextarea.svelte';
import Button from '$lib/Button.svelte';
const props: BorderlessTextareaProps = $props();
let value = $state('Hello world');
function fillTheForm() {
value =
'If this is a WIP PR and you have todos left, feel free to uncomment this and turn this PR into a draft, see https://github.blog/2019-02-14-introducing-draft-pull-requests/';
}
</script>
<div class="wrapper">
<BorderlessTextarea
bind:value
oninput={(e) => {
console.log(e.currentTarget.value);
}}
/>
<Button style="ghost" outline onclick={fillTheForm}>Fill the form</Button>
</div>
<style lang="postcss">
.wrapper {
display: flex;
flex-direction: column;
max-width: 300px;
gap: 12px;
}
</style>

View File

@ -1,5 +1,5 @@
<script lang="ts">
import Button, { type ButtonProps } from '$lib/Button.svelte';
import Button, { type Props as ButtonProps } from '$lib/Button.svelte';
const props: ButtonProps = $props();
</script>

View File

@ -1,5 +1,5 @@
<script lang="ts">
import Checkbox, { type CheckboxProps } from '$lib/Checkbox.svelte';
import Checkbox, { type Props as CheckboxProps } from '$lib/Checkbox.svelte';
const props: CheckboxProps = $props();
</script>