diff --git a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap index d0d91ee4b6..a3d0bb3fbf 100644 --- a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap +++ b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap @@ -3991,6 +3991,7 @@ Use this to provide a unique accessible name for each button group on the page." "type": "string", }, { + "defaultValue": "false", "description": "Use this property to determine dropdown placement strategy for all menu dropdown items. By default, the dropdown height is constrained to fit inside the height of its next scrollable container element. Enabling this property will allow the dropdown to extend beyond that container by using fixed positioning and diff --git a/src/button-group/__tests__/button-group-dev.test.tsx b/src/button-group/__tests__/button-group-dev.test.tsx index c9d1d9d66f..5561eeb023 100644 --- a/src/button-group/__tests__/button-group-dev.test.tsx +++ b/src/button-group/__tests__/button-group-dev.test.tsx @@ -6,10 +6,13 @@ import { fireEvent } from '@testing-library/react'; import { warnOnce } from '@cloudscape-design/component-toolkit/internal'; import { ButtonGroupProps } from '../../../lib/components/button-group'; +import * as baseComponentHooks from '../../../lib/components/internal/hooks/use-base-component'; import { renderButtonGroup } from './common'; import buttonStyles from '../../../lib/components/button/styles.css.js'; +const useBaseComponentSpy = jest.spyOn(baseComponentHooks, 'default'); + jest.mock('@cloudscape-design/component-toolkit/internal', () => ({ ...jest.requireActual('@cloudscape-design/component-toolkit/internal'), warnOnce: jest.fn(), @@ -156,3 +159,34 @@ test('handles file upload', () => { expect(onFilesChange).toHaveBeenCalledWith(expect.objectContaining({ detail: { id: 'file', files: [file] } })); }); + +test('adds feature metrics', () => { + renderButtonGroup({ + items: [ + { type: 'icon-button', id: 'icon1', text: 'icon1' }, + { + type: 'group', + text: 'group1', + items: [ + { type: 'icon-toggle-button', id: 'toggle1', text: 'toggle1', pressed: false }, + { type: 'icon-toggle-button', id: 'toggle2', text: 'toggle2', pressed: false }, + ], + }, + { type: 'icon-file-input', id: 'file1', text: 'file1' }, + { type: 'menu-dropdown', id: 'menu1', text: 'menu1', items: [] }, + ], + }); + expect(useBaseComponentSpy).toHaveBeenCalledWith('ButtonGroup', { + props: { + variant: 'icon', + dropdownExpandToViewport: false, + }, + metadata: { + iconButtonsCount: 1, + iconToggleButtonsCount: 2, + iconFileInputsCount: 1, + menuDropdownsCount: 1, + groupsCount: 1, + }, + }); +}); diff --git a/src/button-group/index.tsx b/src/button-group/index.tsx index c9da236c5a..7c7ead96bd 100644 --- a/src/button-group/index.tsx +++ b/src/button-group/index.tsx @@ -12,13 +12,21 @@ import InternalButtonGroup from './internal'; export { ButtonGroupProps }; const ButtonGroup = React.forwardRef( - ({ variant, dropdownExpandToViewport, ...rest }: ButtonGroupProps, ref: React.Ref) => { + ({ variant, dropdownExpandToViewport = false, ...rest }: ButtonGroupProps, ref: React.Ref) => { const baseProps = getBaseProps(rest); + const itemCounts = getItemCounts(rest.items); const baseComponentProps = useBaseComponent('ButtonGroup', { props: { variant, dropdownExpandToViewport, }, + metadata: { + iconButtonsCount: itemCounts['icon-button'], + iconToggleButtonsCount: itemCounts['icon-toggle-button'], + iconFileInputsCount: itemCounts['icon-file-input'], + menuDropdownsCount: itemCounts['menu-dropdown'], + groupsCount: itemCounts.group, + }, }); const externalProps = getExternalProps(rest); @@ -35,5 +43,22 @@ const ButtonGroup = React.forwardRef( } ); +function getItemCounts(allItems: readonly ButtonGroupProps.ItemOrGroup[] = []) { + const counters = { 'icon-button': 0, 'icon-toggle-button': 0, 'icon-file-input': 0, 'menu-dropdown': 0, group: 0 }; + + function count(items: readonly ButtonGroupProps.ItemOrGroup[]) { + for (const item of items) { + counters[item.type] += 1; + + if (item.type === 'group') { + count(item.items); + } + } + } + count(allItems); + + return counters; +} + applyDisplayName(ButtonGroup, 'ButtonGroup'); export default ButtonGroup; diff --git a/src/button-group/interfaces.ts b/src/button-group/interfaces.ts index 9b17a13eaf..9880828926 100644 --- a/src/button-group/interfaces.ts +++ b/src/button-group/interfaces.ts @@ -6,6 +6,7 @@ import { IconProps } from '../icon/interfaces'; import { BaseComponentProps } from '../internal/base-component'; import { NonCancelableEventHandler } from '../internal/events'; import { InternalBaseComponentProps } from '../internal/hooks/use-base-component'; +import { SomeRequired } from '../internal/types'; export interface ButtonGroupProps extends BaseComponentProps { /** @@ -97,7 +98,9 @@ export interface ButtonGroupProps extends BaseComponentProps { onFilesChange?: NonCancelableEventHandler; } -export interface InternalButtonGroupProps extends ButtonGroupProps, InternalBaseComponentProps {} +export interface InternalButtonGroupProps + extends SomeRequired, + InternalBaseComponentProps {} export namespace ButtonGroupProps { export type Variant = 'icon';