HextaUI

BetaHecho sobre el UI SDK 0.1.0-beta.1 de Vercel. Su API puede cambiar.

Rating

Una valoración con estrellas sobre el UI SDK de Vercel, con estrellas enteras o medias, vista previa al pasar el cursor, medias de solo lectura y soporte de formularios.

Depende de
  • @tabler/icons-react
  • @ui-sdk/core
  • @ui-sdk/react
  • class-variance-authority
"use client"

import * as React from "react"

import { Rating, RatingItem } from "@/components/ui/ui-sdk-rating"

const labels = ["Terrible", "Poor", "Okay", "Good", "Great"]

function UiSdkRatingDemo() {
  const [value, setValue] = React.useState<number | null>(4)

  return (
    <div className="flex flex-col items-center gap-2">
      <Rating
        aria-label="How was your stay?"
        size="lg"
        value={value}
        onValueChange={setValue}
        getValueLabel={(score, max) =>
          labels[score - 1] + ", " + score + " of " + max
        }
      >
        <RatingItem />
      </Rating>
      <p
        aria-hidden="true"
        className="min-h-5 text-sm text-muted-foreground tabular-nums"
      >
        {value ? labels[value - 1] : "Not rated yet"}
      </p>
    </div>
  )
}

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

Añade el componente, los tokens del tema de HextaUI y los componentes de HextaUI de los que depende.

UI SDK añade un poco de CSS propio en una capa de cascada llamada ui-sdk. Declara esa capa al principio de tu hoja de estilos global, encima del import de Tailwind, para que tus clases ganen.

@layer ui-sdk;

@import "tailwindcss";
import { Rating, RatingItem } from "@/components/ui/ui-sdk-rating"
<Rating aria-label="Rating" defaultValue={3}>
  <RatingItem />
</Rating>

Rating renderiza su hijo una vez por estrella, así que pon un RatingItem dentro. Cada estrella dibuja una estrella vacía con una llena recortada a --rating-fill, así que las medias estrellas y medias como 4,3 se ven exactas.

Rating
└── RatingItem

Con step={0.5}, cada mitad de estrella es su propio radio, y las flechas avanzan media estrella.

import { Rating, RatingItem } from "@/components/ui/ui-sdk-rating"

function UiSdkRatingHalf() {
  return (
    <Rating aria-label="Rating" step={0.5} defaultValue={3.5}>
      <RatingItem />
    </Rating>
  )
}

export { UiSdkRatingHalf }

Una valoración de solo lectura es una imagen nombrada por getValueLabel y muestra cualquier puntuación, como una media.

"use client"

import { Rating, RatingItem } from "@/components/ui/ui-sdk-rating"

function UiSdkRatingReadOnly() {
  return (
    <div className="flex items-center gap-2 text-sm">
      <Rating
        readOnly
        size="sm"
        value={4.3}
        getValueLabel={(value, max) =>
          "Rated " + value + " out of " + max + " stars"
        }
      >
        <RatingItem />
      </Rating>
      <span className="font-medium tabular-nums">4.3</span>
      <span className="text-muted-foreground">(1,284 reviews)</span>
    </div>
  )
}

export { UiSdkRatingReadOnly }

size es sm, default o lg.

import { Rating, RatingItem } from "@/components/ui/ui-sdk-rating"

function UiSdkRatingSizes() {
  return (
    <div className="flex flex-col items-center gap-4">
      <Rating aria-label="Small rating" size="sm" defaultValue={3}>
        <RatingItem />
      </Rating>
      <Rating aria-label="Default rating" defaultValue={3}>
        <RatingItem />
      </Rating>
      <Rating aria-label="Large rating" size="lg" defaultValue={3}>
        <RatingItem />
      </Rating>
    </div>
  )
}

export { UiSdkRatingSizes }

Con deselectable, pulsar de nuevo la estrella marcada borra la puntuación.

import { Rating, RatingItem } from "@/components/ui/ui-sdk-rating"

function UiSdkRatingDeselectable() {
  return (
    <Rating aria-label="Rating" deselectable defaultValue={2}>
      <RatingItem />
    </Rating>
  )
}

export { UiSdkRatingDeselectable }

Pasa value y onValueChange, donde null significa sin puntuación. Un controlador de createRatingController() fija la puntuación desde cualquier lugar.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  createRatingController,
  Rating,
  RatingItem,
} from "@/components/ui/ui-sdk-rating"

function UiSdkRatingControlled() {
  const [rating] = React.useState(createRatingController)
  const [value, setValue] = React.useState<number | null>(null)

  return (
    <div className="flex flex-col items-center gap-3">
      <Rating
        aria-label="Rating"
        controller={rating}
        value={value}
        onValueChange={setValue}
      >
        <RatingItem />
      </Rating>
      <div className="flex gap-2">
        <Button variant="outline" size="sm" onClick={() => rating.setValue(5)}>
          Rate 5
        </Button>
        <Button
          variant="ghost"
          size="sm"
          disabled={value === null}
          onClick={() => setValue(null)}
        >
          Clear
        </Button>
      </div>
      <p className="text-sm text-muted-foreground tabular-nums">
        {value === null ? "No score" : "Score: " + value}
      </p>
    </div>
  )
}

export { UiSdkRatingControlled }

Con un name, la puntuación se envía con el formulario. required bloquea el envío hasta que haya puntuación, con el mensaje propio del navegador.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Rating, RatingItem } from "@/components/ui/ui-sdk-rating"

