Improve modal UI and discard changes (#5148)

* style(modal): Update header padding and add color to body

* style: Update font color in modal body paragraphs

* feat(ui): improve DemoModal structure and props usage

* style: Remove unnecessary global styles from Modal component

* feat(ui): Update DemoModal component onclick event and add onSubmit handler

* StoryBook: Update text in DemoModal

* add missing "submit"

* modal lint/checks fixes

* Update "Discard changes" modal
This commit is contained in:
Pavel Laptev 2024-10-15 16:25:02 +02:00 committed by GitHub
parent 436cd41f44
commit c6482a94de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 56 additions and 43 deletions

View File

@ -12,6 +12,7 @@
import { getContext } from '@gitbutler/shared/context'; import { getContext } from '@gitbutler/shared/context';
import Button from '@gitbutler/ui/Button.svelte'; import Button from '@gitbutler/ui/Button.svelte';
import Modal from '@gitbutler/ui/Modal.svelte'; import Modal from '@gitbutler/ui/Modal.svelte';
import FileListItem from '@gitbutler/ui/file/FileListItem.svelte';
import { join } from '@tauri-apps/api/path'; import { join } from '@tauri-apps/api/path';
export let branchId: string | undefined; export let branchId: string | undefined;
@ -116,19 +117,27 @@
<Modal <Modal
width="small" width="small"
type="warning"
title="Discard changes" title="Discard changes"
bind:this={confirmationModal} bind:this={confirmationModal}
onSubmit={confirmDiscard} onSubmit={confirmDiscard}
> >
{#snippet children(item)} {#snippet children(item)}
<div> {#if item.files.length < 10}
Discarding changes to the following files: <p class="discard-caption">
Are you sure you want to discard the changes<br />to the following files:
</p>
<ul class="file-list"> <ul class="file-list">
{#each item.files as file} {#each item.files as file}
<li><code class="code-string">{file.path}</code></li> <FileListItem filePath={file.path} fileStatus={file.status} clickable={false} />
<!-- <li><code class="code-string">{file.path}</code></li> -->
{/each} {/each}
</ul> </ul>
</div> {:else}
Discard the changes to all <span class="text-bold">
{item.files.length} files
</span>?
{/if}
{/snippet} {/snippet}
{#snippet controls(close, item)} {#snippet controls(close, item)}
<Button style="ghost" outline onclick={close}>Cancel</Button> <Button style="ghost" outline onclick={close}>Cancel</Button>
@ -137,12 +146,14 @@
</Modal> </Modal>
<style lang="postcss"> <style lang="postcss">
.file-list { .discard-caption {
list-style: disc; color: var(--clr-text-2);
padding-left: 20px;
padding-top: 6px;
} }
.file-list li { .file-list {
padding: 2px; padding: 4px 0;
border-radius: var(--radius-m);
overflow: hidden;
border: 1px solid var(--clr-border-2);
margin-top: 12px;
} }
</style> </style>

View File

@ -62,7 +62,7 @@
<svelte:window on:keydown={handleKeyDown} /> <svelte:window on:keydown={handleKeyDown} />
<Modal bind:this={modal}> <Modal bind:this={modal} onSubmit={submit}>
<h2 class="text-18 text-bold">Create an topic</h2> <h2 class="text-18 text-bold">Create an topic</h2>
<div class="input"> <div class="input">
@ -93,7 +93,7 @@
{#snippet controls()} {#snippet controls()}
<Button onclick={() => modal?.close()}>Cancel</Button> <Button onclick={() => modal?.close()}>Cancel</Button>
<Button kind="solid" style="pop" onclick={submit} loading={submitProgress === 'loading'} <Button kind="solid" style="pop" type="submit" loading={submitProgress === 'loading'}
>{topic ? 'Update' : 'Create'}</Button >{topic ? 'Update' : 'Create'}</Button
> >
{/snippet} {/snippet}

View File

@ -4,13 +4,12 @@
import { portal } from '$lib/utils/portal'; import { portal } from '$lib/utils/portal';
import { pxToRem } from '$lib/utils/pxToRem'; import { pxToRem } from '$lib/utils/pxToRem';
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import type iconsJson from '$lib/data/icons.json';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
interface Props { interface Props {
width?: 'medium' | 'large' | 'small' | 'xsmall' | number; width?: 'medium' | 'large' | 'small' | 'xsmall' | number;
type?: 'info' | 'warning' | 'error' | 'success';
title?: string; title?: string;
icon?: keyof typeof iconsJson;
noPadding?: boolean; noPadding?: boolean;
onClose?: () => void; onClose?: () => void;
onSubmit?: (close: () => void) => void; onSubmit?: (close: () => void) => void;
@ -22,7 +21,7 @@
const { const {
width = 'medium', width = 'medium',
title, title,
icon, type = 'info',
onClose, onClose,
children, children,
controls, controls,
@ -102,9 +101,18 @@
> >
{#if title} {#if title}
<div class="modal__header"> <div class="modal__header">
{#if icon} {#if type === 'warning'}
<Icon name={icon} /> <Icon name="warning" color="warning" />
{/if} {/if}
{#if type === 'error'}
<Icon name="error" color="error" />
{/if}
{#if type === 'success'}
<Icon name="success" color="success" />
{/if}
<h2 class="text-14 text-semibold"> <h2 class="text-14 text-semibold">
{title} {title}
</h2> </h2>
@ -112,7 +120,9 @@
{/if} {/if}
<div class="modal__body custom-scrollbar text-13 text-body" class:no-padding={noPadding}> <div class="modal__body custom-scrollbar text-13 text-body" class:no-padding={noPadding}>
{@render children(item, close)} {#if children}
{@render children(item, close)}
{/if}
</div> </div>
{#if controls} {#if controls}
@ -171,9 +181,8 @@
.modal__header { .modal__header {
display: flex; display: flex;
padding: 16px; padding: 16px 16px 0;
gap: 8px; gap: 8px;
border-bottom: 1px solid var(--clr-border-2);
} }
.modal__body { .modal__body {
@ -186,8 +195,8 @@
} }
} }
.modal__body > :global(code), .modal__body :global(code),
.modal__body > :global(pre) { .modal__body :global(pre) {
word-wrap: break-word; word-wrap: break-word;
} }

View File

@ -1,18 +1,10 @@
<script lang="ts"> <script lang="ts">
import iconsJson from '$lib/data/icons.json';
import Button from '$lib/Button.svelte'; import Button from '$lib/Button.svelte';
import Modal from '$lib/Modal.svelte'; import Modal from '$lib/Modal.svelte';
import { type SvelteComponent } from 'svelte';
interface Props { const { ...args }: typeof Modal = $props();
width?: 'small' | 'large' | 'medium' | 'xsmall' | number;
title?: string;
icon?: keyof typeof iconsJson;
}
const { ...args }: Props = $props(); let modal: Modal;
let modal: SvelteComponent<Props>;
</script> </script>
<Button <Button
@ -20,13 +12,14 @@
modal?.show(); modal?.show();
}}>Show</Button }}>Show</Button
> >
<Modal bind:this={modal} {...args}> <Modal bind:this={modal} {...args} onSubmit={() => console.log('submitted')}>
<p>Wonderful modal content</p> A branch with the same name already exists. Do you want to merge this branch into the current
branch?
{#snippet controls(close)} {#snippet controls(close)}
<Button onclick={() => close()}>Close</Button> <Button style="ghost" outline onclick={() => close()}>Cancel</Button>
<Button style="pop" kind="solid" type="submit" onclick={() => console.log('Submit clicked')} <Button style="pop" kind="solid" type="submit" onclick={() => console.log('clicked')}
>Submit</Button >Merge</Button
> >
{/snippet} {/snippet}
</Modal> </Modal>

View File

@ -1,22 +1,21 @@
import DemoModal from './DemoModal.svelte'; import DemoModal from './DemoModal.svelte';
import iconsJson from '$lib/data/icons.json'; import type { StoryObj } from '@storybook/svelte';
import type { Meta, StoryObj } from '@storybook/svelte';
const meta = { const meta = {
title: 'Overlays / Modal', title: 'Overlays / Modal',
component: DemoModal, component: DemoModal as any,
argTypes: { argTypes: {
width: { width: {
control: 'select', control: 'select',
options: ['default', 'small', 'large'] options: ['default', 'small', 'large']
}, },
title: { control: 'text' }, title: { control: 'text' },
icon: { type: {
control: 'select', control: 'select',
options: [undefined, ...Object.keys(iconsJson)] options: ['info', 'success', 'warning', 'error']
} }
} }
} satisfies Meta<DemoModal>; };
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<typeof meta>;
@ -25,6 +24,7 @@ export const DefaultStory: Story = {
name: 'Modal', name: 'Modal',
args: { args: {
width: 'small', width: 'small',
type: 'info',
title: 'This is a fantastic modal :D' title: 'This is a fantastic modal :D'
} }
}; };