HextaUI

BetaGebaut auf Vercels UI SDK 0.1.0-beta.2. Die API kann sich ändern.

Segmented control

Ein Segmented Control auf Vercels UI SDK mit gleitender Markierung, drei Größen, reinen Symbol-Elementen, voller Breite, vertikalem Layout und Radiogruppen-Tastatur.

Abhängig von
  • @ui-sdk/core
  • @ui-sdk/react
import { IconLayoutGrid, IconLayoutKanban, IconList } from "@tabler/icons-react"

import {
  SegmentedControl,
  SegmentedControlItem,
} from "@/components/ui/ui-sdk-segmented-control"

function UiSdkSegmentedControlDemo() {
  return (
    <SegmentedControl aria-label="View" defaultValue="list">
      <SegmentedControlItem value="list">
        <IconList aria-hidden="true" />
        List
      </SegmentedControlItem>
      <SegmentedControlItem value="grid">
        <IconLayoutGrid aria-hidden="true" />
        Grid
      </SegmentedControlItem>
      <SegmentedControlItem value="board">
        <IconLayoutKanban aria-hidden="true" />
        Board
      </SegmentedControlItem>
    </SegmentedControl>
  )
}

export { UiSdkSegmentedControlDemo }
pnpm dlx shadcn@latest add https://hextaui.com/r/ui-sdk-segmented-control.json

Fügt die Komponente, die HextaUI-Theme-Tokens und alle HextaUI-Komponenten hinzu, von denen sie abhängt.

UI SDK bringt etwas eigenes CSS in einem Cascade Layer namens ui-sdk mit. Deklariere diesen Layer ganz oben im globalen Stylesheet, über dem Tailwind-Import, damit deine Klassen gewinnen.

@layer ui-sdk;

@import "tailwindcss";
import {
  SegmentedControl,
  SegmentedControlItem,
} from "@/components/ui/ui-sdk-segmented-control"
<SegmentedControl aria-label="View" defaultValue="list">
  <SegmentedControlItem value="list">List</SegmentedControlItem>
  <SegmentedControlItem value="grid">Grid</SegmentedControlItem>
</SegmentedControl>

Ein Segmented Control wechselt zwischen wenigen Ansichten oder Einstellungen. Es funktioniert wie eine Radiogruppe mit einem Wert und einem Tab-Stopp, und die Markierung hinter dem gewählten Element gleitet zur neuen Wahl. Benenne es mit aria-label oder aria-labelledby. UI SDK markiert es in 0.1.0-beta.2 als experimentell.

SegmentedControl
├── SegmentedControlItem
└── SegmentedControlItem

Das Control zeichnet seine Markierung selbst, du fügst nur Elemente hinzu.

size ist sm, default oder lg. Auf Touchscreens werden die Elemente größer.

import {
  SegmentedControl,
  SegmentedControlItem,
} from "@/components/ui/ui-sdk-segmented-control"

const sizes = ["sm", "default", "lg"] as const

function UiSdkSegmentedControlSizes() {
  return (
    <div className="flex flex-col items-center gap-4">
      {sizes.map((size) => (
        <SegmentedControl
          key={size}
          aria-label={`Effort, ${size}`}
          size={size}
          defaultValue="balanced"
        >
          <SegmentedControlItem value="fast">Fast</SegmentedControlItem>
          <SegmentedControlItem value="balanced">Balanced</SegmentedControlItem>
          <SegmentedControlItem value="thorough">Thorough</SegmentedControlItem>
        </SegmentedControl>
      ))}
    </div>
  )
}

export { UiSdkSegmentedControlSizes }

Ein Element mit nur einem Symbol wird quadratisch. Benenne es mit aria-label.

import {
  IconAlignCenter,
  IconAlignJustified,
  IconAlignLeft,
  IconAlignRight,
} from "@tabler/icons-react"

import {
  SegmentedControl,
  SegmentedControlItem,
} from "@/components/ui/ui-sdk-segmented-control"

function UiSdkSegmentedControlIcons() {
  return (
    <SegmentedControl aria-label="Text alignment" defaultValue="left">
      <SegmentedControlItem value="left" aria-label="Align left">
        <IconAlignLeft aria-hidden="true" />
      </SegmentedControlItem>
      <SegmentedControlItem value="center" aria-label="Align center">
        <IconAlignCenter aria-hidden="true" />
      </SegmentedControlItem>
      <SegmentedControlItem value="right" aria-label="Align right">
        <IconAlignRight aria-hidden="true" />
      </SegmentedControlItem>
      <SegmentedControlItem value="justify" aria-label="Justify">
        <IconAlignJustified aria-hidden="true" />
      </SegmentedControlItem>
    </SegmentedControl>
  )
}

export { UiSdkSegmentedControlIcons }

Gib dem Control w-full, und die Elemente teilen sich seine Breite.

import {
  SegmentedControl,
  SegmentedControlItem,
} from "@/components/ui/ui-sdk-segmented-control"

