HextaUI

Collapsible

Yüksekliği hareketle gösterilip gizlenen, yarıda tersine çevrilebilen ve yerleşimi zıplatmayan bir panel.

@preetsuthar17 starred 3 repositories

@radix-ui/primitives
import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  CollapsibleTriggerIcon,
} from "@/components/ui/collapsible"

export function CollapsibleDemo() {
  return (
    <Collapsible className="flex w-full max-w-sm flex-col gap-2">
      <div className="flex items-center justify-between gap-4 ps-4">
        <h4 className="text-sm font-semibold">
          <span dir="ltr">@preetsuthar17</span> starred 3 repositories
        </h4>
        <CollapsibleTrigger
          render={<Button variant="ghost" size="icon-sm" />}
          aria-label="Toggle repositories"
        >
          <CollapsibleTriggerIcon />
        </CollapsibleTrigger>
      </div>
      <div className="rounded-md border px-4 py-2 font-mono text-sm">
        <span dir="ltr">@radix-ui/primitives</span>
      </div>
      <CollapsibleContent className="flex flex-col gap-2">
        <div className="rounded-md border px-4 py-2 font-mono text-sm">
          <span dir="ltr">@base-ui/react</span>
        </div>
        <div className="rounded-md border px-4 py-2 font-mono text-sm">
          <span dir="ltr">@tabler/icons</span>
        </div>
      </CollapsibleContent>
    </Collapsible>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/collapsible.json

Bileşeni, HextaUI tema token'larını ve bileşenin bağımlı olduğu tüm HextaUI bileşenlerini ekler.

import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  CollapsibleTriggerIcon,
} from "@/components/ui/collapsible"
<Collapsible>
  <CollapsibleTrigger render={<Button variant="outline" />}>
    Details
    <CollapsibleTriggerIcon data-icon="inline-end" />
  </CollapsibleTrigger>
  <CollapsibleContent>
    Shipping takes 2 business days.
  </CollapsibleContent>
</Collapsible>
Collapsible
├── CollapsibleTrigger
│   └── CollapsibleTriggerIcon
└── CollapsibleContent

Varsayılan olarak açık

defaultOpen paneli state'ini devralmadan açık başlatır.

import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  CollapsibleTriggerIcon,
} from "@/components/ui/collapsible"

export function CollapsibleOpenByDefault() {
  return (
    <Collapsible defaultOpen className="flex w-full max-w-sm flex-col gap-2">
      <div className="flex items-center justify-between gap-4 ps-4">
        <h4 className="text-sm font-semibold">
          <span dir="ltr">@preetsuthar17</span> starred 3 repositories
        </h4>
        <CollapsibleTrigger
          render={<Button variant="ghost" size="icon-sm" />}
          aria-label="Toggle repositories"
        >
          <CollapsibleTriggerIcon />
        </CollapsibleTrigger>
      </div>
      <div className="rounded-md border px-4 py-2 font-mono text-sm">
        <span dir="ltr">@radix-ui/primitives</span>
      </div>
      <CollapsibleContent className="flex flex-col gap-2">
        <div className="rounded-md border px-4 py-2 font-mono text-sm">
          <span dir="ltr">@base-ui/react</span>
        </div>
        <div className="rounded-md border px-4 py-2 font-mono text-sm">
          <span dir="ltr">@tabler/icons</span>
        </div>
      </CollapsibleContent>
    </Collapsible>
  )
}

Daha fazla göster

Panel trigger'ının üstünde durur. Kapandığında üst öğenin flex düzenindeki boşluk aynı animasyonda daralır; böylece alttaki düğme asla zıplamaz.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  CollapsibleTriggerIcon,
} from "@/components/ui/collapsible"

