@react-md/form
Version:
This package is for creating all the different form input types.
124 lines (111 loc) • 4.09 kB
text/typescript
import type { ReactNode } from "react";
import { createElement } from "react";
import { TextIconSpacing } from "@react-md/icon";
import type { SimpleListItemProps } from "@react-md/list";
export interface ListboxOptionProps extends SimpleListItemProps {
[labelKey: string]: ReactNode;
}
export type ListboxOption = ListboxOptionProps | string | number | null;
/**
* A type guard that simply checks if the option is considered an object of list
* item props.
*
* @param option - The option to check
* @returns true if the option is considered a object of list item props and
* will ensure that the option is typed as ListboxOptionProps
* @internal
*/
export function isListboxOptionProps(
option: ListboxOption
): option is ListboxOptionProps {
return (
option !== "" && option !== 0 && !!option && typeof option === "object"
);
}
/**
* The default implementation to check if an option is disabled. It will just
* check if the option is an object and if it has the disabled prop enabled.
*
* @param option - The option to check
* @returns true if the option is disabled
* @internal
*/
export function defaultIsOptionDisabled(option: ListboxOption): boolean {
return isListboxOptionProps(option) && !!option.disabled;
}
/**
* The default way to generate a "unique" id for each option within the listbox
* by concatenating the current index with a base id.
*
* Note: The index will be incremented by 1 so the ids start from 1 instead of
* 0. This is so that it matches how paginated results work with `aria-posinset`
* + `aria-setsize`.
*
* @param baseId - The base id of the listbox.
* @param index - The current index of the option
* @returns a "unique" id for the option
*/
export function getOptionId(baseId: string, index: number): string {
return `${baseId}-option-${index + 1}`;
}
/**
* A function that will get the label for an option. The default behavior is to
* check if the option is an object. If it is, it'll use the `labelKey` property
* and fallback to the `children` property. If it is anything else, the option
* itself will be returned.
*
* This is used in both the select's button element to show the current value as
* well as rendering each option within the listbox component.
*
* @param option - The option that should be converted into a renderable label
* element.
* @param labelKey - The object key to use to extract the label from an option
* object.
* @returns a renderable label to display.
*/
export function getOptionLabel(
option: ListboxOption,
labelKey: string
): ReactNode {
if (isListboxOptionProps(option)) {
if (typeof option.children !== "undefined") {
return option.children;
}
const label = option[labelKey];
return typeof label === "undefined" ? null : label;
}
return option;
}
/**
* A function that will get the display value for the `Select` field based on
* the current selected option. The default behavior will be to return null if
* an option is not currently selected so the placeholder text can be shown
* instead. If there is an option selected, it will:
* - get the option's label using the general `getOptionLabel` util
* - check if includeLeft is enabled and the option is an object with
* `leftAddon`
* - if there is a `leftAddon`, use the `TextIconSpacing` of
* the label + the icon or avatar.
*
* @param option - The option to get a display label for
* @param labelKey - The key to use to extract a label from the option when it
* is an object
* @param includeLeft - Boolean if a `leftAddon` should be added with
* `TextIconSpacing` to the result.
* @returns A renderable node to display in a `Select` field.
*/
export function getDisplayLabel(
option: ListboxOption,
labelKey: string,
includeLeft: boolean
): ReactNode {
if (!option && option !== 0) {
return null;
}
const label = getOptionLabel(option, labelKey);
if (!includeLeft || !isListboxOptionProps(option)) {
return label;
}
const { leftAddon } = option;
return createElement(TextIconSpacing, { icon: leftAddon }, label);
}