Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "feat: add toolbar controlled state example for toggle buttons",
"packageName": "@fluentui/react-toolbar",
"email": "chassunc@microsoft.com",
"dependentChangeType": "none"
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import * as React from 'react';
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import { SelectableHandler } from '../../../../react-menu/src/index';

export type ToolbarSlots = {
root: Slot<'div'>;
};

export type ToolbarCheckedValueChangeData = {
/** The items for this value that are checked */
checkedItems: string[];
/** The name of the value */
name: string;
};

export type ToolbarCheckedValueChangeEvent = React.MouseEvent | React.KeyboardEvent;

/**
* Toolbar Props
*/
Expand All @@ -14,17 +25,45 @@ export type ToolbarProps = ComponentProps<ToolbarSlots> & {
* @default medium
*/
size?: 'small' | 'medium';

/**
* Map of all checked values
*/
checkedValues?: Record<string, string[]>;

/**
* Default values to be checked on mount
*/
defaultCheckedValues?: Record<string, string[]>;

/**
* Callback when checked items change for value with a name
*
* @param event - React's original SyntheticEvent
* @param data - A data object with relevant information
*/
onCheckedValueChange?: (e: ToolbarCheckedValueChangeEvent, data: ToolbarCheckedValueChangeData) => void;
};

/**
* State used in rendering Toolbar
*/
export type ToolbarState = ComponentState<ToolbarSlots> & Required<Pick<ToolbarProps, 'size'>>;
export type ToolbarState = ComponentState<ToolbarSlots> &
Required<Pick<ToolbarProps, 'size' | 'checkedValues'>> &
Pick<ToolbarProps, 'defaultCheckedValues' | 'onCheckedValueChange'> & {
/*
* Toggles the state of a ToggleButton item
*/
handleToggleButton: SelectableHandler;
};

export type ToolbarContextValue = {
size: ToolbarProps['size'];
export type ToolbarContextValue = Pick<ToolbarProps, 'size'> & {
handleToggleButton?: SelectableHandler;
};

export type ToolbarContextValues = {
toolbar: ToolbarContextValue;
};

export type UninitializedToolbarState = Omit<ToolbarState, 'checkedValues' | 'handleToggleButton'> &
Partial<Pick<ToolbarState, 'checkedValues'>>;
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const ToolbarContext = React.createContext<ToolbarContextValue | undefine

const toolbarContextDefaultValue = {
size: 'medium' as 'medium',
handleToggleButton: () => null,
};

export const useToolbarContext = () => React.useContext(ToolbarContext) ?? toolbarContextDefaultValue;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { useEventCallback, useControllableState } from '@fluentui/react-utilities';
import { getNativeElementProps } from '@fluentui/react-utilities';
import type { ToolbarProps, ToolbarState } from './Toolbar.types';
import type { ToolbarProps, ToolbarState, UninitializedToolbarState } from './Toolbar.types';
import { useArrowNavigationGroup } from '@fluentui/react-tabster';

/**
Expand All @@ -13,14 +14,14 @@ import { useArrowNavigationGroup } from '@fluentui/react-tabster';
* @param ref - reference to root HTMLElement of Toolbar
*/
export const useToolbar_unstable = (props: ToolbarProps, ref: React.Ref<HTMLElement>): ToolbarState => {
const { size = 'medium' } = props;

const arrowNavigationProps = useArrowNavigationGroup({
circular: true,
axis: 'horizontal',
});

const { size = 'medium' } = props;

return {
const initialState: UninitializedToolbarState = {
size,

// TODO add appropriate props/defaults
Expand All @@ -38,4 +39,33 @@ export const useToolbar_unstable = (props: ToolbarProps, ref: React.Ref<HTMLElem
}),
...props,
Comment thread
chpalac marked this conversation as resolved.
Outdated
};

const [checkedValues, setCheckedValues] = useControllableState({
state: initialState.checkedValues,
defaultState: initialState.defaultCheckedValues,
initialState: {},
});

const { onCheckedValueChange } = initialState;

const handleToggleButton = useEventCallback(
(e: React.MouseEvent | React.KeyboardEvent, name: string, value: string, checked: boolean) => {
const checkedItems = checkedValues?.[name] || [];
const newCheckedItems = [...checkedItems];
if (checked) {
newCheckedItems.splice(newCheckedItems.indexOf(value), 1);
} else {
newCheckedItems.push(value);
}

onCheckedValueChange?.(e, { name, checkedItems: newCheckedItems });
setCheckedValues(s => ({ ...s, [name]: newCheckedItems }));
},
);

return {
...initialState,
handleToggleButton,
checkedValues: checkedValues ?? {},
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from 'react';
import { Toolbar, ToolbarToggleButton } from '@fluentui/react-toolbar';
import type { MenuProps } from '@fluentui/react-components';
Comment thread
chpalac marked this conversation as resolved.
Outdated

export const ControlledToggleButton = () => {
const [checkedValues, setCheckedValues] = React.useState<Record<string, string[]>>({ edit: ['cut', 'paste'] });
const onChange: MenuProps['onCheckedValueChange'] = (e, { name, checkedItems }) => {
setCheckedValues(s => {
return s ? { ...s, [name]: checkedItems } : { [name]: checkedItems };
});
};

return (
<Toolbar checkedValues={checkedValues} onCheckedValueChange={onChange}>
<ToolbarToggleButton name="group">Enable Group</ToolbarToggleButton>
<ToolbarToggleButton name="group">Enable Group</ToolbarToggleButton>
</Toolbar>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { OverflowItems } from './ToolbarOverflow.stories';
export { WithTooltip } from './ToolbarWithTooltip.stories';
export { WithPopover } from './ToolbarWithPopover.stories';
export { Subtle } from './ToolbarSubtle.stories';
export { ControlledToggleButton } from './ToolbarControlledToggleButton.stories';

export default {
title: 'Preview Components/Toolbar',
Expand Down