Skip to content

Mobile Tabs #97

Open
Open
@quangdaon

Description

@quangdaon

Is there a recommended approach to dynamically switching the selector style across multiple breakpoints? Specifically, I'd like to use the "Tabs" component while in desktop but switch to a "Listbox" on mobile.

I originally used both createListbox and createTabs to manage separate components, and used a writable store to try to synchronize the selected value, but that didn't work. I also tried to keep the selected value synchronized through event listeners, which worked for updating the listbox when the tabs is changed, but not for the reverse. What ultimately ended up working was using a single createListbox to control both elements and just styling the desktop view to look like tabs, but this seems hacky.

<script lang="ts">
	import { createEventDispatcher } from 'svelte';
	import { createListbox } from 'svelte-headlessui';

	const dispatch = createEventDispatcher();

	const tabOptions: Record<string, string> = {
		option1: 'Option 1',
		option2: 'Option 2',
		option3: 'Option 3'
	};

	const keys = Object.keys(tabOptions);

	const listbox = createListbox({ label: 'Post Type', selected: keys[0] });

	const handleListboxChange = (e: Event) => {
		dispatch('changed', (e as CustomEvent).detail.selected);
	};

	const select = (value: string) => {
		listbox.set({ selected: value });
	};
</script>

<div class="hidden md:flex w-full flex-col">
	<div
		use:listbox.button
		class="flex w-dull rounded-md bg-white divide-x divide-gray-200 overflow-clip"
	>
		{#each keys as value}
			{@const active = $listbox.active === value}
			{@const selected = $listbox.selected === value}
			<button
				use:listbox.item={{ value }}
				on:click={() => select(value)}
				class="w-full font-medium m-0 focus:outline-none"
			>
				<span
					class="block py-4 text-sm border-x-0 border-b-4 border-transparent {selected
						? 'border-b-blue-500 font-bold'
						: active
							? ''
							: 'hover:border-b-gray-200 hover:bg-gray-50'}"
				>
					{tabOptions[value]}
				</span>
			</button>
		{/each}
	</div>
</div>

<div class="block relative md:hidden px-4">
	<button
		use:listbox.button
		on:change={handleListboxChange}
		class="relative w-full flex justify-between items-center cursor-default rounded-md bg-white py-2 px-3 text-left text-sm shadow-sm focus:outline-none"
	>
		<span class="block truncate">{tabOptions[$listbox.selected]}</span>
	</button>

	{#if $listbox.expanded}
		<ul
			use:listbox.items
			class="absolute mt-1 left-4 right-4 overflow-auto rounded-md bg-white py-1 text-sm shadow-sm focus:outline-none"
		>
			{#each keys as value}
				{@const selected = $listbox.selected === value}
				<li
					class="relative cursor-default select-none py-2 px-4 {selected ? 'bg-gray-50' : ''}"
					use:listbox.item={{ value }}
				>
					<span class="block truncate {selected ? 'font-bold' : 'font-normal'}">
						{tabOptions[value]}
					</span>
				</li>
			{/each}
		</ul>
	{/if}
</div>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions