@yamada-ui/rating
Version:
Yamada UI rating component
561 lines (550 loc) • 17.9 kB
JavaScript
"use client"
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/rating.tsx
var rating_exports = {};
__export(rating_exports, {
Rating: () => Rating
});
module.exports = __toCommonJS(rating_exports);
var import_core2 = require("@yamada-ui/core");
var import_utils6 = require("@yamada-ui/utils");
// src/rating-context.ts
var import_utils = require("@yamada-ui/utils");
var [RatingProvider, useRatingContext] = (0, import_utils.createContext)({
name: "RatingContext",
errorMessage: `useRatingContext returned is 'undefined'. Seems you forgot to wrap the components in "<Rating />"`
});
// src/use-rating.tsx
var import_form_control = require("@yamada-ui/form-control");
var import_use_controllable_state = require("@yamada-ui/use-controllable-state");
var import_utils5 = require("@yamada-ui/utils");
var import_react3 = require("react");
// src/rating-group.tsx
var import_motion = require("@yamada-ui/motion");
var import_utils4 = require("@yamada-ui/utils");
// src/rating-item.tsx
var import_core = require("@yamada-ui/core");
var import_icon = require("@yamada-ui/icon");
var import_utils3 = require("@yamada-ui/utils");
var import_react2 = require("react");
// src/use-rating-item.ts
var import_use_focus_visible = require("@yamada-ui/use-focus-visible");
var import_utils2 = require("@yamada-ui/utils");
var import_react = require("react");
var useRatingItem = ({
fractionValue,
groupValue,
value
}) => {
const {
id,
name,
highlightSelectedOnly,
outside,
resolvedValue,
roundedValue,
setHoveredValue,
setValue,
formControlProps
} = useRatingContext();
const {
"aria-disabled": ariaDisabled,
"aria-readonly": _ariaReadOnly,
disabled,
readOnly,
...omittedFormControlProps
} = formControlProps;
const [focused, setFocused] = (0, import_react.useState)(false);
const [focusVisible, setFocusVisible] = (0, import_react.useState)(false);
const active = value === resolvedValue;
const checked = value === roundedValue;
const filled = highlightSelectedOnly ? value === resolvedValue : value <= resolvedValue;
const onBlur = (0, import_react.useCallback)(() => {
setFocused(false);
if (outside) setHoveredValue(-1);
}, [outside, setHoveredValue]);
const onInputChange = (0, import_react.useCallback)(
(ev) => {
if (readOnly || disabled) return;
const value2 = parseFloat(ev.target.value);
setHoveredValue(value2);
},
[disabled, readOnly, setHoveredValue]
);
const onChange = (0, import_react.useCallback)(
(value2) => {
if (readOnly || disabled) return;
setValue(value2);
},
[disabled, readOnly, setValue]
);
const onMouseDown = (0, import_react.useCallback)(() => {
if (readOnly || disabled) return;
onChange(value);
}, [disabled, onChange, readOnly, value]);
const onTouchStart = (0, import_react.useCallback)(() => {
if (readOnly || disabled) return;
onChange(value);
}, [disabled, onChange, readOnly, value]);
const getItemProps = (0, import_react.useCallback)(
(props = {}, ref = null) => {
const zIndex = active ? 1 : -1;
return {
ref,
htmlFor: `${id}-${groupValue}-${value}`,
...omittedFormControlProps,
...props,
"data-active": (0, import_utils2.dataAttr)(active),
"data-disabled": (0, import_utils2.dataAttr)(disabled),
"data-filled": (0, import_utils2.dataAttr)(filled),
"data-focus": (0, import_utils2.dataAttr)(focused),
"data-focus-visible": (0, import_utils2.dataAttr)(focused && focusVisible),
zIndex: fractionValue !== 1 ? zIndex : void 0,
onMouseDown: (0, import_utils2.handlerAll)(onMouseDown, props.onMouseDown),
onTouchStart: (0, import_utils2.handlerAll)(onTouchStart, props.onTouchStart)
};
},
[
disabled,
omittedFormControlProps,
fractionValue,
groupValue,
id,
active,
filled,
focusVisible,
focused,
onMouseDown,
onTouchStart,
value
]
);
const getInputProps = (0, import_react.useCallback)(
(props = {}, ref = null) => {
return {
ref,
"aria-disabled": ariaDisabled,
"aria-label": `${value}`,
disabled,
readOnly,
...omittedFormControlProps,
...props,
id: `${id}-${groupValue}-${value}`,
type: "radio",
name,
style: {
border: "0px",
clip: "rect(0px, 0px, 0px, 0px)",
height: "1px",
margin: "-1px",
overflow: "hidden",
padding: "0px",
position: "absolute",
whiteSpace: "nowrap",
width: "1px"
},
"data-active": (0, import_utils2.dataAttr)(active),
"data-checked": (0, import_utils2.dataAttr)(checked),
checked,
value,
onBlur: (0, import_utils2.handlerAll)(onBlur, props.onBlur),
onChange: (0, import_utils2.handlerAll)(onInputChange, props.onChange),
onFocus: (0, import_utils2.handlerAll)(() => setFocused(true), props.onFocus),
onKeyDown: (0, import_utils2.handlerAll)(
(ev) => ev.key === " " ? onChange(value) : void 0,
props.onKeyDown
)
};
},
[
ariaDisabled,
disabled,
readOnly,
value,
omittedFormControlProps,
id,
groupValue,
name,
checked,
onInputChange,
onBlur,
active,
onChange
]
);
(0, import_react.useEffect)(() => {
return (0, import_use_focus_visible.trackFocusVisible)(setFocusVisible);
}, []);
return {
active,
checked,
filled,
getInputProps,
getItemProps
};
};
// src/rating-item.tsx
var import_jsx_runtime = require("react/jsx-runtime");
var RatingItem = (0, import_core.forwardRef)(
({ className, color, fractionValue, groupValue, value, ...rest }, ref) => {
const {
emptyIcon = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RatingStarIcon, {}),
filledIcon = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RatingStarIcon, {}),
styles,
inputProps,
itemProps
} = useRatingContext();
const { active, filled, getInputProps, getItemProps } = useRatingItem({
fractionValue,
groupValue,
value
});
const computedItemProps = (0, import_utils3.runIfFunc)(itemProps, value);
const computedInputProps = (0, import_utils3.runIfFunc)(inputProps, value);
const customColor = color ? {
_filled: {
color: (0, import_utils3.isString)(color) ? [color, color] : color
}
} : {};
const css = {
display: "block",
lineHeight: "0",
...styles.item,
...customColor
};
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_core.ui.input, { ...getInputProps(computedInputProps, ref) }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
import_core.ui.label,
{
className: (0, import_utils3.cx)("ui-rating__item", className),
__css: css,
...getItemProps({ ...computedItemProps, ...rest }),
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
RatingIcon,
{
clipPath: fractionValue !== 1 ? `inset(0 ${active ? 100 - fractionValue * 100 : 100}% 0 0)` : void 0,
children: filled ? (0, import_utils3.runIfFunc)(filledIcon, groupValue) : (0, import_utils3.runIfFunc)(emptyIcon, groupValue)
}
)
}
)
] });
}
);
RatingItem.displayName = "RatingItem";
RatingItem.__ui__ = "RatingItem";
var RatingIcon = ({ className, children, ...rest }) => {
const { styles } = useRatingContext();
const validChildren = (0, import_utils3.getValidChildren)(children);
const cloneChildren = validChildren.map(
(child) => (0, import_react2.cloneElement)(child, {
style: {
maxHeight: "1em",
maxWidth: "1em"
},
"aria-hidden": true,
focusable: false
})
);
const css = {
alignItems: "center",
display: "inline-flex",
justifyContent: "center",
...styles.icon
};
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
import_core.ui.div,
{
className: (0, import_utils3.cx)("ui-rating__item__icon", className),
__css: css,
...rest,
children: cloneChildren
}
);
};
RatingIcon.displayName = "RatingIcon";
RatingIcon.__ui__ = "RatingIcon";
var RatingStarIcon = ({ ...rest }) => {
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
import_icon.Icon,
{
strokeLinecap: "round",
strokeLinejoin: "round",
viewBox: "0 0 24 24",
...rest,
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" })
}
);
};
RatingStarIcon.displayName = "RatingStarIcon";
RatingStarIcon.__ui__ = "RatingStarIcon";
// src/rating-utils.ts
var getRoundedValue = (value, to) => {
var _a;
const rounded = Math.round(value / to) * to;
const precision = ((_a = `${to}`.split(".")[1]) == null ? void 0 : _a.length) || 0;
return Number(rounded.toFixed(precision));
};
// src/rating-group.tsx
var import_jsx_runtime2 = require("react/jsx-runtime");
var RatingGroup = (0, import_motion.motionForwardRef)(
({ className, color, items, value: groupValue, ...rest }, ref) => {
const { decimal, styles, getGroupProps, groupProps } = useRatingContext();
const computedGroupProps = (0, import_utils4.runIfFunc)(groupProps, groupValue);
const css = { ...styles.group };
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
import_motion.motion.div,
{
className: (0, import_utils4.cx)("ui-rating__group", className),
__css: css,
...getGroupProps(
{ value: groupValue, ...computedGroupProps, ...rest },
ref
),
children: Array(items).fill(0).map((_, index) => {
const fractionValue = decimal * (groupValue === 1 ? index : index + 1);
const value = getRoundedValue(
groupValue - 1 + fractionValue,
decimal
);
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
RatingItem,
{
color,
fractionValue,
groupValue,
value
},
`${groupValue}-${fractionValue}`
);
})
}
);
}
);
RatingGroup.displayName = "RatingGroup";
RatingGroup.__ui__ = "RatingGroup";
// src/use-rating.tsx
var import_jsx_runtime3 = require("react/jsx-runtime");
var useRating = ({
name,
color,
defaultValue = 0,
emptyIcon,
filledIcon,
fractions = 1,
highlightSelectedOnly = false,
items = 5,
value: valueProp,
groupProps,
inputProps,
itemProps,
onChange: onChangeProp,
onHover,
onMouseEnter: onMouseEnterProp,
onMouseLeave: onMouseLeaveProp,
onMouseMove: onMouseMoveProp,
onTouchEnd: onTouchEndProp,
onTouchStart: onTouchStartProp,
...props
}) => {
const uuid = (0, import_react3.useId)();
const { id = uuid, ...rest } = (0, import_form_control.useFormControlProps)(props);
const containerRef = (0, import_react3.useRef)(null);
const [value, setValue] = (0, import_use_controllable_state.useControllableState)({
defaultValue,
value: valueProp,
onChange: onChangeProp
});
const [hoveredValue, setHoveredValue] = (0, import_react3.useState)(-1);
const [outside, setOutside] = (0, import_react3.useState)(true);
const [formControlProps, containerProps] = (0, import_utils5.splitObject)(
rest,
import_form_control.formControlProperties
);
const { disabled, readOnly, ...omittedFormControlProps } = formControlProps;
const resolvedFractions = Math.floor(fractions);
const resolvedItems = Math.floor(items);
const decimal = 1 / resolvedFractions;
const roundedValue = getRoundedValue(value, decimal);
const resolvedValue = hoveredValue !== -1 ? hoveredValue : roundedValue;
name != null ? name : name = `rating-${id}`;
const getHoveredValue = (0, import_react3.useCallback)(
(x) => {
const { left, width } = containerRef.current.getBoundingClientRect();
const itemWidth = width / resolvedItems;
const hoveredValue2 = (x - left) / itemWidth;
const value2 = (0, import_utils5.clampNumber)(
getRoundedValue(hoveredValue2 + decimal / 2, decimal),
decimal,
resolvedItems
);
return value2;
},
[decimal, resolvedItems]
);
const onMouseEnter = (0, import_react3.useCallback)(() => {
if (!disabled && !readOnly) setOutside(false);
}, [disabled, readOnly]);
const onMouseLeave = (0, import_react3.useCallback)(() => {
if (disabled || readOnly) return;
setHoveredValue(-1);
setOutside(true);
if (hoveredValue !== -1) onHover == null ? void 0 : onHover(-1);
}, [disabled, hoveredValue, onHover, readOnly, setHoveredValue]);
const onTouchStart = (0, import_react3.useCallback)(
(ev) => {
ev.preventDefault();
const el = ev.touches[0];
if (!el) return;
const value2 = getHoveredValue(el.clientX);
setValue(value2);
},
[getHoveredValue, setValue]
);
const onTouchEnd = (0, import_react3.useCallback)((ev) => {
ev.preventDefault();
}, []);
const onMouseMove = (0, import_react3.useCallback)(
(ev) => {
if (disabled || readOnly) return;
const roundedValue2 = getHoveredValue(ev.clientX);
setHoveredValue(roundedValue2);
if (roundedValue2 !== hoveredValue) onHover == null ? void 0 : onHover(roundedValue2);
},
[disabled, getHoveredValue, hoveredValue, readOnly, onHover]
);
const getContainerProps = (0, import_react3.useCallback)(
(props2 = {}, ref = null) => ({
ref: (0, import_utils5.mergeRefs)(ref, containerRef),
"aria-label": `${value} Stars`,
role: "radiogroup",
...omittedFormControlProps,
...containerProps,
...props2,
id,
onMouseEnter: (0, import_utils5.handlerAll)(
onMouseEnter,
props2.onMouseEnter,
onMouseEnterProp
),
onMouseLeave: (0, import_utils5.handlerAll)(
onMouseLeave,
props2.onMouseLeave,
onMouseLeaveProp
),
onMouseMove: (0, import_utils5.handlerAll)(onMouseMove, props2.onMouseMove, onMouseMoveProp),
onTouchEnd: (0, import_utils5.handlerAll)(onTouchEnd, props2.onTouchEnd, onTouchEndProp),
onTouchStart: (0, import_utils5.handlerAll)(
onTouchStart,
props2.onTouchStart,
onTouchStartProp
)
}),
[
omittedFormControlProps,
containerProps,
id,
value,
onMouseEnter,
onMouseEnterProp,
onMouseLeave,
onMouseLeaveProp,
onMouseMove,
onMouseMoveProp,
onTouchEnd,
onTouchEndProp,
onTouchStart,
onTouchStartProp
]
);
const getGroupProps = (0, import_react3.useCallback)(
({ value: value2, ...props2 }, ref = null) => {
const isActive = !readOnly && Math.ceil(hoveredValue) === value2;
return {
ref,
whileTap: !disabled && !readOnly ? { y: -4 } : void 0,
...props2,
"data-active": (0, import_utils5.dataAttr)(isActive),
tabIndex: -1
};
},
[disabled, hoveredValue, readOnly]
);
const children = Array(resolvedItems).fill(0).map((_, index) => {
const value2 = index + 1;
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
RatingGroup,
{
color: (0, import_utils5.runIfFunc)(color, value2),
items: index === 0 ? resolvedFractions + 1 : resolvedFractions,
value: value2
},
value2
);
});
return {
id,
name,
children,
decimal,
emptyIcon,
filledIcon,
highlightSelectedOnly,
hoveredValue,
outside,
resolvedValue,
roundedValue,
setHoveredValue,
setValue,
value,
formControlProps,
getContainerProps,
getGroupProps,
groupProps,
inputProps,
itemProps
};
};
// src/rating.tsx
var import_jsx_runtime4 = require("react/jsx-runtime");
var Rating = (0, import_core2.forwardRef)((props, ref) => {
const [styles, mergedProps] = (0, import_core2.useComponentMultiStyle)("Rating", props);
const { className, ...computedProps } = (0, import_core2.omitThemeProps)(mergedProps);
const { children, getContainerProps, ...rest } = useRating(computedProps);
const css = { display: "flex", ...styles.container };
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RatingProvider, { value: { styles, ...rest }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
import_core2.ui.div,
{
className: (0, import_utils6.cx)("ui-rating", className),
__css: css,
...getContainerProps({}, ref),
children
}
) });
});
Rating.displayName = "Rating";
Rating.__ui__ = "Rating";
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Rating
});
//# sourceMappingURL=rating.js.map