From 6e4a9cf5cde1babee21c204765c9d24b963992e9 Mon Sep 17 00:00:00 2001 From: Blake Vandercar Date: Thu, 7 Nov 2024 12:00:35 -0700 Subject: [PATCH 1/4] feat(SegmentedControl): allow for passing stricter type for options --- .../segmented-control/segmented-control.md | 19 ++ .../segmented-control/segmentedControl.tsx | 242 ++++++++++-------- .../core-examples/common/alignmentSelect.tsx | 10 +- .../core-examples/common/layoutSelect.tsx | 32 +-- .../core-examples/common/sizeSelect.tsx | 34 ++- .../examples/core-examples/drawerExample.tsx | 4 +- .../core-examples/menuItemExample.tsx | 13 +- .../core-examples/multistepDialogExample.tsx | 7 +- .../core-examples/nonIdealStateExample.tsx | 32 +-- .../core-examples/segmentedControlExample.tsx | 37 +-- 10 files changed, 217 insertions(+), 213 deletions(-) diff --git a/packages/core/src/components/segmented-control/segmented-control.md b/packages/core/src/components/segmented-control/segmented-control.md index 9faac93e80..dbf441949c 100644 --- a/packages/core/src/components/segmented-control/segmented-control.md +++ b/packages/core/src/components/segmented-control/segmented-control.md @@ -40,6 +40,25 @@ Options are specified as `OptionProps` objects, just like [RadioGroup](#core/com /> ``` +Options type is `string` by default, but can be made stricter, i.e. + +```tsx +// enum OptionType + + + options={[ + { + label: OptionType.VALUE_1, + value: OptionType.VALUE_1, + }, + { + label: OptionType.VALUE_2, + value: OptionType.VALUE_2, + }, + ]} +/> +``` + @## Props interface @interface SegmentedControlProps diff --git a/packages/core/src/components/segmented-control/segmentedControl.tsx b/packages/core/src/components/segmented-control/segmentedControl.tsx index ee48c4d4bb..489af98acd 100644 --- a/packages/core/src/components/segmented-control/segmentedControl.tsx +++ b/packages/core/src/components/segmented-control/segmentedControl.tsx @@ -33,9 +33,9 @@ export type SegmentedControlIntent = typeof Intent.NONE | typeof Intent.PRIMARY; /** * SegmentedControl component props. */ -export interface SegmentedControlProps +export interface SegmentedControlProps extends Props, - ControlledValueProps, + ControlledValueProps, React.RefAttributes { /** * Whether the control should take up the full width of its container. @@ -64,7 +64,7 @@ export interface SegmentedControlProps /** * List of available options. */ - options: Array>; + options: Array>; /** * Aria role for the overall component. Child buttons get appropriate roles. @@ -83,133 +83,151 @@ export interface SegmentedControlProps small?: boolean; } +// This allows the ability to pass a more strict type for `options`/`onValueChange` +// i.e. /> +interface ReactFCWithGeneric extends React.FC { + (props: SegmentedControlProps): ReturnType>>; +} + /** * Segmented control component. * * @see https://blueprintjs.com/docs/#core/components/segmented-control */ -export const SegmentedControl: React.FC = React.forwardRef((props, ref) => { - const { - className, - defaultValue, - fill, - inline, - intent, - large, - onValueChange, - options, - role = "radiogroup", - small, - value: controlledValue, - ...htmlProps - } = props; - - const [localValue, setLocalValue] = React.useState(defaultValue); - const selectedValue = controlledValue ?? localValue; - - const outerRef = React.useRef(null); - - const handleOptionClick = React.useCallback( - (newSelectedValue: string, targetElement: HTMLElement) => { - setLocalValue(newSelectedValue); - onValueChange?.(newSelectedValue, targetElement); - }, - [onValueChange], - ); - - const handleKeyDown = React.useCallback( - (e: React.KeyboardEvent) => { - if (role === "radiogroup") { - // in a `radiogroup`, arrow keys select next item, not tab key. - const direction = Utils.getArrowKeyDirection(e, ["ArrowLeft", "ArrowUp"], ["ArrowRight", "ArrowDown"]); - const { current: outerElement } = outerRef; - if (direction === undefined || !outerElement) return; - - const focusedElement = Utils.getActiveElement(outerElement)?.closest("button"); - if (!focusedElement) return; - - // must rely on DOM state because we have no way of mapping `focusedElement` to a React.JSX.Element - const enabledOptionElements = Array.from( - outerElement.querySelectorAll("button:not(:disabled)"), - ); - const focusedIndex = enabledOptionElements.indexOf(focusedElement); - if (focusedIndex < 0) return; - - e.preventDefault(); - // auto-wrapping at 0 and `length` - const newIndex = - (focusedIndex + direction + enabledOptionElements.length) % enabledOptionElements.length; - const newOption = enabledOptionElements[newIndex]; - newOption.click(); - newOption.focus(); - } - }, - [outerRef, role], - ); - - const classes = classNames(Classes.SEGMENTED_CONTROL, className, { - [Classes.FILL]: fill, - [Classes.INLINE]: inline, - }); - - const isAnySelected = options.some(option => selectedValue === option.value); - - return ( -
- {options.map((option, index) => { - const isSelected = selectedValue === option.value; - return ( - - ); - })} -
- ); -}); +export const SegmentedControl: ReactFCWithGeneric = React.forwardRef( + (props: SegmentedControlProps, ref: React.ForwardedRef) => { + const { + className, + defaultValue, + fill, + inline, + intent, + large, + onValueChange, + options, + role = "radiogroup", + small, + value: controlledValue, + ...htmlProps + } = props; + + const [localValue, setLocalValue] = React.useState(defaultValue); + const selectedValue = controlledValue ?? localValue; + + const outerRef = React.useRef(null); + + const handleOptionClick = React.useCallback( + (newSelectedValue: T, targetElement: HTMLElement) => { + setLocalValue(newSelectedValue); + onValueChange?.(newSelectedValue, targetElement); + }, + [onValueChange], + ); + + const handleKeyDown = React.useCallback( + (e: React.KeyboardEvent) => { + if (role === "radiogroup") { + // in a `radiogroup`, arrow keys select next item, not tab key. + const direction = Utils.getArrowKeyDirection( + e, + ["ArrowLeft", "ArrowUp"], + ["ArrowRight", "ArrowDown"], + ); + const outerElement = outerRef.current; + if (direction === undefined || !outerElement) return; + + const focusedElement = Utils.getActiveElement(outerElement)?.closest("button"); + if (!focusedElement) return; + + // must rely on DOM state because we have no way of mapping `focusedElement` to a React.JSX.Element + const enabledOptionElements = Array.from( + outerElement.querySelectorAll("button:not(:disabled)"), + ); + const focusedIndex = enabledOptionElements.indexOf(focusedElement); + if (focusedIndex < 0) return; + + e.preventDefault(); + // auto-wrapping at 0 and `length` + const newIndex = + (focusedIndex + direction + enabledOptionElements.length) % enabledOptionElements.length; + const newOption = enabledOptionElements[newIndex]; + newOption.click(); + newOption.focus(); + } + }, + [outerRef, role], + ); + + const classes = classNames(Classes.SEGMENTED_CONTROL, className, { + [Classes.FILL]: fill, + [Classes.INLINE]: inline, + }); + + const isAnySelected = options.some(option => selectedValue === option.value); + + return ( +
+ {options.map((option, index) => { + const isSelected = selectedValue === option.value; + return ( + + {...option} + intent={intent} + isSelected={isSelected} + key={option.value} + large={large} + onClick={handleOptionClick} + small={small} + {...(role === "radiogroup" + ? { + "aria-checked": isSelected, + role: "radio", + // "roving tabIndex" on a radiogroup: https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex + // `!isAnySelected` accounts for case where no value is currently selected + // (passed value/defaultValue is not one of the values of the passed options.) + // In this case, set first item to be tabbable even though it's unselected. + tabIndex: isSelected || (index === 0 && !isAnySelected) ? 0 : -1, + } + : { + "aria-pressed": isSelected, + })} + /> + ); + })} +
+ ); + }, +); SegmentedControl.defaultProps = { defaultValue: undefined, intent: Intent.NONE, }; SegmentedControl.displayName = `${DISPLAYNAME_PREFIX}.SegmentedControl`; -interface SegmentedControlOptionProps - extends OptionProps, +interface SegmentedControlOptionProps + extends OptionProps, Pick, Pick, React.AriaAttributes { isSelected: boolean; - onClick: (value: string, targetElement: HTMLElement) => void; + onClick: (value: T, targetElement: HTMLElement) => void; } -function SegmentedControlOption({ isSelected, label, onClick, value, ...buttonProps }: SegmentedControlOptionProps) { +function SegmentedControlOption({ + isSelected, + label, + onClick, + value, + ...buttonProps +}: SegmentedControlOptionProps) { const handleClick = React.useCallback( - (event: React.MouseEvent) => onClick?.(value, event.currentTarget), + (event: React.MouseEvent) => onClick(value, event.currentTarget), [onClick, value], ); diff --git a/packages/docs-app/src/examples/core-examples/common/alignmentSelect.tsx b/packages/docs-app/src/examples/core-examples/common/alignmentSelect.tsx index 112cbd7b6c..8ba67bd221 100644 --- a/packages/docs-app/src/examples/core-examples/common/alignmentSelect.tsx +++ b/packages/docs-app/src/examples/core-examples/common/alignmentSelect.tsx @@ -37,11 +37,15 @@ export const AlignmentSelect: React.FC = ({ { label: "Right", value: Alignment.RIGHT }, ].filter(Boolean); - const handleChange = React.useCallback((value: string) => onChange(value as Alignment), [onChange]); - return ( - + + small={true} + fill={true} + options={options} + onValueChange={onChange} + value={align} + /> ); }; diff --git a/packages/docs-app/src/examples/core-examples/common/layoutSelect.tsx b/packages/docs-app/src/examples/core-examples/common/layoutSelect.tsx index e391681367..aa6712183a 100644 --- a/packages/docs-app/src/examples/core-examples/common/layoutSelect.tsx +++ b/packages/docs-app/src/examples/core-examples/common/layoutSelect.tsx @@ -26,21 +26,17 @@ export interface LayoutSelectProps { } /** Button radio group to switch between horizontal and vertical layouts. */ -export const LayoutSelect: React.FC = ({ layout, onChange }) => { - const handleChange = React.useCallback((value: string) => onChange(value as Layout), [onChange]); - - return ( - - - - ); -}; +export const LayoutSelect: React.FC = ({ layout, onChange }) => ( + + + fill={true} + onValueChange={onChange} + options={[ + { label: "Horizontal", value: "horizontal" }, + { label: "Vertical", value: "vertical" }, + ]} + small={true} + value={layout} + /> + +); diff --git a/packages/docs-app/src/examples/core-examples/common/sizeSelect.tsx b/packages/docs-app/src/examples/core-examples/common/sizeSelect.tsx index 20d845b54e..31413881c6 100644 --- a/packages/docs-app/src/examples/core-examples/common/sizeSelect.tsx +++ b/packages/docs-app/src/examples/core-examples/common/sizeSelect.tsx @@ -27,25 +27,21 @@ export interface SizeSelectProps { onChange: (size: Size) => void; } -export const SizeSelect: React.FC = ({ label, size, optionLabels, onChange }) => { - const handleChange = React.useCallback((value: string) => onChange(value as Size), [onChange]); - - return ( - - - - ); -}; +export const SizeSelect: React.FC = ({ label, size, optionLabels, onChange }) => ( + + + fill={true} + small={true} + options={[ + { label: optionLabels[0], value: "small" }, + { label: optionLabels[1], value: "regular" }, + { label: optionLabels[2], value: "large" }, + ]} + onValueChange={onChange} + value={size} + /> + +); SizeSelect.defaultProps = { label: "Size", optionLabels: ["Small", "Regular", "Large"], diff --git a/packages/docs-app/src/examples/core-examples/drawerExample.tsx b/packages/docs-app/src/examples/core-examples/drawerExample.tsx index 54f4ca2e7e..12982d795e 100644 --- a/packages/docs-app/src/examples/core-examples/drawerExample.tsx +++ b/packages/docs-app/src/examples/core-examples/drawerExample.tsx @@ -72,7 +72,7 @@ export class DrawerExample extends React.PureComponent this.setState({ usePortal })); - private handlePositionChange = (position: string) => this.setState({ position: position as Position }); + private handlePositionChange = (position: Position) => this.setState({ position }); private handleOutsideClickChange = handleBooleanChange(val => this.setState({ canOutsideClickClose: val })); @@ -147,7 +147,7 @@ export class DrawerExample extends React.PureComponent
Props
- fill={true} options={[ { label: Position.TOP, value: Position.TOP }, diff --git a/packages/docs-app/src/examples/core-examples/menuItemExample.tsx b/packages/docs-app/src/examples/core-examples/menuItemExample.tsx index 2bb403b5fc..855d32c5bf 100644 --- a/packages/docs-app/src/examples/core-examples/menuItemExample.tsx +++ b/packages/docs-app/src/examples/core-examples/menuItemExample.tsx @@ -35,6 +35,8 @@ import { PropCodeTooltip } from "../../common/propCodeTooltip"; import { BooleanOrUndefinedSelect } from "./common/booleanOrUndefinedSelect"; import { IntentSelect } from "./common/intentSelect"; +type RoleStructure = MenuItemProps["roleStructure"]; + export function MenuItemExample(props: ExampleProps) { const [active, setActive] = React.useState(false); const [disabled, setDisabled] = React.useState(false); @@ -42,12 +44,7 @@ export function MenuItemExample(props: ExampleProps) { const [intent, setIntent] = React.useState("none"); const [iconEnabled, setIconEnabled] = React.useState(true); const [submenuEnabled, setSubmenuEnabled] = React.useState(true); - const [roleStructure, setRoleStructure] = React.useState("menuitem"); - - const handleRoleStructureChange = React.useCallback( - (newValue: string) => setRoleStructure(newValue as MenuItemProps["roleStructure"]), - [], - ); + const [roleStructure, setRoleStructure] = React.useState("menuitem"); const isSelectable = roleStructure === "listoption"; @@ -78,12 +75,12 @@ export function MenuItemExample(props: ExampleProps) { - options={[ { label: "menuitem", value: "menuitem" }, { label: "listoption", value: "listoption" }, ]} - onValueChange={handleRoleStructureChange} + onValueChange={setRoleStructure} small={true} value={roleStructure} /> diff --git a/packages/docs-app/src/examples/core-examples/multistepDialogExample.tsx b/packages/docs-app/src/examples/core-examples/multistepDialogExample.tsx index 166cc14ef4..cb1219215d 100644 --- a/packages/docs-app/src/examples/core-examples/multistepDialogExample.tsx +++ b/packages/docs-app/src/examples/core-examples/multistepDialogExample.tsx @@ -52,7 +52,7 @@ export interface MultistepDialogExampleState { initialStepIndex: number; } -const NAV_POSITIONS = ["left", "top", "right"]; +const NAV_POSITIONS = ["left", "top", "right"] as const; export class MultistepDialogExample extends React.PureComponent< ExampleProps, @@ -90,8 +90,7 @@ export class MultistepDialogExample extends React.PureComponent< private handleHasTitleChange = handleBooleanChange(hasTitle => this.setState({ hasTitle })); - private handleNavPositionChange = (newValue: string) => - this.setState({ navPosition: newValue as MultistepDialogNavPosition }); + private handleNavPositionChange = (navPosition: MultistepDialogNavPosition) => this.setState({ navPosition }); public render() { const finalButtonProps: Partial = { @@ -171,7 +170,7 @@ export class MultistepDialogExample extends React.PureComponent< - fill={true} onValueChange={this.handleNavPositionChange} options={NAV_POSITIONS.map(p => ({ label: p, value: p }))} diff --git a/packages/docs-app/src/examples/core-examples/nonIdealStateExample.tsx b/packages/docs-app/src/examples/core-examples/nonIdealStateExample.tsx index 64a592366e..48f0b9450a 100644 --- a/packages/docs-app/src/examples/core-examples/nonIdealStateExample.tsx +++ b/packages/docs-app/src/examples/core-examples/nonIdealStateExample.tsx @@ -138,21 +138,17 @@ type NonIdealStateVisualKind = "icon" | "spinner"; const NonIdealStateVisualSelect: React.FC<{ visual: NonIdealStateVisualKind; onChange: (option: NonIdealStateVisualKind) => void; -}> = ({ visual, onChange }) => { - const handleChange = React.useCallback((value: string) => onChange(value as NonIdealStateVisualKind), [onChange]); - - return ( - - - - ); -}; +}> = ({ visual, onChange }) => ( + + + fill={true} + onValueChange={onChange} + options={[ + { label: "Icon", value: "icon" }, + { label: "Spinner", value: "spinner" }, + ]} + small={true} + value={visual} + /> + +); diff --git a/packages/docs-app/src/examples/core-examples/segmentedControlExample.tsx b/packages/docs-app/src/examples/core-examples/segmentedControlExample.tsx index 272a5ca519..ba19aaf251 100644 --- a/packages/docs-app/src/examples/core-examples/segmentedControlExample.tsx +++ b/packages/docs-app/src/examples/core-examples/segmentedControlExample.tsx @@ -23,8 +23,6 @@ import { type Size, SizeSelect } from "./common/sizeSelect"; export const SegmentedControlExample: React.FC = props => { const [intent, setIntent] = React.useState("none"); - const handleIntentChange = React.useCallback(newIntent => setIntent(newIntent as SegmentedControlIntent), []); - const [fill, setFill] = React.useState(false); const [inline, setInline] = React.useState(false); const [size, setSize] = React.useState("small"); @@ -36,20 +34,14 @@ export const SegmentedControlExample: React.FC = props => { - defaultValue="none" inline={true} options={[ - { - label: "None", - value: "none", - }, - { - label: "Primary", - value: "primary", - }, + { label: "None", value: "none" }, + { label: "Primary", value: "primary" }, ]} - onValueChange={handleIntentChange} + onValueChange={setIntent} small={true} /> @@ -65,23 +57,10 @@ export const SegmentedControlExample: React.FC = props => { inline={inline} intent={intent} options={[ - { - label: "List", - value: "list", - }, - { - label: "Grid", - value: "grid", - }, - { - disabled: true, - label: "Disabled", - value: "disabled", - }, - { - label: "Gallery", - value: "gallery", - }, + { label: "List", value: "list" }, + { label: "Grid", value: "grid" }, + { disabled: true, label: "Disabled", value: "disabled" }, + { label: "Gallery", value: "gallery" }, ]} large={size === "large"} small={size === "small"} From 7cde4c207fb76b162c4ebc6e69298d1048ec398a Mon Sep 17 00:00:00 2001 From: Blake Vandercar Date: Wed, 13 Nov 2024 08:49:32 -0700 Subject: [PATCH 2/4] rename type --- .../src/components/segmented-control/segmentedControl.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/segmented-control/segmentedControl.tsx b/packages/core/src/components/segmented-control/segmentedControl.tsx index 489af98acd..8ae92f6324 100644 --- a/packages/core/src/components/segmented-control/segmentedControl.tsx +++ b/packages/core/src/components/segmented-control/segmentedControl.tsx @@ -83,9 +83,9 @@ export interface SegmentedControlProps small?: boolean; } -// This allows the ability to pass a more strict type for `options`/`onValueChange` -// i.e. /> -interface ReactFCWithGeneric extends React.FC { +// This makes the react FC a generic, allowing the ability to pass a more strict +// type for `options`/`onValueChange` i.e. /> +interface SegmentedControlReactFC extends React.FC { (props: SegmentedControlProps): ReturnType>>; } @@ -94,7 +94,7 @@ interface ReactFCWithGeneric extends React.FC { * * @see https://blueprintjs.com/docs/#core/components/segmented-control */ -export const SegmentedControl: ReactFCWithGeneric = React.forwardRef( +export const SegmentedControl: SegmentedControlReactFC = React.forwardRef( (props: SegmentedControlProps, ref: React.ForwardedRef) => { const { className, From 56be76a73805898283606ae605c1513fa99f3e28 Mon Sep 17 00:00:00 2001 From: Blake Vandercar Date: Tue, 19 Nov 2024 09:48:47 -0700 Subject: [PATCH 3/4] run format --- .../segmented-control/segmentedControl.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/segmented-control/segmentedControl.tsx b/packages/core/src/components/segmented-control/segmentedControl.tsx index 1c7758949b..8ae92f6324 100644 --- a/packages/core/src/components/segmented-control/segmentedControl.tsx +++ b/packages/core/src/components/segmented-control/segmentedControl.tsx @@ -124,13 +124,17 @@ export const SegmentedControl: SegmentedControlReactFC = React.forwardRef( [onValueChange], ); - const handleKeyDown = React.useCallback( - (e: React.KeyboardEvent) => { - if (role === "radiogroup") { - // in a `radiogroup`, arrow keys select next item, not tab key. - const direction = Utils.getArrowKeyDirection(e, ["ArrowLeft", "ArrowUp"], ["ArrowRight", "ArrowDown"]); - const outerElement = outerRef.current; - if (direction === undefined || !outerElement) return; + const handleKeyDown = React.useCallback( + (e: React.KeyboardEvent) => { + if (role === "radiogroup") { + // in a `radiogroup`, arrow keys select next item, not tab key. + const direction = Utils.getArrowKeyDirection( + e, + ["ArrowLeft", "ArrowUp"], + ["ArrowRight", "ArrowDown"], + ); + const outerElement = outerRef.current; + if (direction === undefined || !outerElement) return; const focusedElement = Utils.getActiveElement(outerElement)?.closest("button"); if (!focusedElement) return; From ee2ac5e43c431eaf851cfe9b9cdee54d395811ae Mon Sep 17 00:00:00 2001 From: Blake Vandercar Date: Tue, 7 Jan 2025 18:49:37 -0700 Subject: [PATCH 4/4] rename to match others --- packages/core/src/components/icon/icon.tsx | 4 ++-- .../segmented-control/segmentedControl.tsx | 15 ++++++++++----- packages/icons/src/svgIconContainer.tsx | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/icon/icon.tsx b/packages/core/src/components/icon/icon.tsx index 2d708d2668..9a2fe7577d 100644 --- a/packages/core/src/components/icon/icon.tsx +++ b/packages/core/src/components/icon/icon.tsx @@ -94,8 +94,8 @@ export interface DefaultIconProps extends IntentProps, Props, DefaultSVGIconProp } /** - * Generic icon component type. This is essentially a type hack required to make forwardRef work with generic - * components. Note that this slows down TypeScript compilation, but it better than the alternative of globally + * Generic component type. This is essentially a type hack required to make forwardRef work with generic + * components. Note that this slows down TypeScript compilation, but is better than the alternative of globally * augmenting "@types/react". * * @see https://stackoverflow.com/a/73795494/7406866 diff --git a/packages/core/src/components/segmented-control/segmentedControl.tsx b/packages/core/src/components/segmented-control/segmentedControl.tsx index 884307a75c..634bba2bbf 100644 --- a/packages/core/src/components/segmented-control/segmentedControl.tsx +++ b/packages/core/src/components/segmented-control/segmentedControl.tsx @@ -83,10 +83,15 @@ export interface SegmentedControlProps small?: boolean; } -// This makes the react FC a generic, allowing the ability to pass a more strict -// type for `options`/`onValueChange` i.e. /> -interface SegmentedControlReactFC extends React.FC { - (props: SegmentedControlProps): ReturnType>>; +/** + * Generic component type. This is essentially a type hack required to make forwardRef work with generic + * components. Note that this slows down TypeScript compilation, but is better than the alternative of globally + * augmenting "@types/react". + * + * @see https://stackoverflow.com/a/73795494/7406866 + */ +export interface SegmentedControlComponent extends React.FC { + (props: SegmentedControlProps): React.ReactElement | null; } /** @@ -94,7 +99,7 @@ interface SegmentedControlReactFC extends React.FC { * * @see https://blueprintjs.com/docs/#core/components/segmented-control */ -export const SegmentedControl: SegmentedControlReactFC = React.forwardRef( +export const SegmentedControl: SegmentedControlComponent = React.forwardRef( (props: SegmentedControlProps, ref: React.ForwardedRef) => { const { className, diff --git a/packages/icons/src/svgIconContainer.tsx b/packages/icons/src/svgIconContainer.tsx index c84b7c1942..ca390fb5ba 100644 --- a/packages/icons/src/svgIconContainer.tsx +++ b/packages/icons/src/svgIconContainer.tsx @@ -35,8 +35,8 @@ export type SVGIconContainerProps = Omit, "ch }; /** - * Generic icon container component type. This is essentially a type hack required to make forwardRef work with generic - * components. Note that this slows down TypeScript compilation, but it better than the alternative of globally + * Generic component type. This is essentially a type hack required to make forwardRef work with generic + * components. Note that this slows down TypeScript compilation, but is better than the alternative of globally * augmenting "@types/react". * * @see https://stackoverflow.com/a/73795494/7406866