HextaUI

BetaFeito sobre o UI SDK 0.1.0-beta.1 da Vercel. A API pode mudar.

Rating

Uma avaliação por estrelas sobre o UI SDK da Vercel, com estrelas inteiras ou meias, prévia ao passar o mouse, médias somente leitura e suporte a formulários.

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

Adiciona o componente, os tokens de tema do HextaUI e quaisquer componentes do HextaUI dos quais ele depende.

O UI SDK adiciona um pouco de CSS próprio em uma camada de cascata chamada ui-sdk. Declare essa camada no topo da sua folha de estilos global, acima do import do Tailwind, para que suas classes vençam.

@layer ui-sdk;

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

Rating renderiza o filho uma vez por estrela, então coloque um RatingItem dentro. Cada estrela desenha uma estrela vazia com uma cheia recortada em --rating-fill, então meias estrelas e médias como 4,3 aparecem exatas.

Rating
└── RatingItem

Com step={0.5}, cada metade de estrela é um radio próprio, e as setas andam meia estrela.

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 }

Uma avaliação somente leitura é uma imagem nomeada por getValueLabel e mostra qualquer nota, como uma média.

"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 é sm, default ou 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 }

Com deselectable, pressionar de novo a estrela marcada limpa a nota.

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

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

export { UiSdkRatingDeselectable }

Passe value e onValueChange, em que null significa sem nota ainda. Um controlador de createRatingController() define a nota de qualquer 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 }

Com um name, a nota é enviada com o formulário. required bloqueia o envio até haver uma nota, com a mensagem do próprio 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 }

Passe seu próprio ícone como filho de RatingItem e troque-o com data-filled. tone define a cor.

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 }

Uma avaliação desativada não recebe foco nem muda, e não envia nada.

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

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

export { UiSdkRatingDisabled }

As estrelas começam pela direita, e uma meia estrela se preenche a partir da borda direita.

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 }
TeclaAção
TabEntra na avaliação, na estrela marcada ou na primeira.
←→↑↓Vai para a nota seguinte ou anterior e a marca, parando nas pontas a menos que loop esteja ativo.
SpaceMarca a estrela focada, ou a desmarca quando já está marcada e a avaliação é desmarcável.
EnterEnvia o formulário.

Uma avaliação é um grupo de radios com uma única parada de tab. Cada estrela tem um radio por nota que pode escolher, nomeado por getValueLabel, então nomeie as notas com palavras que as pessoas usam, como “Ótimo, 5 de 5”. Os ícones de estrela ficam ocultos para leitores de tela. Uma avaliação somente leitura é uma única imagem.

As estrelas só crescem ao passar o mouse e encolhem ao pressionar quando o movimento é permitido. Em telas de toque cada estrela ganha espaçamento extra, e no modo de cores forçadas as estrelas cheias usam a cor do texto e as vazias o cinza do sistema.

Cada parte envolve a parte do UI SDK de mesmo nome e aceita todas as suas props, incluindo render e um className que pode ser uma função do estado da parte.

PropTipoPadrão
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
getValueLabelNomeia cada nota para seu radio e para uma avaliação somente leitura.
(value: number, max: number) => string–
controllerDe createRatingController().
RatingController–
AtributoDescrição
data-slot="rating"Seleciona a avaliação no CSS.
data-sizeO tamanho: sm, default ou lg.
data-toneO tom: default, neutral ou destructive.
data-readonlyPresente quando somente leitura.
data-disabledPresente quando desabilitado.
--rating-colorA cor das estrelas cheias, definida por tone.
PropTipoPadrão
childrenO ícone da estrela. Por padrão, uma estrela do Tabler que se preenche por --rating-fill.
ReactNode–
render
ReactElement | (props, state) => ReactElement–
AtributoDescrição
data-slot="rating-item"Seleciona uma estrela no CSS.
data-filledPresente nas estrelas que a nota exibida cobre por inteiro.
data-partialPresente na estrela que a nota exibida cobre em parte.
data-checkedPresente na estrela em que a nota termina.
data-highlightedPresente nas estrelas até a nota que o ponteiro pré-visualiza.
--rating-fillQuanto da estrela a nota exibida cobre, de 0 a 1.