mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-26 12:24:26 +03:00
segment control added (#4544)
This commit is contained in:
parent
ab0c70b673
commit
6194a58f5b
110
packages/ui/src/lib/SegmentControl/Segment.svelte
Normal file
110
packages/ui/src/lib/SegmentControl/Segment.svelte
Normal file
@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from 'svelte';
|
||||
import type { SegmentContext } from './segmentTypes';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface SegmentProps {
|
||||
id: string;
|
||||
onselect?: (id: string) => void;
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
const { id, onselect, children }: SegmentProps = $props();
|
||||
|
||||
const context = getContext<SegmentContext>('SegmentControl');
|
||||
const index = context.setIndex();
|
||||
const selectedSegmentIndex = context.selectedSegmentIndex;
|
||||
|
||||
let elRef = $state<HTMLButtonElement>();
|
||||
let isFocused = $state(false);
|
||||
let isSelected = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
elRef && isFocused && elRef.focus();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
isSelected = $selectedSegmentIndex === index;
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
context.addSegment({ index });
|
||||
});
|
||||
</script>
|
||||
|
||||
<button
|
||||
bind:this={elRef}
|
||||
{id}
|
||||
class="segment"
|
||||
role="tab"
|
||||
tabindex={isSelected ? -1 : 0}
|
||||
aria-selected={isSelected}
|
||||
onmousedown={() => {
|
||||
if (index !== $selectedSegmentIndex) {
|
||||
context.setSelected({
|
||||
index,
|
||||
id
|
||||
});
|
||||
onselect && onselect(id);
|
||||
}
|
||||
}}
|
||||
onkeydown={({ key }) => {
|
||||
if (key === 'Enter' || key === ' ') {
|
||||
if (index !== $selectedSegmentIndex) {
|
||||
context.setSelected({
|
||||
index,
|
||||
id
|
||||
});
|
||||
onselect && onselect(id);
|
||||
}
|
||||
}
|
||||
}}
|
||||
><span class="text-base-12 label">
|
||||
{@render children()}
|
||||
</span></button
|
||||
>
|
||||
|
||||
<style lang="postcss">
|
||||
.segment {
|
||||
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);
|
||||
}
|
||||
}
|
||||
</style>
|
53
packages/ui/src/lib/SegmentControl/SegmentControl.svelte
Normal file
53
packages/ui/src/lib/SegmentControl/SegmentControl.svelte
Normal file
@ -0,0 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { setContext } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { SegmentContext, SegmentItem } from './segmentTypes';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface SegmentProps {
|
||||
selectedIndex: number;
|
||||
fullWidth?: boolean;
|
||||
onselect?: (id: string) => void;
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
const { selectedIndex, fullWidth, onselect, children }: SegmentProps = $props();
|
||||
|
||||
let indexesIterator = -1;
|
||||
let segments: SegmentItem[] = [];
|
||||
|
||||
let selectedSegmentIndex = writable(selectedIndex);
|
||||
|
||||
const context: SegmentContext = {
|
||||
selectedSegmentIndex,
|
||||
setIndex: () => {
|
||||
indexesIterator += 1;
|
||||
return indexesIterator;
|
||||
},
|
||||
addSegment: ({ index }) => {
|
||||
segments = [...segments, { index }];
|
||||
},
|
||||
setSelected: ({ index: segmentIndex, id }) => {
|
||||
if (segmentIndex >= 0 && segmentIndex < segments.length) {
|
||||
$selectedSegmentIndex = segmentIndex;
|
||||
onselect && onselect(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setContext<SegmentContext>('SegmentControl', context);
|
||||
</script>
|
||||
|
||||
<div class="wrapper" class:full-width={fullWidth}>
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.wrapper {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.wrapper.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
11
packages/ui/src/lib/SegmentControl/segmentTypes.ts
Normal file
11
packages/ui/src/lib/SegmentControl/segmentTypes.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export interface SegmentItem {
|
||||
index: number;
|
||||
}
|
||||
export interface SegmentContext {
|
||||
selectedSegmentIndex: Writable<number>;
|
||||
setIndex(): number;
|
||||
addSegment(segment: SegmentItem): void;
|
||||
setSelected({ index, id }: { index: number; id: string }): void;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import SegmentControl from './SegmentControl.svelte';
|
||||
import type { Meta, StoryObj } from '@storybook/svelte';
|
||||
|
||||
const meta = {
|
||||
component: SegmentControl
|
||||
} satisfies Meta<SegmentControl>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const SegmentControlStory: Story = {
|
||||
args: {
|
||||
selectedIndex: 1,
|
||||
fullWidth: false
|
||||
}
|
||||
};
|
24
packages/ui/src/stories/SegmentControl/SegmentControl.svelte
Normal file
24
packages/ui/src/stories/SegmentControl/SegmentControl.svelte
Normal file
@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import Segment from '$lib/SegmentControl/Segment.svelte';
|
||||
import SegmentControl from '$lib/SegmentControl/SegmentControl.svelte';
|
||||
|
||||
interface Props {
|
||||
selectedIndex: number;
|
||||
fullWidth?: boolean;
|
||||
}
|
||||
|
||||
const { selectedIndex, fullWidth }: Props = $props();
|
||||
</script>
|
||||
|
||||
<SegmentControl
|
||||
{selectedIndex}
|
||||
{fullWidth}
|
||||
onselect={(id) => {
|
||||
console.log('Selected index:', id);
|
||||
}}
|
||||
>
|
||||
<Segment id="first">First</Segment>
|
||||
<Segment id="second">Second</Segment>
|
||||
<Segment id="third">Third</Segment>
|
||||
<Segment id="fourth">Fourth</Segment>
|
||||
</SegmentControl>
|
Loading…
Reference in New Issue
Block a user