Gallery/Layout/Shared layout โ€” tabs

Shared layout โ€” tabs

layoutId slides the active underline; AnimatePresence swaps the body.

layoutId
๐Ÿ…
shared-layout-tabs.tsx
import { AnimatePresence, motion } from 'motion-solidjs'
import { For, Show, createSignal } from 'solid-js'

export const meta = {
  slug: 'shared-layout-tabs',
  title: 'Shared layout โ€” tabs',
  category: 'layout',
  description: 'layoutId slides the active underline; AnimatePresence swaps the body.',
  tag: 'layoutId',
} as const

const tabs = [
  { id: 'tomato', icon: '๐Ÿ…', label: 'Tomato' },
  { id: 'lettuce', icon: '๐Ÿฅฌ', label: 'Lettuce' },
  { id: 'cheese', icon: '๐Ÿง€', label: 'Cheese' },
] as const

type TabId = (typeof tabs)[number]['id']

export default function SharedLayoutTabs() {
  const [active, setActive] = createSignal<TabId>('tomato')
  const selected = () => tabs.find((t) => t.id === active())!

  return (
    <div class="flex w-72 flex-col overflow-hidden rounded-2xl border border-border bg-card">
      <nav class="border-b border-border px-1.5 pt-1.5">
        <ul class="flex gap-1" style={{ 'list-style': 'none', padding: 0, margin: 0 }}>
          <For each={tabs}>
            {(t) => (
              <motion.li
                class="relative flex flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-t-lg px-3 py-2 text-xs font-medium select-none"
                initial={false}
                animate={{
                  backgroundColor:
                    t.id === active() ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0)',
                }}
                onClick={() => setActive(t.id)}
              >
                <span>{t.icon}</span>
                <span>{t.label}</span>
                <Show when={t.id === active()}>
                  <motion.span
                    layoutId="shared-layout-tabs-underline"
                    class="absolute right-0 -bottom-px left-0 h-[2px] bg-grad-rose"
                    transition={{ type: 'spring', stiffness: 380, damping: 30 }}
                  />
                </Show>
              </motion.li>
            )}
          </For>
        </ul>
      </nav>
      <main class="flex h-32 items-center justify-center">
        <AnimatePresence mode="wait">
          <Show when={selected()} keyed>
            {(tab) => (
              <motion.div
                initial={{ y: 10, opacity: 0 }}
                animate={{ y: 0, opacity: 1 }}
                exit={{ y: -10, opacity: 0 }}
                transition={{ duration: 0.2 }}
                class="text-6xl"
              >
                {tab.icon}
              </motion.div>
            )}
          </Show>
        </AnimatePresence>
      </main>
    </div>
  )
}