segment control added (#4544)

This commit is contained in:
Pavel Laptev 2024-07-30 17:08:02 +02:00 committed by GitHub
parent ab0c70b673
commit 6194a58f5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 214 additions and 0 deletions

View 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>

View 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>

View 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;
}

View File

@ -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
}
};

View 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>