export function CollapsibleShowMore() {
  const [open, setOpen] = React.useState(false)

  return (
    <Collapsible
      open={open}
      onOpenChange={setOpen}
      className="flex max-w-md flex-col gap-3"
    >
      <p className="text-sm text-muted-foreground">
        HextaUI components are built on Base UI primitives and styled with
        Tailwind. You own the source, so every class can be changed.
      </p>
      <CollapsibleContent>
        <p className="text-sm text-muted-foreground">
          Motion is interruptible: clicking again mid-animation reverses from
          where the panel is instead of snapping. The gap between this paragraph
          and its neighbours collapses in the same animation, so nothing below
          jumps when it finishes.
        </p>
      </CollapsibleContent>
      <div>
        <CollapsibleTrigger render={<Button variant="outline" size="sm" />}>
          {open ? "Show less" : "Show more"}
          <CollapsibleTriggerIcon data-icon="inline-end" />
        </CollapsibleTrigger>
      </div>
    </Collapsible>
  )
}

Kontrollü

Paneli kendi state'inizle yönetmek için open ve onOpenChange geçin. Animasyon sırasında değiştirmek, panelin bulunduğu yerden geri döner.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  CollapsibleTriggerIcon,
} from "@/components/ui/collapsible"

export function CollapsibleControlled() {
  const [open, setOpen] = React.useState(false)

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <div className="flex items-center gap-2">
        <Button variant="outline" size="sm" onClick={() => setOpen(true)}>
          Open
        </Button>
        <Button variant="outline" size="sm" onClick={() => setOpen(false)}>
          Close
        </Button>
        <span className="text-sm text-muted-foreground">
          {open ? "Open" : "Closed"}
        </span>
      </div>
      <Collapsible
        open={open}
        onOpenChange={setOpen}
        className="flex flex-col gap-2"
      >
        <div className="flex items-center justify-between gap-4 ps-4">
          <h4 className="text-sm font-semibold">Release notes</h4>
          <CollapsibleTrigger
            render={<Button variant="ghost" size="icon-sm" />}
            aria-label="Toggle release notes"
          >
            <CollapsibleTriggerIcon />
          </CollapsibleTrigger>
        </div>
        <div className="rounded-md border px-4 py-2 text-sm">v2.4.0</div>
        <CollapsibleContent className="flex flex-col gap-2">
          <div className="rounded-md border px-4 py-2 text-sm">v2.3.1</div>
          <div className="rounded-md border px-4 py-2 text-sm">v2.3.0</div>
        </CollapsibleContent>
      </Collapsible>
    </div>
  )
}

Devre dışı

Kökteki disabled paneli mevcut durumunda tutar ve trigger'ın tıklamaları ve tuşları yok saymasını sağlar.

import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  CollapsibleTriggerIcon,
} from "@/components/ui/collapsible"

export function CollapsibleDisabled() {
  return (
    <Collapsible disabled className="flex w-full max-w-sm flex-col gap-2">
      <div className="flex items-center justify-between gap-4 ps-4">
        <h4 className="text-sm font-semibold">
          <span dir="ltr">@preetsuthar17</span> starred 3 repositories
        </h4>
        <CollapsibleTrigger
          render={<Button variant="ghost" size="icon-sm" />}
          aria-label="Toggle repositories"
        >
          <CollapsibleTriggerIcon />
        </CollapsibleTrigger>
      </div>
      <div className="rounded-md border px-4 py-2 font-mono text-sm">
        <span dir="ltr">@radix-ui/primitives</span>
      </div>
      <CollapsibleContent className="flex flex-col gap-2">
        <div className="rounded-md border px-4 py-2 font-mono text-sm">
          <span dir="ltr">@base-ui/react</span>
        </div>
        <div className="rounded-md border px-4 py-2 font-mono text-sm">
          <span dir="ltr">@tabler/icons</span>
        </div>
      </CollapsibleContent>
    </Collapsible>
  )
}

Özel simge

<CollapsibleTriggerIcon /> bileşeninin alt öğeleri oku değiştirir. Açık durumunu group-data-panel-open/collapsible-trigger ile stilleyin.

