feat: use new Tabs components to organise Preferences page (#4906)

This commit is contained in:
Nico Domino 2024-09-17 11:22:17 +02:00 committed by GitHub
parent 04b854c10c
commit 6476a1c754
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 498 additions and 263 deletions

View File

@ -115,10 +115,12 @@
.section-card__title {
color: var(--clr-scale-ntrl-0);
user-select: none;
}
.section-card__text {
color: var(--clr-text-2);
user-select: none;
/* if empty hide the caption */
&:empty {

View File

@ -0,0 +1,31 @@
<script lang="ts">
import { type Snippet, getContext } from 'svelte';
import type { TabContext } from './types';
interface Props {
children: Snippet;
value: string;
}
const { children, value }: Props = $props();
const tabStore = getContext<TabContext>('tab');
const selectedIndex = $derived(tabStore.selectedIndex);
const isActive = $derived($selectedIndex === value);
</script>
{#if isActive}
<div data-value={value} class="tab-content">
{@render children()}
</div>
{/if}
<style>
.tab-content {
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
margin-top: 1rem;
}
</style>

View File

@ -0,0 +1,21 @@
<script lang="ts">
import { type Snippet } from 'svelte';
interface Props {
children: Snippet;
}
const { children }: Props = $props();
</script>
<ul class="tab-list">
{@render children()}
</ul>
<style>
.tab-list {
width: 100%;
display: flex;
border-radius: var(--radius-m);
}
</style>

View File

@ -0,0 +1,37 @@
<script lang="ts">
import { type TabContext } from './types';
import { getContext, type Snippet } from 'svelte';
interface Props {
children: Snippet;
value: string;
disabled?: boolean;
}
const { value, children, disabled }: Props = $props();
const tabStore = getContext<TabContext>('tab');
const selectedIndex = $derived(tabStore.selectedIndex);
const isActive = $derived($selectedIndex === value);
function setActive() {
tabStore?.setSelected(value);
}
</script>
<button
role="tab"
tabindex={isActive ? -1 : 0}
aria-selected={isActive}
id={value}
{value}
{disabled}
onclick={setActive}
class="segment-control-item"
class:disabled
class:active={isActive}
>
<span class="text-12 segment-control-item__label">
{@render children()}
</span>
</button>

View File

@ -0,0 +1,38 @@
<script lang="ts">
import { type TabContext } from './types';
import { setContext } from 'svelte';
import { writable } from 'svelte/store';
import type { Snippet } from 'svelte';
interface Props {
children: Snippet;
defaultSelected: string;
}
const { children, defaultSelected }: Props = $props();
let selectedIndex = writable(defaultSelected);
const context: TabContext = {
selectedIndex,
setSelected: (i) => {
selectedIndex.set(i);
return selectedIndex;
}
};
setContext<TabContext>('tab', context);
</script>
<section class="tab-wrapper">
{@render children()}
</section>
<style>
.tab-wrapper {
display: flex;
flex-direction: column;
width: 100%;
margin: 0 auto;
}
</style>

View File

@ -0,0 +1,6 @@
import type { Writable } from 'svelte/store';
export interface TabContext {
selectedIndex: Writable<string>;
setSelected: (id: string) => Writable<string>;
}

View File

@ -3,7 +3,7 @@
import { pxToRem } from '@gitbutler/ui/utils/pxToRem';
export let spacer = false;
export let gap = 20;
export let gap = 16;
</script>
<div class="settings-section" style="gap: {pxToRem(gap)}">

View File

@ -1,12 +1,12 @@
<script lang="ts">
import InfoMessage from '../shared/InfoMessage.svelte';
import { Project } from '$lib/backend/projects';
import { BaseBranch } from '$lib/baseBranch/baseBranch';
import { getRemoteBranches } from '$lib/baseBranch/baseBranchService';
import SectionCard from '$lib/components/SectionCard.svelte';
import Select from '$lib/select/Select.svelte';
import SelectItem from '$lib/select/SelectItem.svelte';
import Section from '$lib/settings/Section.svelte';
import InfoMessage from '$lib/shared/InfoMessage.svelte';
import Spacer from '$lib/shared/Spacer.svelte';
import { getContext, getContextStore } from '$lib/utils/context';
import { BranchController } from '$lib/vbranches/branchController';
import { VirtualBranchService } from '$lib/vbranches/virtualBranch';
@ -57,76 +57,75 @@
</InfoMessage>
{:then remoteBranches}
{#if remoteBranches.length > 0}
<Section spacer>
<SectionCard>
<svelte:fragment slot="title">Remote configuration</svelte:fragment>
<svelte:fragment slot="caption">
Lets you choose where to push code and set the target branch for contributions. The target
branch is usually the "production" branch like 'origin/master' or 'upstream/main.' This
section helps ensure your code goes to the correct remote and branch for integration.
</svelte:fragment>
<SectionCard>
<svelte:fragment slot="title">Remote configuration</svelte:fragment>
<svelte:fragment slot="caption">
Lets you choose where to push code and set the target branch for contributions. The target
branch is usually the "production" branch like 'origin/master' or 'upstream/main.' This
section helps ensure your code goes to the correct remote and branch for integration.
</svelte:fragment>
<Select
value={selectedBranch.name}
options={remoteBranches.map((b) => ({ label: b.name, value: b.name }))}
onselect={(value) => {
selectedBranch = { name: value };
}}
disabled={targetChangeDisabled}
label="Current target branch"
searchable
>
{#snippet itemSnippet({ item, highlighted })}
<SelectItem selected={item.value === selectedBranch.name} {highlighted}>
{item.label}
</SelectItem>
{/snippet}
</Select>
{#if uniqueRemotes(remoteBranches).length > 1}
<Select
value={selectedBranch.name}
options={remoteBranches.map((b) => ({ label: b.name, value: b.name }))}
value={selectedRemote.name}
options={uniqueRemotes(remoteBranches).map((r) => ({ label: r.name!, value: r.name!}))}
onselect={(value) => {
selectedBranch = { name: value };
selectedRemote = { name: value };
}}
disabled={targetChangeDisabled}
label="Current target branch"
searchable
label="Create branches on remote"
>
{#snippet itemSnippet({ item, highlighted })}
<SelectItem selected={item.value === selectedBranch.name} {highlighted}>
<SelectItem selected={item.value === selectedRemote.name} {highlighted}>
{item.label}
</SelectItem>
{/snippet}
</Select>
{/if}
{#if uniqueRemotes(remoteBranches).length > 1}
<Select
value={selectedRemote.name}
options={uniqueRemotes(remoteBranches).map((r) => ({ label: r.name!, value: r.name!}))}
onselect={(value) => {
selectedRemote = { name: value };
}}
disabled={targetChangeDisabled}
label="Create branches on remote"
>
{#snippet itemSnippet({ item, highlighted })}
<SelectItem selected={item.value === selectedRemote.name} {highlighted}>
{item.label}
</SelectItem>
{/snippet}
</Select>
{/if}
{#if $activeBranches && targetChangeDisabled}
<InfoMessage filled outlined={false} icon="info">
<svelte:fragment slot="content">
You have {$activeBranches.length === 1
? '1 active branch'
: `${$activeBranches.length} active branches`} in your workspace. Please clear the workspace
before switching the base branch.
</svelte:fragment>
</InfoMessage>
{:else}
<Button
size="cta"
style="ghost"
outline
onclick={onSetBaseBranchClick}
id="set-base-branch"
loading={isSwitching}
disabled={(selectedBranch.name === $baseBranch.branchName &&
selectedRemote.name === $baseBranch.actualPushRemoteName()) ||
targetChangeDisabled}
>
{isSwitching ? 'Switching branches...' : 'Update configuration'}
</Button>
{/if}
</SectionCard>
</Section>
{#if $activeBranches && targetChangeDisabled}
<InfoMessage filled outlined={false} icon="info">
<svelte:fragment slot="content">
You have {$activeBranches.length === 1
? '1 active branch'
: `${$activeBranches.length} active branches`} in your workspace. Please clear the workspace
before switching the base branch.
</svelte:fragment>
</InfoMessage>
{:else}
<Button
size="cta"
style="ghost"
outline
onclick={onSetBaseBranchClick}
id="set-base-branch"
loading={isSwitching}
disabled={(selectedBranch.name === $baseBranch.branchName &&
selectedRemote.name === $baseBranch.actualPushRemoteName()) ||
targetChangeDisabled}
>
{isSwitching ? 'Switching branches...' : 'Update configuration'}
</Button>
{/if}
</SectionCard>
<Spacer />
{/if}
{:catch}
<InfoMessage filled outlined={true} style="error" icon="error">

View File

@ -72,19 +72,20 @@
}
</script>
{#if !$user}
<WelcomeSigninAction />
<Spacer />
{/if}
<Section spacer>
<svelte:fragment slot="title">AI options</svelte:fragment>
<Section>
<svelte:fragment slot="description">
GitButler supports the use of OpenAI and Anthropic to provide commit message and branch name
generation. This works either through GitButler's API or in a bring your own key configuration
and can be configured in the main preferences screen.
</svelte:fragment>
<Spacer />
{#if !$user}
<WelcomeSigninAction />
<Spacer />
{/if}
<div class="options">
<SectionCard labelFor="aiGenEnabled" orientation="row">
<svelte:fragment slot="title">Enable branch and commit message generation</svelte:fragment>
@ -123,7 +124,7 @@
</Section>
{#if $user?.role === 'admin'}
<Section spacer>
<Section>
<svelte:fragment slot="title">Full data synchronization</svelte:fragment>
<SectionCard labelFor="historySync" orientation="row">

View File

@ -3,7 +3,6 @@
import { Project, ProjectService } from '$lib/backend/projects';
import SectionCard from '$lib/components/SectionCard.svelte';
import SectionCardDisclaimer from '$lib/components/SectionCardDisclaimer.svelte';
import { projectRunCommitHooks } from '$lib/config/config';
import Select from '$lib/select/Select.svelte';
import SelectItem from '$lib/select/SelectItem.svelte';
import Section from '$lib/settings/Section.svelte';
@ -19,29 +18,10 @@
const projectService = getContext(ProjectService);
const project = getContext(Project);
let snaphotLinesThreshold = project?.snapshot_lines_threshold || 20; // when undefined, the default is 20
let allowForcePushing = project?.ok_with_force_push;
let omitCertificateCheck = project?.omit_certificate_check;
let useNewLocking = project?.use_new_locking || false;
let signCommits = false;
const gitConfig = getContext(GitConfigService);
const runCommitHooks = projectRunCommitHooks(project.id);
async function setWithForcePush(value: boolean) {
project.ok_with_force_push = value;
await projectService.updateProject(project);
}
async function setOmitCertificateCheck(value: boolean | undefined) {
project.omit_certificate_check = !!value;
await projectService.updateProject(project);
}
async function setSnapshotLinesThreshold(value: number) {
project.snapshot_lines_threshold = value;
await projectService.updateProject(project);
}
async function setSignCommits(targetState: boolean) {
signCommits = targetState;
@ -125,24 +105,14 @@
async function handleSignCommitsClick(event: MouseEvent) {
await setSignCommits((event.target as HTMLInputElement)?.checked);
}
async function handleAllowForcePushClick(event: MouseEvent) {
await setWithForcePush((event.target as HTMLInputElement)?.checked);
}
async function handleOmitCertificateCheckClick(event: MouseEvent) {
await setOmitCertificateCheck((event.target as HTMLInputElement)?.checked);
}
</script>
<Section spacer>
<svelte:fragment slot="title">Commit signing</svelte:fragment>
<svelte:fragment slot="description">
Use GPG or SSH to sign your commits so they can be verified as authentic.
</svelte:fragment>
<Section>
<SectionCard orientation="row" labelFor="signCommits">
<svelte:fragment slot="title">Sign commits</svelte:fragment>
<svelte:fragment slot="caption">
Use GPG or SSH to sign your commits so they can be verified as authentic.
<br />
GitButler will sign commits as per your git configuration.
</svelte:fragment>
<svelte:fragment slot="actions">
@ -155,7 +125,7 @@
value={signingFormat}
options={signingFormatOptions}
label="Signing format"
onselect={(value) => {
onselect={(value: string) => {
signingFormat = value;
updateSigningInfo();
}}
@ -218,95 +188,3 @@
</SectionCard>
{/if}
</Section>
<Section spacer>
<svelte:fragment slot="title">Preferences</svelte:fragment>
<svelte:fragment slot="description">
Other settings to customize your GitButler experience.
</svelte:fragment>
<SectionCard orientation="row" labelFor="allowForcePush">
<svelte:fragment slot="title">Allow force pushing</svelte:fragment>
<svelte:fragment slot="caption">
Force pushing allows GitButler to override branches even if they were pushed to remote.
GitButler will never force push to the target branch.
</svelte:fragment>
<svelte:fragment slot="actions">
<Toggle
id="allowForcePush"
checked={allowForcePushing}
on:click={handleAllowForcePushClick}
/>
</svelte:fragment>
</SectionCard>
<SectionCard orientation="row" labelFor="omitCertificateCheck">
<svelte:fragment slot="title">Ignore host certificate checks</svelte:fragment>
<svelte:fragment slot="caption">
Enabling this will ignore host certificate checks when authenticating with ssh.
</svelte:fragment>
<svelte:fragment slot="actions">
<Toggle
id="omitCertificateCheck"
checked={omitCertificateCheck}
on:click={handleOmitCertificateCheckClick}
/>
</svelte:fragment>
</SectionCard>
<SectionCard labelFor="runHooks" orientation="row">
<svelte:fragment slot="title">Run commit hooks</svelte:fragment>
<svelte:fragment slot="caption">
Enabling this will run any git pre and post commit hooks you have configured in your
repository.
</svelte:fragment>
<svelte:fragment slot="actions">
<Toggle id="runHooks" bind:checked={$runCommitHooks} />
</svelte:fragment>
</SectionCard>
<SectionCard orientation="row" centerAlign>
<svelte:fragment slot="title">Snapshot lines threshold</svelte:fragment>
<svelte:fragment slot="caption">
The number of lines that trigger a snapshot when saving.
</svelte:fragment>
<svelte:fragment slot="actions">
<TextBox
type="number"
width={100}
textAlign="center"
value={snaphotLinesThreshold?.toString()}
minVal={5}
maxVal={1000}
showCountActions
on:change={(e) => {
setSnapshotLinesThreshold(parseInt(e.detail));
}}
/>
</svelte:fragment>
</SectionCard>
<SectionCard labelFor="useNewLocking" orientation="row">
<svelte:fragment slot="title">Use new experimental hunk locking algorithm</svelte:fragment>
<svelte:fragment slot="caption">
This new hunk locking algorithm is still in the testing phase but should more accurately catch
locks and subsequently cause fewer errors.
</svelte:fragment>
<svelte:fragment slot="actions">
<Toggle id="useNewLocking" bind:checked={useNewLocking} />
</svelte:fragment>
</SectionCard>
<SectionCard labelFor="succeedingRebases" orientation="row">
<svelte:fragment slot="title">Edit mode and succeeding rebases</svelte:fragment>
<svelte:fragment slot="caption">
This is an experimental setting which will ensure that rebasing will always succeed,
introduces a mode for editing individual commits, and adds the ability to resolve conflicted
commits.
</svelte:fragment>
<svelte:fragment slot="actions">
<Toggle id="succeedingRebases" bind:checked={succeedingRebases} />
</svelte:fragment>
</SectionCard>
</Section>

View File

@ -0,0 +1,143 @@
<script lang="ts">
import { Project, ProjectService } from '$lib/backend/projects';
import SectionCard from '$lib/components/SectionCard.svelte';
import { projectRunCommitHooks } from '$lib/config/config';
import Section from '$lib/settings/Section.svelte';
import TextBox from '$lib/shared/TextBox.svelte';
import Toggle from '$lib/shared/Toggle.svelte';
import { getContext } from '$lib/utils/context';
const projectService = getContext(ProjectService);
const project = getContext(Project);
let snaphotLinesThreshold = project?.snapshot_lines_threshold || 20; // when undefined, the default is 20
let allowForcePushing = project?.ok_with_force_push;
let omitCertificateCheck = project?.omit_certificate_check;
let useNewLocking = project?.use_new_locking || false;
const runCommitHooks = projectRunCommitHooks(project.id);
async function setWithForcePush(value: boolean) {
project.ok_with_force_push = value;
await projectService.updateProject(project);
}
async function setOmitCertificateCheck(value: boolean | undefined) {
project.omit_certificate_check = !!value;
await projectService.updateProject(project);
}
async function setSnapshotLinesThreshold(value: number) {
project.snapshot_lines_threshold = value;
await projectService.updateProject(project);
}
let succeedingRebases = project.succeedingRebases;
$: {
project.succeedingRebases = succeedingRebases;
projectService.updateProject(project);
}
async function setUseNewLocking(value: boolean) {
project.use_new_locking = value;
await projectService.updateProject(project);
}
$: setUseNewLocking(useNewLocking);
async function handleAllowForcePushClick(event: MouseEvent) {
await setWithForcePush((event.target as HTMLInputElement)?.checked);
}
async function handleOmitCertificateCheckClick(event: MouseEvent) {
await setOmitCertificateCheck((event.target as HTMLInputElement)?.checked);
}
</script>
<Section gap={8}>
<SectionCard orientation="row" labelFor="allowForcePush">
<svelte:fragment slot="title">Allow force pushing</svelte:fragment>
<svelte:fragment slot="caption">
Force pushing allows GitButler to override branches even if they were pushed to remote.
GitButler will never force push to the target branch.
</svelte:fragment>
<svelte:fragment slot="actions">
<Toggle
id="allowForcePush"
checked={allowForcePushing}
on:click={handleAllowForcePushClick}
/>
</svelte:fragment>
</SectionCard>
<SectionCard orientation="row" labelFor="omitCertificateCheck">
<svelte:fragment slot="title">Ignore host certificate checks</svelte:fragment>
<svelte:fragment slot="caption">
Enabling this will ignore host certificate checks when authenticating with ssh.
</svelte:fragment>
<svelte:fragment slot="actions">
<Toggle
id="omitCertificateCheck"
checked={omitCertificateCheck}
on:click={handleOmitCertificateCheckClick}
/>
</svelte:fragment>
</SectionCard>
<SectionCard labelFor="runHooks" orientation="row">
<svelte:fragment slot="title">Run commit hooks</svelte:fragment>
<svelte:fragment slot="caption">
Enabling this will run any git pre and post commit hooks you have configured in your
repository.
</svelte:fragment>
<svelte:fragment slot="actions">
<Toggle id="runHooks" bind:checked={$runCommitHooks} />
</svelte:fragment>
</SectionCard>
<SectionCard orientation="row" centerAlign>
<svelte:fragment slot="title">Snapshot lines threshold</svelte:fragment>
<svelte:fragment slot="caption">
The number of lines that trigger a snapshot when saving.
</svelte:fragment>
<svelte:fragment slot="actions">
<TextBox
type="number"
width={100}
textAlign="center"
value={snaphotLinesThreshold?.toString()}
minVal={5}
maxVal={1000}
showCountActions
on:change={(e) => {
setSnapshotLinesThreshold(parseInt(e.detail));
}}
/>
</svelte:fragment>
</SectionCard>
<SectionCard labelFor="useNewLocking" orientation="row">
<svelte:fragment slot="title">Use new experimental hunk locking algorithm</svelte:fragment>
<svelte:fragment slot="caption">
This new hunk locking algorithm is still in the testing phase but should more accurately catch
locks and subsequently cause fewer errors.
</svelte:fragment>
<svelte:fragment slot="actions">
<Toggle id="useNewLocking" bind:checked={useNewLocking} />
</svelte:fragment>
</SectionCard>
<SectionCard labelFor="succeedingRebases" orientation="row">
<svelte:fragment slot="title">Edit mode and succeeding rebases</svelte:fragment>
<svelte:fragment slot="caption">
This is an experimental setting which will ensure that rebasing will always succeed,
introduces a mode for editing individual commits, and adds the ability to resolve conflicted
commits.
</svelte:fragment>
<svelte:fragment slot="actions">
<Toggle id="succeedingRebases" bind:checked={succeedingRebases} />
</svelte:fragment>
</SectionCard>
</Section>

View File

@ -0,0 +1,40 @@
<script lang="ts">
import { Project, ProjectService } from '$lib/backend/projects';
import RemoveProjectButton from '$lib/components/RemoveProjectButton.svelte';
import SectionCard from '$lib/components/SectionCard.svelte';
import { showError } from '$lib/notifications/toasts';
import { getContext } from '$lib/utils/context';
import * as toasts from '$lib/utils/toasts';
import { goto } from '$app/navigation';
const projectService = getContext(ProjectService);
const project = getContext(Project);
let isDeleting = $state(false);
async function onDeleteClicked() {
isDeleting = true;
try {
await projectService.deleteProject(project.id);
await projectService.reload();
goto('/');
toasts.success('Project deleted');
} catch (err: any) {
console.error(err);
showError('Failed to delete project', err);
} finally {
isDeleting = false;
}
}
</script>
<SectionCard>
<svelte:fragment slot="title">Remove project</svelte:fragment>
<svelte:fragment slot="caption">
You can remove projects from GitButler, your code remains safe as this only clears
configuration.
</svelte:fragment>
<div>
<RemoveProjectButton projectTitle={project.title} {isDeleting} {onDeleteClicked} />
</div>
</SectionCard>

View File

@ -7,7 +7,7 @@
dotted?: boolean;
}
const { margin = 16, noLine = false, dotted = false }: SpacerProps = $props();
const { margin = 12, noLine = false, dotted = false }: SpacerProps = $props();
function getMargins() {
if (margin === undefined) {
@ -29,7 +29,7 @@
.divider {
height: 1px;
width: 100%;
opacity: 0.2;
opacity: 0.13;
}
.divider.line {
@ -37,6 +37,7 @@
}
.divider.dotted {
opacity: 0.2;
background: repeating-linear-gradient(
90deg,
transparent,

View File

@ -193,6 +193,7 @@
}
.textbox__icon {
display: flex;
z-index: var(--z-ground);
pointer-events: none;
position: absolute;

View File

@ -1,74 +1,56 @@
<script lang="ts">
import { Project, ProjectService } from '$lib/backend/projects';
import BaseBranchSwitch from '$lib/components/BaseBranchSwitch.svelte';
import RemoveProjectButton from '$lib/components/RemoveProjectButton.svelte';
import SectionCard from '$lib/components/SectionCard.svelte';
import TabContent from '$lib/components/tabs/TabContent.svelte';
import TabList from '$lib/components/tabs/TabList.svelte';
import TabTrigger from '$lib/components/tabs/TabTrigger.svelte';
import Tabs from '$lib/components/tabs/Tabs.svelte';
import { featureBaseBranchSwitching } from '$lib/config/uiFeatureFlags';
import { getGitHost } from '$lib/gitHost/interface/gitHost';
import SettingsPage from '$lib/layout/SettingsPage.svelte';
import { showError } from '$lib/notifications/toasts';
import { platformName } from '$lib/platform/platform';
import CloudForm from '$lib/settings/CloudForm.svelte';
import DetailsForm from '$lib/settings/DetailsForm.svelte';
import KeysForm from '$lib/settings/KeysForm.svelte';
import PreferencesForm from '$lib/settings/PreferencesForm.svelte';
import PullRequestTemplateForm from '$lib/settings/PullRequestTemplateForm.svelte';
import Section from '$lib/settings/Section.svelte';
import BaseBranchSwitch from '$lib/settings/userPreferences/BaseBranchSwitch.svelte';
import CloudForm from '$lib/settings/userPreferences/CloudForm.svelte';
import CommitSigningForm from '$lib/settings/userPreferences/CommitSigningForm.svelte';
import DetailsForm from '$lib/settings/userPreferences/DetailsForm.svelte';
import PreferencesForm from '$lib/settings/userPreferences/PreferencesForm.svelte';
import RemoveProjectForm from '$lib/settings/userPreferences/RemoveProjectForm.svelte';
import Spacer from '$lib/shared/Spacer.svelte';
import { getContext } from '$lib/utils/context';
import * as toasts from '$lib/utils/toasts';
import { goto } from '$app/navigation';
const baseBranchSwitching = featureBaseBranchSwitching();
const projectService = getContext(ProjectService);
const project = getContext(Project);
const gitHost = getGitHost();
let deleteConfirmationModal: RemoveProjectButton;
let isDeleting = $state(false);
async function onDeleteClicked() {
isDeleting = true;
try {
await projectService.deleteProject(project.id);
await projectService.reload();
goto('/');
toasts.success('Project deleted');
} catch (err: any) {
console.error(err);
showError('Failed to delete project', err);
} finally {
isDeleting = false;
}
}
</script>
<SettingsPage title="Project settings">
{#if $baseBranchSwitching}
<BaseBranchSwitch />
{/if}
<CloudForm />
<DetailsForm />
{#if $gitHost}
<PullRequestTemplateForm />
{/if}
{#if $platformName !== 'win32'}
<KeysForm showProjectName={false} />
<Spacer />
{/if}
<PreferencesForm />
<SectionCard>
<svelte:fragment slot="title">Remove project</svelte:fragment>
<svelte:fragment slot="caption">
You can remove projects from GitButler, your code remains safe as this only clears
configuration.
</svelte:fragment>
<div>
<RemoveProjectButton
bind:this={deleteConfirmationModal}
projectTitle={project.title}
{isDeleting}
{onDeleteClicked}
/>
</div>
</SectionCard>
<Tabs defaultSelected="project">
<TabList>
<TabTrigger value="project">Project</TabTrigger>
<TabTrigger value="git">Git</TabTrigger>
<TabTrigger value="ai">AI</TabTrigger>
<TabTrigger value="feature-flags">Experimental</TabTrigger>
</TabList>
<TabContent value="git">
<Section>
<CommitSigningForm />
{#if $platformName !== 'win32'}
<Spacer />
<KeysForm showProjectName={false} />
{/if}
</Section>
</TabContent>
<TabContent value="ai">
<CloudForm />
</TabContent>
<TabContent value="project">
<Section>
{#if $baseBranchSwitching}
<BaseBranchSwitch />
{/if}
<DetailsForm />
<RemoveProjectForm />
</Section>
</TabContent>
<TabContent value="feature-flags">
<PreferencesForm />
</TabContent>
</Tabs>
</SettingsPage>

View File

@ -36,7 +36,7 @@
<button
bind:this={elRef}
{id}
class="segment"
class="segment-control-item"
role="tab"
{disabled}
tabindex={isSelected ? -1 : 0}
@ -62,12 +62,12 @@
}
}}
>
<span class="text-12 label">
<span class="text-12 segment-control-item__label">
{@render children()}
</span>
</button>
<style lang="postcss">
<!-- <style lang="postcss">
.segment {
cursor: pointer;
display: inline-flex;
@ -122,4 +122,4 @@
white-space: nowrap;
transition: color var(--transition-fast);
}
</style>
</style> -->

View File

@ -0,0 +1,54 @@
.segment-control-item {
cursor: pointer;
display: inline-flex;
flex-grow: 1;
flex-basis: 0;
align-items: center;
justify-content: center;
user-select: none;
padding: 0 8px;
gap: 4px;
border-top-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
color: var(--clr-text-1);
border-color: var(--clr-border-2);
background-color: var(--clr-bg-1);
height: var(--size-button);
transition: background var(--transition-fast);
&:first-of-type {
border-top-left-radius: var(--radius-m);
border-bottom-left-radius: var(--radius-m);
}
&:last-of-type {
border-top-right-radius: var(--radius-m);
border-bottom-right-radius: var(--radius-m);
border-right-width: 1px;
}
&:not([aria-selected='true']):hover {
background-color: var(--clr-bg-1-muted);
}
&[aria-selected='true'] {
background-color: var(--clr-bg-2);
color: var(--clr-text-2);
}
&:disabled {
cursor: default;
opacity: 0.5;
}
}
.segment-control-item__label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
transition: color var(--transition-fast);
}

View File

@ -17,6 +17,7 @@
@import './components/text-input.css';
@import './components/commit-lines.css';
@import './components/draggable.css';
@import './components/segment.css';
/* LAYERS PRIORITY */
@layer reset, sharable;