Skip to content

Mobile Tabs #97

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>

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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