import { IconPlus } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  CollapsibleTriggerIcon,
} from "@/components/ui/collapsible"

export function CollapsibleCustomIcon() {
  return (
    <Collapsible className="flex w-full max-w-sm flex-col gap-2">
      <CollapsibleTrigger
        render={<Button variant="ghost" />}
        className="self-start"
      >
        <CollapsibleTriggerIcon data-icon="inline-start">
          <IconPlus className="transition-transform duration-200 ease-out-cubic group-data-panel-open/collapsible-trigger:rotate-45 motion-reduce:transition-none" />
        </CollapsibleTriggerIcon>
        Add a note
      </CollapsibleTrigger>
      <CollapsibleContent>
        <p className="ps-3 text-sm text-muted-foreground">
          Notes are only visible to you.
        </p>
      </CollapsibleContent>
    </Collapsible>
  )
}

Dosya ağacı

Her klasör kendi collapsible'ıdır. İç paneller açıldıkça dış paneller düzgünce büyür.

import { IconChevronRight, IconFile, IconFolder } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  CollapsibleTriggerIcon,
} from "@/components/ui/collapsible"

type TreeNode = { name: string; children?: TreeNode[] }

const tree: TreeNode[] = [
  {
    name: "components",
    children: [
      {
        name: "ui",
        children: [
          { name: "button.tsx" },
          { name: "collapsible.tsx" },
          { name: "card.tsx" },
        ],
      },
      { name: "theme-provider.tsx" },
    ],
  },
  { name: "app", children: [{ name: "layout.tsx" }, { name: "page.tsx" }] },
  { name: "package.json" },
]

function TreeItem({ node }: { node: TreeNode }) {
  if (!node.children) {
    return (
      <li className="flex h-8 items-center gap-2 ps-8 text-sm">
        <IconFile className="size-4 text-muted-foreground" />
        {node.name}
      </li>
    )
  }

  return (
    <li>
      <Collapsible defaultOpen={node.name === "components"}>
        <CollapsibleTrigger
          render={<Button variant="ghost" size="sm" />}
          className="w-full justify-start"
        >
          <CollapsibleTriggerIcon>
            <IconChevronRight className="transition-transform duration-200 ease-out-cubic group-data-panel-open/collapsible-trigger:rotate-90 motion-reduce:transition-none rtl:-scale-x-100 rtl:group-data-panel-open/collapsible-trigger:-rotate-90" />
          </CollapsibleTriggerIcon>
          <IconFolder className="text-muted-foreground" />
          {node.name}
        </CollapsibleTrigger>
        <CollapsibleContent>
          <ul className="ms-4 border-s ps-1">
            {node.children.map((child) => (
              <TreeItem key={child.name} node={child} />
            ))}
          </ul>
        </CollapsibleContent>
      </Collapsible>
    </li>
  )
}

export function CollapsibleFileTree() {
  return (
    <ul className="w-64">
      {tree.map((node) => (
        <TreeItem key={node.name} node={node} />
      ))}
    </ul>
  )
}

Bir kartın içinde

Kapalı paneller hidden=“until-found” ile sayfada kalır; bu yüzden Cmd/Ctrl+F ile “webhook” aramak bunu açar.

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  CollapsibleTriggerIcon,
} from "@/components/ui/collapsible"

export function CollapsibleCard() {
  return (
    <Card className="w-full max-w-md">
      <CardHeader>
        <CardTitle>Notifications</CardTitle>
        <CardDescription>Choose where alerts are sent.</CardDescription>
      </CardHeader>
      <CardContent>
        <Collapsible className="flex flex-col gap-3">
          <CollapsibleTrigger
            render={<Button variant="outline" />}
            className="justify-between"
          >
            Advanced settings
            <CollapsibleTriggerIcon data-icon="inline-end" />
          </CollapsibleTrigger>
          <CollapsibleContent className="flex flex-col gap-2 text-sm text-muted-foreground">
            <p>Send a webhook to your endpoint for every alert.</p>
            <p>Retry failed deliveries up to five times with backoff.</p>
            <p>Group alerts that arrive within 30 seconds.</p>
          </CollapsibleContent>
        </Collapsible>
      </CardContent>
    </Card>
  )
}