function UiSdkSegmentedControlFullWidth() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <p id="billing-period" className="m-0 text-sm font-medium">
        Billing period
      </p>
      <SegmentedControl
        aria-labelledby="billing-period"
        defaultValue="yearly"
        className="w-full"
      >
        <SegmentedControlItem value="monthly">Monthly</SegmentedControlItem>
        <SegmentedControlItem value="yearly">Yearly</SegmentedControlItem>
      </SegmentedControl>
    </div>
  )
}

export { UiSdkSegmentedControlFullWidth }

orientation="vertical" stapelt die Elemente. Pfeiltasten beider Achsen wechseln weiter zwischen ihnen.

import {
  IconLayoutAlignBottom,
  IconLayoutAlignMiddle,
  IconLayoutAlignTop,
} from "@tabler/icons-react"

import {
  SegmentedControl,
  SegmentedControlItem,
} from "@/components/ui/ui-sdk-segmented-control"

function UiSdkSegmentedControlVertical() {
  return (
    <SegmentedControl
      aria-label="Vertical alignment"
      orientation="vertical"
      defaultValue="middle"
    >
      <SegmentedControlItem value="top">
        <IconLayoutAlignTop aria-hidden="true" />
        Top
      </SegmentedControlItem>
      <SegmentedControlItem value="middle">
        <IconLayoutAlignMiddle aria-hidden="true" />
        Middle
      </SegmentedControlItem>
      <SegmentedControlItem value="bottom">
        <IconLayoutAlignBottom aria-hidden="true" />
        Bottom
      </SegmentedControlItem>
    </SegmentedControl>
  )
}

export { UiSdkSegmentedControlVertical }

disabled am Control schaltet alles ab und sendet nichts. An einem Element überspringen es die Pfeiltasten.

import {
  SegmentedControl,
  SegmentedControlItem,
} from "@/components/ui/ui-sdk-segmented-control"

function UiSdkSegmentedControlDisabled() {
  return (
    <div className="flex flex-col items-center gap-4">
      <SegmentedControl aria-label="Theme" defaultValue="system" disabled>
        <SegmentedControlItem value="light">Light</SegmentedControlItem>
        <SegmentedControlItem value="dark">Dark</SegmentedControlItem>
        <SegmentedControlItem value="system">System</SegmentedControlItem>
      </SegmentedControl>
      <SegmentedControl aria-label="Model" defaultValue="mini">
        <SegmentedControlItem value="mini">Mini</SegmentedControlItem>
        <SegmentedControlItem value="standard">Standard</SegmentedControlItem>
        <SegmentedControlItem value="pro" disabled>
          Pro
        </SegmentedControlItem>
      </SegmentedControl>
    </div>
  )
}

export { UiSdkSegmentedControlDisabled }

Übergib value und aktualisiere es in onValueChange. Ein Controller aus createSegmentedControlController() wählt ein Element von überall.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  SegmentedControl,
  SegmentedControlItem,
  createSegmentedControlController,
} from "@/components/ui/ui-sdk-segmented-control"

type Range = "day" | "week" | "month"

function UiSdkSegmentedControlControlled() {
  const [controller] = React.useState(() =>
    createSegmentedControlController<Range>()
  )
  const [range, setRange] = React.useState<Range | null>("week")

  return (
    <div className="flex flex-col items-center gap-3">
      <SegmentedControl<Range>
        aria-label="Range"
        value={range}
        onValueChange={(value) => setRange(value)}
        controller={controller}
      >
        <SegmentedControlItem value="day">Day</SegmentedControlItem>
        <SegmentedControlItem value="week">Week</SegmentedControlItem>
        <SegmentedControlItem value="month">Month</SegmentedControlItem>
      </SegmentedControl>
      <div className="flex items-center gap-2">
        <Button
          size="sm"
          variant="outline"
          onClick={() => controller.setValue("month")}
        >
          Show month
        </Button>
        <p className="m-0 text-xs text-muted-foreground">
          {`Showing ${range ?? "nothing"}`}
        </p>
      </div>
    </div>
  )
}

export { UiSdkSegmentedControlControlled }

Jedes Element enthält ein verstecktes natives Radio, daher sendet name den gewählten Wert, required wartet auf eine Wahl, und ein Zurücksetzen stellt den Standard her.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  SegmentedControl,
  SegmentedControlItem,
} from "@/components/ui/ui-sdk-segmented-control"

