"use client";
import { cnb } from "cnbuilder";
import { Fragment, type ReactElement, type ReactNode, type Ref } from "react";
import {
TextIconSpacing,
type TextIconSpacingProps,
} from "../icon/TextIconSpacing.js";
import { getIcon } from "../icon/config.js";
import { icon } from "../icon/styles.js";
import { ListItemText } from "../list/ListItemText.js";
import { getListItemHeight } from "../list/getListItemHeight.js";
import { MenuItem, type MenuItemProps } from "../menu/MenuItem.js";
import { useEnsuredId } from "../useEnsuredId.js";
import { useListboxContext } from "./ListboxProvider.js";
import { option } from "./optionStyles.js";
const noop = (): void => {
// do nothing
};
/**
* This icon is used while the option is unselected so that the selected and
* unselected options have the same alignment.
*
* @since 6.0.0
* @defaultValue `
);
/**
* @since 6.0.0
*/
export interface OptionSelectedIconProps {
/**
* @defaultValue `getIcon("selected")`
*/
selectedIcon?: ReactNode;
/**
* @see {@link DEFAULT_OPTION_UNSELECTED_ICON}
* @defaultValue ``
*/
unselectedIcon?: ReactNode;
/**
* Set this to `true` of the {@link selectedIcon}/{@link unselectedIcon}
* should appear as the {@link rightAddon} instead of the {@link leftAddon}.
*
* @defaultValue `false`
*/
selectedIconAfter?: boolean;
/**
* Set this to `true` to remove selected icon behavior from the `Option`.
*
* @defaultValue `false`
*/
disableSelectedIcon?: boolean;
}
/**
* @since 6.0.0 removed the `selected` and `focused` props.
* @since 6.0.0 Added the `value`, `selectedIcon`, `unselectedIcon`,
* `selectedIconAfter`, and `iconSpacingProps` props.
*/
export interface OptionProps extends MenuItemProps, OptionSelectedIconProps {
ref?: Ref;
/**
* @defaultValue `"option"`
*/
role?: string;
value: string | number | object;
/**
* An optional className to apply only while the current option is selected to
* override any global default selected styles. It is recommended to update
* the `react-md.$form-option-selected-styles` map first to change selected
* style globally and then any one-off customizations through this prop.
*
* @example Global Change
* ```scss
* @use "@react-md/core" with (
* // these are the defaults
* $form-option-selected-styles: (
* --rmd-icon-color: currentcolor,
* background-color: colors.$blue-900,
* color: colors.$white,
* ),
*
* // so if you wanted to remove the styles globally
* $form-option-selected-styles: (),
* );
* ```
*
* This really results in something like:
* ```ts
* className="rmd-list-item ... rmd-menu-item ... rmd-option rmd-option--selected ${selectedClassName}"
* ```
*/
selectedClassName?: string;
/**
* Since the `selectedIcon`/`unselectedIcon` are rendered as
* `leftAddon`/`rightAddon`, the provided `leftAddon`/`rightAddon` will be
* wrapped in the {@link TextIconSpacing} component to maintain the correct
* spacing. You can use this prop to provide any additional configuration to
* the spacing.
*
* @example
* ```tsx
*
* ```
*/
textIconSpacingProps?: Omit;
}
/**
* **Client Component**
*
* This component is a wrapper around the {@link MenuItem} to implement custom
* select option behavior.
*
* @see {@link https://react-md.dev/components/select | Select Demos}
* @since 6.0.0 removed the `selected` and `focused` props.
* @since 6.0.0 Added the `value`, `selectedIcon`, `unselectedIcon`,
* `selectedIconAfter`, `iconSpacingProps`, and `selectedClassName` props.
*/
export function Option(props: OptionProps): ReactElement {
const {
ref,
id: propId,
role = "option",
value,
children: propChildren,
onClick = noop,
className,
selectedClassName,
selectedIcon: propSelectedIcon,
unselectedIcon: propUnselectedIcon,
selectedIconAfter: propSelectedIconAfter,
disableSelectedIcon: propDisableSelectedIcon,
textIconSpacingProps,
leftAddon: propLeftAddon,
leftAddonType,
leftAddonClassName,
rightAddon: propRightAddon,
rightAddonType,
rightAddonClassName,
secondaryText,
height: propHeight,
disableTextChildren: propDisableTextChildren,
...remaining
} = props;
const id = useEnsuredId(propId, "option");
const {
selectOption,
isOptionSelected,
disableSelectedIcon: contextDisableSelectedIcon,
selectedIcon: contextSelectedIcon,
unselectedIcon: contextUnselectedIcon,
selectedIconAfter: contextSelectedIconAfter,
} = useListboxContext();
const selectedIconAfter = propSelectedIconAfter ?? contextSelectedIconAfter;
const disableSelectedIcon =
propDisableSelectedIcon ?? contextDisableSelectedIcon;
const selected = isOptionSelected(value);
const selectedIcon = getIcon(
"selected",
disableSelectedIcon ? null : (propSelectedIcon ?? contextSelectedIcon)
);
const unselectedIcon = disableSelectedIcon
? null
: (propUnselectedIcon ??
contextUnselectedIcon ??
DEFAULT_OPTION_UNSELECTED_ICON);
const icon = selected ? selectedIcon : unselectedIcon;
let leftAddon = propLeftAddon;
let rightAddon = propRightAddon;
let children = propChildren;
let disableTextChildren = propDisableTextChildren;
if (!selectedIconAfter && icon) {
leftAddon = icon;
if (propLeftAddon) {
disableTextChildren = true;
const Wrapper = propDisableTextChildren ? Fragment : ListItemText;
children = (
{children}
);
}
} else if (icon) {
rightAddon = icon;
if (propRightAddon) {
disableTextChildren = true;
const Wrapper = propDisableTextChildren ? Fragment : ListItemText;
children = (
{children}
);
}
}
const height = getListItemHeight({
height: propHeight,
leftAddon: leftAddon === icon ? null : leftAddon,
leftAddonType,
rightAddon: rightAddon === icon ? null : rightAddon,
rightAddonType,
secondaryText,
});
return (
);
}