Rating Group
Allows users to rate items using a set of icons.
Usage
import { RatingGroup, type RatingGroupProps } from '~/components/ui'
export const Demo = (props: RatingGroupProps) => {
  return (
    <RatingGroup defaultValue={3} {...props}>
      Label
    </RatingGroup>
  )
}
Examples
Different color
Use the colorPalette prop to change the color of the rating group.
<RatingGroup colorPalette="red" value={3}>
  Label
</RatingGroup>Rating count
Use the count prop to render a specific number of stars.
<RatingGroup count={10}>Label</RatingGroup>Half star rating
Use the allowHalf prop to enable half star ratings.
<RatingGroup value={3.5} allowHalf>
  Label
</RatingGroup>Installation
npx @park-ui/cli components add rating-group1
Styled Primitive
Copy the code snippet below into ~/components/ui/primitives/rating-group.tsx
'use client'
import type { Assign } from '@ark-ui/react'
import { RatingGroup } from '@ark-ui/react/rating-group'
import { type RatingGroupVariantProps, ratingGroup } from 'styled-system/recipes'
import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from '~/lib/create-style-context'
const { withProvider, withContext } = createStyleContext(ratingGroup)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
  HTMLDivElement,
  Assign<Assign<HTMLStyledProps<'div'>, RatingGroup.RootProviderBaseProps>, RatingGroupVariantProps>