function UiSdkSegmentedControlForm() {
  const [saved, setSaved] = React.useState<string | null>(null)

  return (
    <form
      className="flex flex-col items-start gap-4"
      onSubmit={(event) => {
        event.preventDefault()
        const data = new FormData(event.currentTarget)
        setSaved(`theme=${data.get("theme")}, density=${data.get("density")}`)
      }}
    >
      <div className="flex flex-col gap-2">
        <p id="form-theme" className="m-0 text-sm font-medium">
          Theme
        </p>
        <SegmentedControl
          aria-labelledby="form-theme"
          name="theme"
          defaultValue="system"
          required
        >
          <SegmentedControlItem value="light">Light</SegmentedControlItem>
          <SegmentedControlItem value="dark">Dark</SegmentedControlItem>
          <SegmentedControlItem value="system">System</SegmentedControlItem>
        </SegmentedControl>
      </div>
      <div className="flex flex-col gap-2">
        <p id="form-density" className="m-0 text-sm font-medium">
          Density
        </p>
        <SegmentedControl
          aria-labelledby="form-density"
          name="density"
          defaultValue="comfortable"
        >
          <SegmentedControlItem value="compact">Compact</SegmentedControlItem>
          <SegmentedControlItem value="comfortable">
            Comfortable
          </SegmentedControlItem>
        </SegmentedControl>
      </div>
      <div className="flex items-center gap-3">
        <Button type="submit" size="sm">
          Save
        </Button>
        <Button type="reset" size="sm" variant="ghost">
          Reset
        </Button>
      </div>
      <p className="m-0 text-sm text-muted-foreground">
        {saved ? `Saved ${saved}` : "Nothing saved yet."}
      </p>
    </form>
  )
}

export { UiSdkSegmentedControlForm }

Ein schreibgeschütztes Control behält seinen Wert und wird trotzdem gesendet. Pfeiltasten bewegen den Fokus, ohne etwas zu wählen.

import {
  SegmentedControl,
  SegmentedControlItem,
} from "@/components/ui/ui-sdk-segmented-control"

function UiSdkSegmentedControlReadOnly() {
  return (
    <SegmentedControl aria-label="Plan" defaultValue="team" readOnly>
      <SegmentedControlItem value="solo">Solo</SegmentedControlItem>
      <SegmentedControlItem value="team">Team</SegmentedControlItem>
    </SegmentedControl>
  )
}

export { UiSdkSegmentedControlReadOnly }

Die Elemente laufen von rechts, und die Markierung folgt ihnen.

import {
  SegmentedControl,
  SegmentedControlItem,
} from "@/components/ui/ui-sdk-segmented-control"

function UiSdkSegmentedControlRtl() {
  return (
    <div dir="rtl" lang="ar">
      <SegmentedControl aria-label="العرض" defaultValue="list">
        <SegmentedControlItem value="list">قائمة</SegmentedControlItem>
        <SegmentedControlItem value="grid">شبكة</SegmentedControlItem>
        <SegmentedControlItem value="board">لوحة</SegmentedControlItem>
      </SegmentedControl>
    </div>
  )
}

export { UiSdkSegmentedControlRtl }
TasteAktion
TabSpringt in das Control, auf das gewählte oder das erste aktive Element.
←→↑↓Wechseln zum nächsten oder vorherigen Element und wählen es, deaktivierte werden übersprungen. Sie laufen im Kreis, außer loop ist false.
SpaceWählt das fokussierte Element.
EnterSendet sein Formular ab.

Das Control ist eine Radiogruppe und jedes Element ein Radio, daher sagen Screenreader die Wahl und ihre Position an. Die Markierung ist rein visuell und wird von Screenreadern übersprungen.

Die Markierung gleitet erst, wenn die Seite interaktiv ist, animiert also nie beim Laden, und bei reduzierter Bewegung gar nicht. In erzwungenen Farben behält das gewählte Element einen sichtbaren Rahmen.

Jeder Teil umhüllt den gleichnamigen UI-SDK-Teil und nimmt alle seine Props an, auch render und einen className, der eine Funktion des Zustands sein kann.

PropTypStandard
value
T | null–
defaultValue
T | null–
onValueChange
(value: T | null, details) => void–
size
"sm" | "default" | "lg""default"
orientation
"horizontal" | "vertical""horizontal"
loop
booleantrue
disabled
booleanfalse
readOnly
booleanfalse
required
booleanfalse
name
string–
form
string–
controllerAus createSegmentedControlController().
SegmentedControlController–
render
ReactElement | (props, state) => ReactElement–
AttributBeschreibung
data-slot="segmented-control"Zum Ansprechen des Segmented Controls in CSS.
data-sizeDie Größe: sm, default oder lg.
data-orientationhorizontal oder vertical.
data-disabledVorhanden, wenn deaktiviert.
data-readonlyVorhanden, wenn schreibgeschützt.
data-requiredVorhanden, wenn erforderlich.
PropTypStandard
value
string–
disabled
booleanfalse
render
ReactElement | (props, state) => ReactElement–
AttributBeschreibung
data-slot="segmented-control-item"Zum Ansprechen eines Elements in CSS.
data-checkedAm gewählten Element vorhanden.
data-uncheckedVorhanden an den anderen Einträgen.
data-icon-onlyVorhanden, wenn das Element nur ein Symbol enthält.
data-disabledVorhanden, wenn deaktiviert.