Kapalıyken kaldırılır (unmount)

Kapalıyken DOM'dan kaldırmak için içerikte hiddenUntilFound={false} ayarlayın; baştan render edilmesini istemediğiniz ağır paneller için.

import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  CollapsibleTriggerIcon,
} from "@/components/ui/collapsible"

export function CollapsibleUnmounted() {
  return (
    <Collapsible className="flex w-full max-w-sm flex-col gap-2">
      <CollapsibleTrigger
        render={<Button variant="secondary" />}
        className="self-start"
      >
        Details
        <CollapsibleTriggerIcon data-icon="inline-end" />
      </CollapsibleTrigger>
      <CollapsibleContent hiddenUntilFound={false}>
        <p className="text-sm text-muted-foreground">
          Rendered only while open.
        </p>
      </CollapsibleContent>
    </Collapsible>
  )
}

Uzun içerik

Kırılmayan dizeler sayfayı genişletmek yerine panelin içinde sarar.

import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  CollapsibleTriggerIcon,
} from "@/components/ui/collapsible"

export function CollapsibleLongContent() {
  return (
    <Collapsible className="flex w-72 max-w-full flex-col gap-2">
      <CollapsibleTrigger
        render={<Button variant="outline" />}
        className="justify-between"
      >
        Token
        <CollapsibleTriggerIcon data-icon="inline-end" />
      </CollapsibleTrigger>
      <CollapsibleContent className="font-mono text-sm wrap-anywhere">
        hx_demo_7Kq2vX9mLpR4tYw8NzB3cF6jHs1DgQe5UaV0iMoTnWbZrE2yPkLxJ4
      </CollapsibleContent>
    </Collapsible>
  )
}

Sağdan sola

Oklar yansıtılır ve iç içe girintiler okuma yönünü izler.

import { IconChevronRight, IconFile, IconFolder } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  CollapsibleTriggerIcon,
} from "@/components/ui/collapsible"

type TreeNode = { name: string; children?: TreeNode[] }

const tree: TreeNode[] = [
  {
    name: "components",
    children: [
      {
        name: "ui",
        children: [
          { name: "button.tsx" },
          { name: "collapsible.tsx" },
          { name: "card.tsx" },
        ],
      },
      { name: "theme-provider.tsx" },
    ],
  },
  { name: "app", children: [{ name: "layout.tsx" }, { name: "page.tsx" }] },
  { name: "package.json" },
]

function TreeItem({ node }: { node: TreeNode }) {
  if (!node.children) {
    return (
      <li className="flex h-8 items-center gap-2 ps-8 text-sm">
        <IconFile className="size-4 text-muted-foreground" />
        {node.name}
      </li>
    )
  }

  return (
    <li>
      <Collapsible defaultOpen={node.name === "components"}>
        <CollapsibleTrigger
          render={<Button variant="ghost" size="sm" />}
          className="w-full justify-start"
        >
          <CollapsibleTriggerIcon>
            <IconChevronRight className="transition-transform duration-200 ease-out-cubic group-data-panel-open/collapsible-trigger:rotate-90 motion-reduce:transition-none rtl:-scale-x-100 rtl:group-data-panel-open/collapsible-trigger:-rotate-90" />
          </CollapsibleTriggerIcon>
          <IconFolder className="text-muted-foreground" />
          {node.name}
        </CollapsibleTrigger>
        <CollapsibleContent>
          <ul className="ms-4 border-s ps-1">
            {node.children.map((child) => (
              <TreeItem key={child.name} node={child} />
            ))}
          </ul>
        </CollapsibleContent>
      </Collapsible>
    </li>
  )
}

