Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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": "minor",
"comment": "feat: add base hooks for Toolbar components",
"packageName": "@fluentui/react-toolbar",
"email": "dmytrokirpa@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ import { ButtonSlots } from '@fluentui/react-button';
import { ButtonState } from '@fluentui/react-button';
import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import { DividerSlots } from '@fluentui/react-divider';
import { DividerState } from '@fluentui/react-divider';
import { ContextSelector } from '@fluentui/react-context-selector';
import type { DividerSlots } from '@fluentui/react-divider';
import type { DividerState } from '@fluentui/react-divider';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import type { JSXElement } from '@fluentui/react-utilities';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
import { SlotClassNames } from '@fluentui/react-utilities';
import { ToggleButtonProps } from '@fluentui/react-button';
import { ToggleButtonState } from '@fluentui/react-button';
import type { ToggleButtonProps } from '@fluentui/react-button';
import type { ToggleButtonState } from '@fluentui/react-button';

// @public
export const renderToolbar_unstable: (state: ToolbarState, contextValues: ToolbarContextValues) => JSXElement;
Expand Down Expand Up @@ -144,6 +145,12 @@ export const useToolbarButton_unstable: (props: ToolbarButtonProps, ref: React_2
// @public
export const useToolbarButtonStyles_unstable: (state: ToolbarButtonState) => void;

// @public (undocumented)
export const useToolbarContext_unstable: <T>(selector: ContextSelector<ToolbarContextValue, T>) => T;

// @public (undocumented)
export function useToolbarContextValues_unstable(state: ToolbarState): ToolbarContextValues;

// @public
export const useToolbarDivider_unstable: (props: ToolbarDividerProps, ref: React_2.Ref<HTMLElement>) => ToolbarDividerState;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ export type {
ToolbarCheckedValueChangeEvent,
ToolbarContextValue,
ToolbarContextValues,
ToolbarBaseProps,
ToolbarProps,
ToolbarSlots,
ToolbarBaseState,
ToolbarState,
UninitializedToolbarState,
} from './components/Toolbar/index';
Expand All @@ -15,4 +17,7 @@ export {
toolbarClassNames,
useToolbarStyles_unstable,
useToolbar_unstable,
useToolbarBase_unstable,
useToolbarContext_unstable,
useToolbarContextValues_unstable,
} from './components/Toolbar/index';
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
export type { ToolbarButtonProps, ToolbarButtonState } from './components/ToolbarButton/index';
export type {
ToolbarButtonBaseState,
ToolbarButtonProps,
ToolbarButtonBaseProps,
ToolbarButtonState,
} from './components/ToolbarButton/index';
export {
ToolbarButton,
useToolbarButtonStyles_unstable,
useToolbarButton_unstable,
useToolbarButtonBase_unstable,
} from './components/ToolbarButton/index';
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
export type { ToolbarDividerProps, ToolbarDividerState } from './components/ToolbarDivider/index';
export type {
ToolbarDividerBaseState,
ToolbarDividerProps,
ToolbarDividerBaseProps,
ToolbarDividerState,
} from './components/ToolbarDivider/index';
export {
ToolbarDivider,
useToolbarDividerStyles_unstable,
useToolbarDivider_unstable,
useToolbarDividerBase_unstable,
} from './components/ToolbarDivider/index';
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
export type { ToolbarRadioButtonProps, ToolbarRadioButtonState } from './components/ToolbarRadioButton/index';
export type {
ToolbarRadioButtonBaseProps,
ToolbarRadioButtonProps,
ToolbarRadioButtonBaseState,
ToolbarRadioButtonState,
} from './components/ToolbarRadioButton/index';
export {
ToolbarRadioButton,
useToolbarRadioButtonStyles_unstable,
useToolbarRadioButton_unstable,
useToolbarRadioButtonBase_unstable,
} from './components/ToolbarRadioButton/index';
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
export type { ToolbarToggleButtonProps, ToolbarToggleButtonState } from './components/ToolbarToggleButton/index';
export type {
ToolbarToggleButtonBaseProps,
ToolbarToggleButtonProps,
ToolbarToggleButtonBaseState,
ToolbarToggleButtonState,
} from './components/ToolbarToggleButton/index';
export {
ToolbarToggleButton,
useToolbarToggleButtonStyles_unstable,
useToolbarToggleButton_unstable,
useToolbarToggleButtonBase_unstable,
} from './components/ToolbarToggleButton/index';
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export type ToolbarProps = ComponentProps<ToolbarSlots> & {
onCheckedValueChange?: (e: ToolbarCheckedValueChangeEvent, data: ToolbarCheckedValueChangeData) => void;
};

export type ToolbarBaseProps = Omit<ToolbarProps, 'size'>;

/**
* State used in rendering Toolbar
*/
Expand All @@ -67,6 +69,8 @@ export type ToolbarState = ComponentState<ToolbarSlots> &
handleRadio: ToggableHandler;
};

