UNPKG

@yamada-ui/rating

Version:

Yamada UI rating component

1 lines 12.2 kB
{"version":3,"sources":["../src/use-rating.tsx"],"sourcesContent":["import type {\n CSSUIProps,\n HTMLUIProps,\n PropGetter,\n RequiredPropGetter,\n} from \"@yamada-ui/core\"\nimport type { FormControlOptions } from \"@yamada-ui/form-control\"\nimport type { MotionProps } from \"@yamada-ui/motion\"\nimport type { Merge } from \"@yamada-ui/utils\"\nimport type { MouseEvent, ReactNode, TouchEvent } from \"react\"\nimport type { RatingGroupProps } from \"./rating-group\"\nimport type { RatingItemProps } from \"./rating-item\"\nimport {\n formControlProperties,\n useFormControlProps,\n} from \"@yamada-ui/form-control\"\nimport { useControllableState } from \"@yamada-ui/use-controllable-state\"\nimport {\n clampNumber,\n dataAttr,\n handlerAll,\n mergeRefs,\n runIfFunc,\n splitObject,\n} from \"@yamada-ui/utils\"\nimport { useCallback, useId, useRef, useState } from \"react\"\nimport { RatingGroup } from \"./rating-group\"\nimport { getRoundedValue } from \"./rating-utils\"\n\ntype OmittedGroupProps = Omit<RatingGroupProps, \"children\" | \"items\" | \"value\">\ntype OmittedItemProps = Omit<\n RatingItemProps,\n \"children\" | \"fractionValue\" | \"groupValue\" | \"value\"\n>\ntype OmittedInputProps = Omit<\n HTMLUIProps<\"input\">,\n \"checked\" | \"defaultValue\" | \"value\"\n>\n\nexport type GroupProps =\n | ((value: number) => OmittedGroupProps)\n | OmittedGroupProps\nexport type ItemProps = ((value: number) => OmittedItemProps) | OmittedItemProps\nexport type InputProps =\n | ((value: number) => OmittedInputProps)\n | OmittedInputProps\n\ninterface UseRatingOptions {\n /**\n * The top-level id string that will be applied to the rating.\n * The index of the rating item will be appended to this top-level id.\n */\n id?: string\n /**\n * The name of the input element.\n */\n name?: string\n /**\n * The color of the filled icons.\n */\n color?: ((value: number) => CSSUIProps[\"color\"]) | CSSUIProps[\"color\"]\n /**\n * The initial value of the rating.\n *\n * @default 0\n */\n defaultValue?: number\n /**\n * The empty icon for the rating.\n */\n emptyIcon?: ((value: number) => ReactNode) | ReactNode\n /**\n * The filled icon for the rating.\n */\n filledIcon?: ((value: number) => ReactNode) | ReactNode\n /**\n * Number of fractions each item can be divided into,\n *\n * @default 1\n */\n fractions?: number\n /**\n * If `true`, only the selected icons will be filled.\n *\n * @default false\n */\n highlightSelectedOnly?: boolean\n /**\n * Number of controls that should be rendered.\n *\n * @default 5\n */\n items?: number\n /**\n * The value of the rating.\n */\n value?: number\n /**\n * Props for the rating group.\n */\n groupProps?: GroupProps\n /**\n * Props for the input element.\n */\n inputProps?: InputProps\n /**\n * Props for the rating item.\n */\n itemProps?: ItemProps\n /**\n * The callback invoked when value state changes.\n */\n onChange?: (value: number) => void\n /**\n * The callback invoked when hovering over the rating.\n */\n onHover?: (value: number) => void\n}\n\nexport type UseRatingProps = FormControlOptions &\n Omit<HTMLUIProps, \"color\" | \"defaultValue\" | \"id\" | \"onChange\"> &\n UseRatingOptions\n\nexport const useRating = ({\n name,\n color,\n defaultValue = 0,\n emptyIcon,\n filledIcon,\n fractions = 1,\n highlightSelectedOnly = false,\n items = 5,\n value: valueProp,\n groupProps,\n inputProps,\n itemProps,\n onChange: onChangeProp,\n onHover,\n onMouseEnter: onMouseEnterProp,\n onMouseLeave: onMouseLeaveProp,\n onMouseMove: onMouseMoveProp,\n onTouchEnd: onTouchEndProp,\n onTouchStart: onTouchStartProp,\n ...props\n}: UseRatingProps) => {\n const uuid = useId()\n const { id = uuid, ...rest } = useFormControlProps(props)\n const containerRef = useRef<HTMLDivElement>(null)\n const [value, setValue] = useControllableState({\n defaultValue,\n value: valueProp,\n onChange: onChangeProp,\n })\n const [hoveredValue, setHoveredValue] = useState<number>(-1)\n const [outside, setOutside] = useState(true)\n const [formControlProps, containerProps] = splitObject(\n rest,\n formControlProperties,\n )\n const { disabled, readOnly, ...omittedFormControlProps } = formControlProps\n const resolvedFractions = Math.floor(fractions)\n const resolvedItems = Math.floor(items)\n const decimal = 1 / resolvedFractions\n const roundedValue = getRoundedValue(value, decimal)\n const resolvedValue = hoveredValue !== -1 ? hoveredValue : roundedValue\n\n name ??= `rating-${id}`\n\n const getHoveredValue = useCallback(\n (x: number) => {\n const { left, width } = containerRef.current!.getBoundingClientRect()\n const itemWidth = width / resolvedItems\n\n const hoveredValue = (x - left) / itemWidth\n\n const value = clampNumber(\n getRoundedValue(hoveredValue + decimal / 2, decimal),\n decimal,\n resolvedItems,\n )\n\n return value\n },\n [decimal, resolvedItems],\n )\n\n const onMouseEnter = useCallback(() => {\n if (!disabled && !readOnly) setOutside(false)\n }, [disabled, readOnly])\n\n const onMouseLeave = useCallback(() => {\n if (disabled || readOnly) return\n\n setHoveredValue(-1)\n setOutside(true)\n\n if (hoveredValue !== -1) onHover?.(-1)\n }, [disabled, hoveredValue, onHover, readOnly, setHoveredValue])\n\n const onTouchStart = useCallback(\n (ev: TouchEvent<HTMLDivElement>) => {\n ev.preventDefault()\n\n const el = ev.touches[0]\n\n if (!el) return\n\n const value = getHoveredValue(el.clientX)\n\n setValue(value)\n },\n [getHoveredValue, setValue],\n )\n\n const onTouchEnd = useCallback((ev: TouchEvent<HTMLDivElement>) => {\n ev.preventDefault()\n }, [])\n\n const onMouseMove = useCallback(\n (ev: MouseEvent<HTMLDivElement>) => {\n if (disabled || readOnly) return\n\n const roundedValue = getHoveredValue(ev.clientX)\n\n setHoveredValue(roundedValue)\n\n if (roundedValue !== hoveredValue) onHover?.(roundedValue)\n },\n [disabled, getHoveredValue, hoveredValue, readOnly, onHover],\n )\n\n const getContainerProps: PropGetter = useCallback(\n (props = {}, ref = null) => ({\n ref: mergeRefs(ref, containerRef),\n \"aria-label\": `${value} Stars`,\n role: \"radiogroup\",\n ...omittedFormControlProps,\n ...containerProps,\n ...props,\n id,\n onMouseEnter: handlerAll(\n onMouseEnter,\n props.onMouseEnter,\n onMouseEnterProp,\n ),\n onMouseLeave: handlerAll(\n onMouseLeave,\n props.onMouseLeave,\n onMouseLeaveProp,\n ),\n onMouseMove: handlerAll(onMouseMove, props.onMouseMove, onMouseMoveProp),\n onTouchEnd: handlerAll(onTouchEnd, props.onTouchEnd, onTouchEndProp),\n onTouchStart: handlerAll(\n onTouchStart,\n props.onTouchStart,\n onTouchStartProp,\n ),\n }),\n [\n omittedFormControlProps,\n containerProps,\n id,\n value,\n onMouseEnter,\n onMouseEnterProp,\n onMouseLeave,\n onMouseLeaveProp,\n onMouseMove,\n onMouseMoveProp,\n onTouchEnd,\n onTouchEndProp,\n onTouchStart,\n onTouchStartProp,\n ],\n )\n\n const getGroupProps: RequiredPropGetter<\n Merge<MotionProps, { value: number }>,\n MotionProps\n > = useCallback(\n ({ value, ...props }, ref = null) => {\n const isActive = !readOnly && Math.ceil(hoveredValue) === value\n\n return {\n ref,\n whileTap: !disabled && !readOnly ? { y: -4 } : undefined,\n ...props,\n \"data-active\": dataAttr(isActive),\n tabIndex: -1,\n }\n },\n [disabled, hoveredValue, readOnly],\n )\n\n const children = Array(resolvedItems)\n .fill(0)\n .map((_, index) => {\n const value = index + 1\n\n return (\n <RatingGroup\n key={value}\n color={runIfFunc(color, value)}\n items={index === 0 ? resolvedFractions + 1 : resolvedFractions}\n value={value}\n />\n )\n })\n\n return {\n id,\n name,\n children,\n decimal,\n emptyIcon,\n filledIcon,\n highlightSelectedOnly,\n hoveredValue,\n outside,\n resolvedValue,\n roundedValue,\n setHoveredValue,\n setValue,\n value,\n formControlProps,\n getContainerProps,\n getGroupProps,\n groupProps,\n inputProps,\n itemProps,\n }\n}\n\nexport type UseRatingReturn = ReturnType<typeof useRating>\n"],"mappings":"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","names":["hoveredValue","value","roundedValue","props"]}