export function CollapsibleRtl() {
  return (
    <div dir="rtl" className="flex w-full max-w-sm flex-col gap-8">
      <Collapsible className="flex flex-col gap-2">
        <div className="flex items-center justify-between gap-4 ps-4">
          <h4 className="text-sm font-semibold">المستودعات المميزة</h4>
          <CollapsibleTrigger
            render={<Button variant="ghost" size="icon-sm" />}
            aria-label="إظهار المستودعات"
          >
            <CollapsibleTriggerIcon />
          </CollapsibleTrigger>
        </div>
        <div className="rounded-md border px-4 py-2 font-mono text-sm">
          <span dir="ltr">@radix-ui/primitives</span>
        </div>
        <CollapsibleContent className="flex flex-col gap-2">
          <div className="rounded-md border px-4 py-2 font-mono text-sm">
            <span dir="ltr">@base-ui/react</span>
          </div>
          <div className="rounded-md border px-4 py-2 font-mono text-sm">
            <span dir="ltr">@tabler/icons</span>
          </div>
        </CollapsibleContent>
      </Collapsible>
      <ul className="w-64">
        {tree.map((node) => (
          <TreeItem key={node.name} node={node} />
        ))}
      </ul>
    </div>
  )
}
TuşAction
EnterSpacePaneli trigger'dan açar veya kapatır.
TabOdağı trigger'dan açık panelin içine taşır. Kapalı paneller atlanır.
  • Trigger otomatik olarak aria-expanded ve aria-controls alır.
  • Yalnızca simgeli trigger'lar bir aria-label gerektirir. <CollapsibleTriggerIcon /> yardımcı teknolojilerden gizlenir.
  • Azaltılmış harekette panel, yüksekliğini animasyonlamadan açılır ve kapanır.

Base UI collapsible üzerine kuruludur. Her parça, sardığı primitive'in prop'larını kabul eder.

PropTürVarsayılan
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean, details) => void–
disabled
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
ÖznitelikAçıklama
data-slot="collapsible"CSS'te kökü hedefleyin.
data-openAçıkken bulunur.
data-closedKapalıyken bulunur.
PropTürVarsayılan
renderGenellikle bir Button.
ReactElement | (props, state) => ReactElement<button>
ÖznitelikAçıklama
data-slot="collapsible-trigger"CSS'te tetikleyiciyi hedefleyin.
data-panel-openPanel açıkken bulunur. Alt öğeleri group-data-panel-open/collapsible-trigger ile stilleyin.
PropTürVarsayılan
childrenPanel açıldığında varsayılan ok ters döner.
ReactNode<IconChevronDown />
render
ReactElement | (props, state) => ReactElement<span>
ÖznitelikAçıklama
data-slot="collapsible-trigger-icon"Yardımcı teknolojilerden gizlenir. Trigger basılıyken aşağı kayar.
PropTürVarsayılan
classNameİç sarmalayıcıya uygulanır; böylece dolgu hiçbir zaman yükseklik animasyonuyla çakışmaz.
string–
hiddenUntilFoundKapalı paneli DOM'da tutar; böylece sayfa içi arama onu bulup açabilir.
booleantrue
keepMountedhiddenUntilFound açıkken yok sayılır.
booleanfalse
ÖznitelikAçıklama
data-slot="collapsible-content"CSS'te paneli hedefleyin.
data-openAçıkken bulunur.
data-closedKapalıyken bulunur.
data-starting-stylePanel açılma animasyonu sırasında bulunur.
data-ending-stylePanel kapanma animasyonu sırasında bulunur.
--collapsible-panel-heightPanelin ölçülen yüksekliği.
--collapsible-panel-widthPanelin ölçülen genişliği.
--collapsible-gap-startÜst öğenin panelden önceki boşluğu; kapalıyken daralır.
--collapsible-gap-endÜst öğenin panelden sonraki boşluğu; kapalıyken daralır.

Kullanıldığı bloklar

Collapsible üzerine kurulan bloklar.