export type ToolbarBaseState = Omit<ToolbarState, 'size'>;

export type ToolbarContextValue = Pick<ToolbarState, 'size' | 'vertical' | 'checkedValues'> & {
handleToggleButton?: ToggableHandler;
handleRadio?: ToggableHandler;
Expand All @@ -76,7 +80,7 @@ export type ToolbarContextValues = {
toolbar: ToolbarContextValue;
};

export type UninitializedToolbarState = Omit<ToolbarState, 'checkedValues' | 'handleToggleButton' | 'handleRadio'> &
export type UninitializedToolbarState = Omit<ToolbarBaseState, 'checkedValues' | 'handleToggleButton' | 'handleRadio'> &
Partial<Pick<ToolbarState, 'checkedValues'>>;

export type ToggableHandler = (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
export { useToolbarContext_unstable } from './ToolbarContext';
export { Toolbar } from './Toolbar';
export type {
ToggableHandler,
ToolbarCheckedValueChangeData,
ToolbarCheckedValueChangeEvent,
ToolbarContextValue,
ToolbarContextValues,
ToolbarBaseProps,
ToolbarProps,
ToolbarSlots,
ToolbarBaseState,
ToolbarState,
UninitializedToolbarState,
} from './Toolbar.types';
export { renderToolbar_unstable } from './renderToolbar';
export { useToolbar_unstable } from './useToolbar';
export { useToolbar_unstable, useToolbarBase_unstable } from './useToolbar';
export { useToolbarContextValues_unstable } from './useToolbarContextValues';
export { toolbarClassNames, useToolbarStyles_unstable } from './useToolbarStyles.styles';
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@

import * as React from 'react';
import { useEventCallback, useControllableState, getIntrinsicElementProps, slot } from '@fluentui/react-utilities';
import type { ToggableHandler, ToolbarProps, ToolbarState, UninitializedToolbarState } from './Toolbar.types';
import { useArrowNavigationGroup } from '@fluentui/react-tabster';
import type {
ToggableHandler,
ToolbarBaseProps,
ToolbarBaseState,
ToolbarProps,
ToolbarState,
UninitializedToolbarState,
} from './Toolbar.types';
import { TabsterDOMAttribute, useArrowNavigationGroup } from '@fluentui/react-tabster';

/**
* Create the state required to render Toolbar.
Expand All @@ -15,15 +22,32 @@ 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', vertical = false } = props;
const { size = 'medium' } = props;
const state = useToolbarBase_unstable(props, ref);
const arrowNavigationProps = useToolbarArrowNavigationProps_unstable();

const arrowNavigationProps = useArrowNavigationGroup({
circular: true,
axis: 'both',
});
return {
size,
...state,
root: {
...state.root,
...arrowNavigationProps,
},
};
};

/**
* Base hook that builds Toolbar state for behavior and structure only.
* It does not add arrow key navigation, which is handled by `useToolbar_unstable`.
*
* @internal
* @param props - Props for this Toolbar instance.
* @param ref - Ref to the root HTMLElement.
*/
export const useToolbarBase_unstable = (props: ToolbarBaseProps, ref: React.Ref<HTMLElement>): ToolbarBaseState => {
const { vertical = false } = props;

const initialState: UninitializedToolbarState = {
size,
vertical,
// TODO add appropriate props/defaults
components: {
Expand All @@ -35,12 +59,8 @@ export const useToolbar_unstable = (props: ToolbarProps, ref: React.Ref<HTMLElem
root: slot.always(
getIntrinsicElementProps('div', {
role: 'toolbar',
// FIXME:
// `ref` is wrongly assigned to be `HTMLElement` instead of `HTMLDivElement`
// but since it would be a breaking change to fix it, we are casting ref to it's proper type
ref: ref as React.Ref<HTMLDivElement>,
...(vertical && ({ 'aria-orientation': 'vertical' } as const)),
...arrowNavigationProps,
...props,
}),
{ elementType: 'div' },
Expand Down Expand Up @@ -92,23 +112,38 @@ export const useToolbar_unstable = (props: ToolbarProps, ref: React.Ref<HTMLElem
* i.e checkboxes and radios
*/
const useToolbarSelectableState = (
state: Pick<ToolbarProps, 'checkedValues' | 'defaultCheckedValues' | 'onCheckedValueChange'>,
state: Pick<ToolbarBaseProps, 'checkedValues' | 'defaultCheckedValues' | 'onCheckedValueChange'>,
) => {
const [checkedValues, setCheckedValues] = useControllableState({
state: state.checkedValues,
defaultState: state.defaultCheckedValues,
initialState: {},
});
const { onCheckedValueChange: onCheckedValueChangeOriginal } = state;
const onCheckedValueChange: ToolbarState['onCheckedValueChange'] = useEventCallback((e, { name, checkedItems }) => {
if (onCheckedValueChangeOriginal) {
onCheckedValueChangeOriginal(e, { name, checkedItems });
}
const onCheckedValueChange: ToolbarBaseState['onCheckedValueChange'] = useEventCallback(
(e, { name, checkedItems }) => {
if (onCheckedValueChangeOriginal) {
onCheckedValueChangeOriginal(e, { name, checkedItems });
}

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

return [checkedValues, onCheckedValueChange] as const;
};

/**
* Hook to add arrow navigation props to the Toolbar.
*
* @internal
* @returns - Tabster DOM attributes for arrow navigation
*/
export const useToolbarArrowNavigationProps_unstable = (): TabsterDOMAttribute => {
return useArrowNavigationGroup({
circular: true,
axis: 'both',
});
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ComponentProps, ComponentState } from '@fluentui/react-utilities';
import type { ComponentProps, ComponentState, DistributiveOmit } from '@fluentui/react-utilities';
import { ButtonProps, ButtonSlots, ButtonState } from '@fluentui/react-button';

/**
Expand All @@ -11,9 +11,13 @@ export type ToolbarButtonProps = ComponentProps<ButtonSlots> &
vertical?: boolean;
};

export type ToolbarButtonBaseProps = DistributiveOmit<ToolbarButtonProps, 'appearance'>;

/**
* State used in rendering ToolbarButton
*/
export type ToolbarButtonState = ComponentState<Partial<ButtonSlots>> &
ButtonState &
Required<Pick<ToolbarButtonProps, 'vertical'>>;

export type ToolbarButtonBaseState = DistributiveOmit<ToolbarButtonState, 'appearance' | 'size' | 'shape'>;
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export { ToolbarButton } from './ToolbarButton';
export type { ToolbarButtonProps, ToolbarButtonState } from './ToolbarButton.types';
export { useToolbarButton_unstable } from './useToolbarButton';
export type {
ToolbarButtonBaseProps,
ToolbarButtonProps,
ToolbarButtonBaseState,
ToolbarButtonState,
} from './ToolbarButton.types';
export { useToolbarButton_unstable, useToolbarButtonBase_unstable } from './useToolbarButton';
export { useToolbarButtonStyles_unstable } from './useToolbarButtonStyles.styles';
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

import * as React from 'react';
import { useButton_unstable } from '@fluentui/react-button';
import { ToolbarButtonProps, ToolbarButtonState } from './ToolbarButton.types';
import type {
ToolbarButtonBaseProps,
ToolbarButtonBaseState,
ToolbarButtonProps,
ToolbarButtonState,
} from './ToolbarButton.types';

/**
* Given user props, defines default props for the Button, calls useButtonState and useChecked, and returns
Expand All @@ -14,6 +19,28 @@ export const useToolbarButton_unstable = (
props: ToolbarButtonProps,
ref: React.Ref<HTMLButtonElement | HTMLAnchorElement>,
): ToolbarButtonState => {
const state = useToolbarButtonBase_unstable(props, ref);

return {
appearance: 'subtle',
size: 'medium',
shape: 'rounded',
...state,
};
};

/**
* Base hook that builds Toolbar Button state for behavior and structure only.
* It does not provide any design-related defaults.
*
* @internal
* @param props - User provided props to the Button component.
* @param ref - User provided ref to be passed to the Button component.
*/
export const useToolbarButtonBase_unstable = (
props: ToolbarButtonBaseProps,
ref: React.Ref<HTMLButtonElement | HTMLAnchorElement>,
): ToolbarButtonBaseState => {
const { vertical = false, ...buttonProps } = props;
const state = useButton_unstable(
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ComponentProps, ComponentState } from '@fluentui/react-utilities';
import { DividerSlots, DividerState } from '@fluentui/react-divider';
import type { DividerSlots, DividerState } from '@fluentui/react-divider';

/**
* ToolbarDivider Props
Expand All @@ -13,7 +13,11 @@ export type ToolbarDividerProps = ComponentProps<Partial<DividerSlots>> & {
vertical?: boolean;
};

export type ToolbarDividerBaseProps = ToolbarDividerProps;

/**
* State used in rendering ToolbarDivider
*/
export type ToolbarDividerState = ComponentState<Partial<DividerSlots>> & DividerState;

export type ToolbarDividerBaseState = Omit<ToolbarDividerState, 'appearance'>;
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export { ToolbarDivider } from './ToolbarDivider';
export type { ToolbarDividerProps, ToolbarDividerState } from './ToolbarDivider.types';
export type {
ToolbarDividerBaseProps,
ToolbarDividerProps,
ToolbarDividerBaseState,
ToolbarDividerState,
} from './ToolbarDivider.types';
export { useToolbarDividerStyles_unstable } from './useToolbarDividerStyles.styles';
export { useToolbarDivider_unstable } from './useToolbarDivider';
export { useToolbarDivider_unstable, useToolbarDividerBase_unstable } from './useToolbarDivider';
Loading