>(RatingGroup.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
  HTMLDivElement,
  Assign<Assign<HTMLStyledProps<'div'>, RatingGroup.RootBaseProps>, RatingGroupVariantProps>
>(RatingGroup.Root, 'root')
export const Control = withContext<
  HTMLDivElement,
  Assign<HTMLStyledProps<'div'>, RatingGroup.ControlBaseProps>
>(RatingGroup.Control, 'control')
export const Item = withContext<
  HTMLSpanElement,
  Assign<HTMLStyledProps<'span'>, RatingGroup.ItemBaseProps>
>(RatingGroup.Item, 'item')
export const Label = withContext<
  HTMLLabelElement,
  Assign<HTMLStyledProps<'label'>, RatingGroup.LabelBaseProps>
>(RatingGroup.Label, 'label')
export {
  RatingGroupContext as Context,
  RatingGroupItemContext as ItemContext,
  RatingGroupHiddenInput as HiddenInput,
} from '@ark-ui/react/rating-group'
import { type Assign, RatingGroup } from '@ark-ui/solid'
import type { ComponentProps } from 'solid-js'
import { type RatingGroupVariantProps, ratingGroup } from 'styled-system/recipes'
import type { HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from '~/lib/create-style-context'
const { withProvider, withContext } = createStyleContext(ratingGroup)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
  Assign<Assign<HTMLStyledProps<'div'>, RatingGroup.RootProviderBaseProps>, RatingGroupVariantProps>
>(RatingGroup.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
  Assign<Assign<HTMLStyledProps<'div'>, RatingGroup.RootBaseProps>, RatingGroupVariantProps>
>(RatingGroup.Root, 'root')
export const Control = withContext<Assign<HTMLStyledProps<'div'>, RatingGroup.ControlBaseProps>>(
  RatingGroup.Control,
  'control',
)
export const Item = withContext<Assign<HTMLStyledProps<'span'>, RatingGroup.ItemBaseProps>>(
  RatingGroup.Item,
  'item',
)
export const Label = withContext<Assign<HTMLStyledProps<'label'>, RatingGroup.LabelBaseProps>>(
  RatingGroup.Label,
  'label',
)
export {
  RatingGroupContext as Context,
  RatingGroupHiddenInput as HiddenInput,
  RatingGroupItemContext as ItemContext,
} from '@ark-ui/solid'
No snippet foundExtend ~/components/ui/primitives/index.ts with the following line:
export * as RatingGroup from './rating-group'2
Add Composition
Copy the code snippet below into ~/components/ui/rating-group.tsx
'use client'
import { forwardRef } from 'react'
import { RatingGroup as ArkRatingGroup } from '~/components/ui/primitives'
export interface RatingGroupProps extends ArkRatingGroup.RootProps {}
export const RatingGroup = forwardRef<HTMLDivElement, RatingGroupProps>((props, ref) => {
  const { children, ...rootProps } = props
  return (
    <ArkRatingGroup.Root ref={ref} {...rootProps}>
      {children && <ArkRatingGroup.Label>{children}</ArkRatingGroup.Label>}
      <ArkRatingGroup.Control>
        <ArkRatingGroup.Context>
          {({ items }) =>
            items.map((index) => (
              <ArkRatingGroup.Item key={index} index={index}>
                <ArkRatingGroup.ItemContext>
                  {(item) => <StarIcon isHalf={item.half} />}
                </ArkRatingGroup.ItemContext>
              </ArkRatingGroup.Item>
            ))
          }
        </ArkRatingGroup.Context>
      </ArkRatingGroup.Control>
      <ArkRatingGroup.HiddenInput />
    </ArkRatingGroup.Root>
  )
})
RatingGroup.displayName = 'RatingGroup'
type IconProps = {
  isHalf: boolean
}
const StarIcon = (props: IconProps) => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width="24"
    height="24"
    viewBox="0 0 24 24"
    fill="inherit"
    stroke="inherit"
    strokeWidth="2"
    strokeLinecap="round"
    strokeLinejoin="round"
  >
    <title>Star Icon</title>
    <defs>
      <linearGradient id="half">
        <stop offset="50%" stopColor="var(--colors-color-palette-default)" />
        <stop offset="50%" stopColor="var(--colors-bg-emphasized)" />
      </linearGradient>
    </defs>
    <polygon
      fill={props.isHalf ? 'url(#half)' : 'inherit'}
      points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
    />
  </svg>
)
import { Index, Show, children } from 'solid-js'
import { RatingGroup as ArkRatingGroup } from '~/components/ui/primitives'
export interface RatingGroupProps extends ArkRatingGroup.RootProps {}
export const RatingGroup = (props: RatingGroupProps) => {
  const getChildren = children(() => props.children)
  return (
    <ArkRatingGroup.Root {...props}>
      <Show when={getChildren()}>
        <ArkRatingGroup.Label>{getChildren()}</ArkRatingGroup.Label>
      </Show>
      <ArkRatingGroup.Control>
        <ArkRatingGroup.Context>
          {(context) => (
            <Index each={context().items}>
              {(index) => (
                <ArkRatingGroup.Item index={index()}>
                  <ArkRatingGroup.ItemContext>
                    {(item) => (
                      <Show when={item().highlighted} fallback={<StarIcon />}>
                        <StarIcon half={item().half} />
                      </Show>
                    )}
                  </ArkRatingGroup.ItemContext>
                </ArkRatingGroup.Item>
              )}
            </Index>
          )}
        </ArkRatingGroup.Context>
      </ArkRatingGroup.Control>
      <ArkRatingGroup.HiddenInput />
    </ArkRatingGroup.Root>
  )
}
interface Props {
  half?: boolean
}
const StarIcon = (props: Props) => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width="24"
    height="24"
    viewBox="0 0 24 24"
    fill="inherit"
    stroke="inherit"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
  >
    <title>Star Icon</title>
    <defs>
      <linearGradient id="half">
        <stop offset="50%" stop-color="var(--colors-color-palette-default)" />
        <stop offset="50%" stop-color="var(--colors-bg-emphasized)" />
      </linearGradient>
    </defs>
    <polygon
      fill={props.half ? 'url(#half)' : 'inherit'}
      points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
    />
  </svg>
)
Extend ~/components/ui/index.ts with the following line:
export * from './primitives'
export { RatingGroup, type RatingGroupProps } from './rating-group'3
Integrate Recipe
If you're not using @park-ui/preset, add the following recipe to yourpanda.config.ts:
import { ratingGroupAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'
export const ratingGroup = defineSlotRecipe({
  className: 'ratingGroup',
  slots: ratingGroupAnatomy.keys(),
  base: {
    root: {
      colorPalette: 'accent',
      display: 'flex',
      flexDirection: 'column',
      gap: '1.5',
    },
    label: {
      color: 'fg.default',
      fontWeight: 'medium',
    },
    control: {
      display: 'flex',
    },
    item: {
      cursor: 'pointer',
      transitionDuration: 'normal',
      transitionProperty: 'color, fill',
      transitionTimingFunction: 'default',
      fill: 'bg.emphasized',
      _highlighted: {
        fill: 'colorPalette.default',
      },
      _focusVisible: {
        outline: 'none',
      },
    },
  },
  defaultVariants: {
    size: 'md',
  },
  variants: {
    size: {
      sm: {
        control: {
          gap: '0',
        },
        item: {
          '& svg': {
            width: '4',
            height: '4',
          },
        },
        label: {
          textStyle: 'sm',
        },
      },
      md: {
        control: {
          gap: '0.5',
        },
        item: {
          '& svg': {
            width: '5',
            height: '5',
          },
        },
        label: {
          textStyle: 'sm',
        },
      },
      lg: {
        control: {
          gap: '0.5',
        },
        item: {
          '& svg': {
            width: '6',
            height: '6',
          },
        },
        label: {
          textStyle: 'md',
        },
      },
    },
  },
})