function UiSdkRatingForm() {
  const [submitted, setSubmitted] = React.useState<string | null>(null)

  return (
    <form
      className="flex flex-col items-start gap-3"
      onSubmit={(event) => {
        event.preventDefault()
        setSubmitted(String(new FormData(event.currentTarget).get("score")))
      }}
      onReset={() => setSubmitted(null)}
    >
      <div className="flex flex-col gap-1.5">
        <span id="rating-form-label" className="text-sm font-medium">
          Rate this answer
        </span>
        <Rating aria-labelledby="rating-form-label" name="score" required>
          <RatingItem />
        </Rating>
      </div>
      <div className="flex gap-2">
        <Button type="submit" size="sm">
          Submit
        </Button>
        <Button type="reset" variant="outline" size="sm">
          Reset
        </Button>
      </div>
      <p role="status" className="text-sm text-muted-foreground">
        {submitted ? "Submitted score=" + submitted : "Nothing submitted yet."}
      </p>
    </form>
  )
}

export { UiSdkRatingForm }

Pasa tu propio icono como hijo de RatingItem y cámbialo con data-filled. tone define el color.

import { IconHeart, IconHeartFilled } from "@tabler/icons-react"

import { Rating, RatingItem } from "@/components/ui/ui-sdk-rating"

function UiSdkRatingCustomIcon() {
  return (
    <Rating
      aria-label="How much did you like it?"
      defaultValue={3}
      tone="destructive"
    >
      <RatingItem>
        <IconHeart
          aria-hidden="true"
          className="size-6 group-data-[filled]/rating-item:hidden group-data-[partial]/rating-item:hidden"
        />
        <IconHeartFilled
          aria-hidden="true"
          className="hidden size-6 group-data-[filled]/rating-item:block group-data-[partial]/rating-item:block"
        />
      </RatingItem>
    </Rating>
  )
}

export { UiSdkRatingCustomIcon }

Una valoración deshabilitada no recibe foco ni cambia, y no envía nada.

import { Rating, RatingItem } from "@/components/ui/ui-sdk-rating"

function UiSdkRatingDisabled() {
  return (
    <Rating aria-label="Rating" disabled defaultValue={3}>
      <RatingItem />
    </Rating>
  )
}

export { UiSdkRatingDisabled }

Las estrellas van desde la derecha, y una media estrella se llena desde su borde derecho.

import { Rating, RatingItem } from "@/components/ui/ui-sdk-rating"

function UiSdkRatingRtl() {
  return (
    <div dir="rtl">
      <Rating aria-label="التقييم" step={0.5} defaultValue={2.5}>
        <RatingItem />
      </Rating>
    </div>
  )
}

export { UiSdkRatingRtl }
KeyAcción
TabEntra en la valoración, en la estrella marcada o en la primera.
←→↑↓Pasa a la puntuación siguiente o anterior y la marca, deteniéndose en los extremos salvo que loop esté activo.
SpaceMarca la estrella enfocada, o la desmarca si ya lo está y la valoración es deseleccionable.
EnterEnvía el formulario.

Una valoración es un grupo de radios con una sola parada de tabulación. Cada estrella tiene un radio por puntuación que puede elegir, nombrado por getValueLabel, así que nombra las puntuaciones con palabras que la gente usa, como “Genial, 5 de 5”. Los iconos de estrella se ocultan a los lectores de pantalla. Una valoración de solo lectura es, en cambio, una única imagen.

Las estrellas solo crecen al pasar el cursor y se encogen al pulsar cuando se permite el movimiento. En pantallas táctiles cada estrella tiene relleno extra, y en modo de colores forzados las estrellas llenas usan el color del texto y las vacías el gris del sistema.

Cada parte envuelve la parte de UI SDK del mismo nombre y acepta todas sus props, incluidas render y un className que puede ser una función del estado de la parte.

PropTipoPredeterminado
value
number | null–
defaultValue
number | null–
onValueChange
(value: number | null, details) => void–
max
number5
step
number1
size
"sm" | "default" | "lg""default"
tone
"default" | "neutral" | "destructive""default"
deselectable
booleanfalse
readOnly
booleanfalse
disabled
booleanfalse
required
booleanfalse
name
string–
loop
booleanfalse
getValueLabelNombra cada puntuación para su radio y para una valoración de solo lectura.
(value: number, max: number) => string–
controllerDe createRatingController().
RatingController–
AtributoDescripción
data-slot="rating"Selecciona la valoración en CSS.
data-sizeEl tamaño: sm, default o lg.
data-toneEl tono: default, neutral o destructive.
data-readonlyPresente cuando es de solo lectura.
data-disabledPresente cuando está deshabilitado.
--rating-colorEl color de las estrellas llenas, definido por tone.
PropTipoPredeterminado
childrenEl icono de la estrella. Por defecto, una estrella de Tabler que se llena según --rating-fill.
ReactNode–
render
ReactElement | (props, state) => ReactElement–
AtributoDescripción
data-slot="rating-item"Selecciona una estrella en CSS.
data-filledPresente en las estrellas que la puntuación mostrada cubre por completo.
data-partialPresente en la estrella que la puntuación mostrada cubre en parte.
data-checkedPresente en la estrella donde termina la puntuación.
data-highlightedPresente en las estrellas hasta la puntuación que previsualiza el puntero.
--rating-fillCuánto de la estrella cubre la puntuación mostrada, de 0 a 1.