diff --git a/packages/react-components/react-nav-preview/etc/react-nav-preview.api.md b/packages/react-components/react-nav-preview/etc/react-nav-preview.api.md index 0d555ec4272fb7..221c667ffaab3d 100644 --- a/packages/react-components/react-nav-preview/etc/react-nav-preview.api.md +++ b/packages/react-components/react-nav-preview/etc/react-nav-preview.api.md @@ -35,7 +35,8 @@ export type NavCategoryItemProps = ComponentProps> // @public (undocumented) export type NavCategoryItemSlots = { - root: Slot<'button'>; + root: NonNullable>; + icon?: Slot<'span'>; expandIcon: NonNullable>; }; @@ -110,7 +111,6 @@ export type NavItemRegisterData = { export type NavItemSlots = { root: NonNullable>; icon?: Slot<'span'>; - content: NonNullable>; }; // @public @@ -177,7 +177,6 @@ export type NavSubItemProps = ComponentProps> & { // @public (undocumented) export type NavSubItemSlots = { root: Slot<'a'>; - content: NonNullable>; }; // @public diff --git a/packages/react-components/react-nav-preview/src/components/NavCategoryItem/NavCategoryItem.types.ts b/packages/react-components/react-nav-preview/src/components/NavCategoryItem/NavCategoryItem.types.ts index 8c8e6fc77388cc..d9c5a260aaf9f3 100644 --- a/packages/react-components/react-nav-preview/src/components/NavCategoryItem/NavCategoryItem.types.ts +++ b/packages/react-components/react-nav-preview/src/components/NavCategoryItem/NavCategoryItem.types.ts @@ -1,5 +1,4 @@ import { NavCategoryItemContextValue } from '../NavCategoryItemContext'; - import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; export type NavCategoryItemContextValues = { @@ -10,20 +9,18 @@ export type NavCategoryItemSlots = { /** * The root element */ - root: Slot<'button'>; + root: NonNullable>; /** - * The component to be used as button in heading + * Icon that renders before the content. + * Should be specific to each Category */ - // button: NonNullable>>; + icon?: Slot<'span'>; + /** - * Expand icon slot rendered before (or after) children content in heading. + * Expand icon slot rendered after the content to indicate an open and closed state. */ expandIcon: NonNullable>; - // /** - // * Expand icon slot rendered before (or after) children content in heading. - // */ - // icon?: Slot<'div'>; }; /** diff --git a/packages/react-components/react-nav-preview/src/components/NavCategoryItem/renderNavCategoryItem.tsx b/packages/react-components/react-nav-preview/src/components/NavCategoryItem/renderNavCategoryItem.tsx index c489aa0586111e..e7179ed2c31654 100644 --- a/packages/react-components/react-nav-preview/src/components/NavCategoryItem/renderNavCategoryItem.tsx +++ b/packages/react-components/react-nav-preview/src/components/NavCategoryItem/renderNavCategoryItem.tsx @@ -18,13 +18,9 @@ export const renderNavCategoryItem_unstable = ( return ( - {/* TODO: These were copied from AccordionHeader. - We need to decide if they are still applicable - in our scenario and how to adapt them. */} - {/* */} - {/* Todo: {state.icon && } */} + {state.icon && } {state.root.children} - {state.expandIcon && } {/* */} + {state.expandIcon && } ); diff --git a/packages/react-components/react-nav-preview/src/components/NavCategoryItem/useNavCategoryItem.styles.ts b/packages/react-components/react-nav-preview/src/components/NavCategoryItem/useNavCategoryItem.styles.ts index 33bfb8a99be2b7..f0d75d4b5daec7 100644 --- a/packages/react-components/react-nav-preview/src/components/NavCategoryItem/useNavCategoryItem.styles.ts +++ b/packages/react-components/react-nav-preview/src/components/NavCategoryItem/useNavCategoryItem.styles.ts @@ -1,57 +1,77 @@ -import { makeResetStyles, mergeClasses, makeStyles } from '@griffel/react'; -import { typographyStyles } from '@fluentui/react-theme'; +import { makeStyles, mergeClasses } from '@griffel/react'; import { SlotClassNames } from '@fluentui/react-utilities'; +import { typographyStyles } from '@fluentui/react-theme'; +import { + useContentStyles, + useIconStyles, + useIndicatorStyles, + useRootDefaultClassName, +} from '../sharedNavStyles.styles'; import type { NavCategoryItemSlots, NavCategoryItemState } from './NavCategoryItem.types'; export const navCategoryItemClassNames: SlotClassNames = { root: 'fui-NavCategoryItem', + icon: 'fui-NavCategoryItem__icon', expandIcon: 'fui-NavCategoryItem__expandIcon', }; -/** - * Styles for the root slot - */ -const useRootStyles = makeResetStyles({ - display: 'flex', - ...typographyStyles.body1, -}); - -const useContentStyles = makeStyles({ - icon: { - display: 'flex', +const useExpandIconStyles = makeStyles({ + base: { + marginInlineStart: 'auto', }, open: { - transform: 'rotate(-90deg)', - }, - closed: { transform: 'rotate(90deg)', }, selected: typographyStyles.body1Strong, }); +/** + * Styles for the root slot + */ +export const useRootStyles = makeStyles({ + base: { + width: '100%', + }, +}); + /** * Apply styling to the NavCategoryItem slots based on the state */ export const useNavCategoryItemStyles_unstable = (state: NavCategoryItemState): NavCategoryItemState => { - const defaultRootStyles = useRootStyles(); + const rootStyles = useRootStyles(); + const defaultRootClassName = useRootDefaultClassName(); const contentStyles = useContentStyles(); + const indicatorStyles = useIndicatorStyles(); + const iconStyles = useIconStyles(); + const expandIconStyles = useExpandIconStyles(); - const { selected } = state; + const { selected, open } = state; state.root.className = mergeClasses( navCategoryItemClassNames.root, - defaultRootStyles, + defaultRootClassName, + rootStyles.base, + selected && open === false && indicatorStyles.base, + selected && open === false && contentStyles.selected, state.root.className, - selected && state.open === false && contentStyles.selected, ); state.expandIcon.className = mergeClasses( navCategoryItemClassNames.expandIcon, - contentStyles.icon, - state.open ? contentStyles.open : contentStyles.closed, + expandIconStyles.base, + state.open && expandIconStyles.open, state.expandIcon.className, ); + if (state.icon) { + state.icon.className = mergeClasses( + navCategoryItemClassNames.icon, + iconStyles.base, + selected && iconStyles.selected, + state.icon.className, + ); + } + return state; }; diff --git a/packages/react-components/react-nav-preview/src/components/NavCategoryItem/useNavCategoryItem.tsx b/packages/react-components/react-nav-preview/src/components/NavCategoryItem/useNavCategoryItem.tsx index d04461dc08475f..4b9bb67d2df553 100644 --- a/packages/react-components/react-nav-preview/src/components/NavCategoryItem/useNavCategoryItem.tsx +++ b/packages/react-components/react-nav-preview/src/components/NavCategoryItem/useNavCategoryItem.tsx @@ -18,7 +18,7 @@ export const useNavCategoryItem_unstable = ( props: NavCategoryItemProps, ref: React.Ref, ): NavCategoryItemState => { - const { onClick, expandIcon } = props; + const { onClick, expandIcon, icon } = props; const { open, value } = useNavCategoryContext_unstable(); @@ -30,41 +30,14 @@ export const useNavCategoryItem_unstable = ( const selected = selectedCategoryValue === value; - // TODO - these are copied from AccordionHeader. - // We need to figure out if they are applicable to this - // scenario and adapt them accordingly. - - // const buttonSlot = slot.always(button, { - // elementType: 'button', - // defaultProps: { - // // we may decide to light these up later - // // disabled, - // // disabledFocusable, - // 'aria-expanded': open, - // type: 'button', - // onClick: onNavCategoryItemClick, - // }, - // }); - - // buttonSlot.onClick = useEventCallback(event => { - // if (isResolvedShorthand(button)) { - // button.onClick?.(event); - // } - // if (!event.defaultPrevented) { - // onRequestNavCategoryItemToggle(event, { value, event }); //({ value, event }); - // } - // }); - return { open, value, selected, - // TODO add appropriate props/defaults components: { root: 'button', - // button: 'div', + icon: 'span', expandIcon: 'span', - // icon: 'div', }, root: slot.always( getIntrinsicElementProps('button', { @@ -83,15 +56,8 @@ export const useNavCategoryItem_unstable = ( }, elementType: 'span', }), - // button: useARIAButtonProps(buttonSlot.as, buttonSlot), - // button: slot.always( - // getIntrinsicElementProps('button', { - // ref, - // role: 'button', - // type: 'button', - // onClick: onNavCategoryItemClick, - // }), - // { elementType: 'button' }, - // ), + icon: slot.optional(icon, { + elementType: 'span', + }), }; }; diff --git a/packages/react-components/react-nav-preview/src/components/NavItem/NavItem.test.tsx b/packages/react-components/react-nav-preview/src/components/NavItem/NavItem.test.tsx index d1a1f221cccb6a..68d74890a5367d 100644 --- a/packages/react-components/react-nav-preview/src/components/NavItem/NavItem.test.tsx +++ b/packages/react-components/react-nav-preview/src/components/NavItem/NavItem.test.tsx @@ -12,7 +12,6 @@ describe('NavItem', () => { props: { icon: 'Test Icon', content: 'Some Content' }, expectedClassNames: { root: navItemClassNames.root, - content: navItemClassNames.content, icon: navItemClassNames.icon, }, }, diff --git a/packages/react-components/react-nav-preview/src/components/NavItem/NavItem.types.ts b/packages/react-components/react-nav-preview/src/components/NavItem/NavItem.types.ts index 46bcb803132411..f49fc3c6faa84f 100644 --- a/packages/react-components/react-nav-preview/src/components/NavItem/NavItem.types.ts +++ b/packages/react-components/react-nav-preview/src/components/NavItem/NavItem.types.ts @@ -8,12 +8,6 @@ export type NavItemSlots = { * Icon that renders before the content. */ icon?: Slot<'span'>; - - /** - * Component children are placed in this slot - * Avoid using the `children` property in this slot in favour of Component children whenever possible. - */ - content: NonNullable>; }; /** diff --git a/packages/react-components/react-nav-preview/src/components/NavItem/renderNavItem.tsx b/packages/react-components/react-nav-preview/src/components/NavItem/renderNavItem.tsx index bb39696eff39c8..1ec917adf10d1e 100644 --- a/packages/react-components/react-nav-preview/src/components/NavItem/renderNavItem.tsx +++ b/packages/react-components/react-nav-preview/src/components/NavItem/renderNavItem.tsx @@ -14,7 +14,7 @@ export const renderNavItem_unstable = (state: NavItemState) => { return ( {state.icon && } - + {state.root.children} ); }; diff --git a/packages/react-components/react-nav-preview/src/components/NavItem/useNavItem.ts b/packages/react-components/react-nav-preview/src/components/NavItem/useNavItem.ts index 34ac01d9c5593c..833c37cb018b7e 100644 --- a/packages/react-components/react-nav-preview/src/components/NavItem/useNavItem.ts +++ b/packages/react-components/react-nav-preview/src/components/NavItem/useNavItem.ts @@ -14,7 +14,7 @@ import type { NavItemProps, NavItemState } from './NavItem.types'; * @param ref - reference to root HTMLAnchorElement of NavItem */ export const useNavItem_unstable = (props: NavItemProps, ref: React.Ref): NavItemState => { - const { content, onClick, value, icon } = props; + const { onClick, value, icon } = props; const { selectedValue, onRegister, onUnregister, onSelect } = useNavContext_unstable(); @@ -36,13 +36,8 @@ export const useNavItem_unstable = (props: NavItemProps, ref: React.Ref = { root: 'fui-NavItem', - content: 'fui-NavItem__content', icon: 'fui-NavItem__icon', }; -const navItemTokens = { - indicatorOffset: 18, - indicatorWidth: 4, - indicatorHeight: 20, -}; - -/** - * Styles for the root slot - */ -const useRootDefaultClassName = makeResetStyles({ - display: 'flex', - textTransform: 'none', - position: 'relative', - justifyContent: 'start', - gap: tokens.spacingVerticalL, - padding: tokens.spacingVerticalMNudge, - backgroundColor: tokens.colorNeutralBackground4, - borderRadius: tokens.borderRadiusMedium, - color: tokens.colorNeutralForeground2, - textDecorationLine: 'none', - ...typographyStyles.body1, - ':hover': { - backgroundColor: tokens.colorNeutralBackground4Hover, - }, - ':active': { - backgroundColor: tokens.colorNeutralBackground4Pressed, - }, -}); - -/** - * Styles for the content slot (children) - */ -const useContentStyles = makeStyles({ - selected: typographyStyles.body1Strong, -}); - -const useIndicatorStyles = makeStyles({ - base: { - '::after': { - position: 'absolute', - marginInlineStart: `-${navItemTokens.indicatorOffset}px`, - backgroundColor: tokens.colorNeutralForeground2BrandSelected, - height: `${navItemTokens.indicatorHeight}px`, - width: `${navItemTokens.indicatorWidth}px`, - ...shorthands.borderRadius(tokens.borderRadiusCircular), - content: '""', - }, - }, -}); - -const useIconStyles = makeStyles({ - base: { - minHeight: '20px', - minWidth: '20px', - alignItems: 'top', - display: 'inline-flex', - justifyContent: 'center', - ...shorthands.overflow('hidden'), - [`& .${iconFilledClassName}`]: { - display: 'none', - }, - [`& .${iconRegularClassName}`]: { - display: 'inline', - }, - }, - selected: { - [`& .${iconFilledClassName}`]: { - display: 'inline', - color: tokens.colorNeutralForeground2BrandSelected, - }, - [`& .${iconRegularClassName}`]: { - display: 'none', - }, - }, -}); - /** * Apply styling to the NavItem slots based on the state */ @@ -101,13 +28,8 @@ export const useNavItemStyles_unstable = (state: NavItemState): NavItemState => navItemClassNames.root, rootDefaultClassName, selected && indicatorStyles.base, - state.root.className, - ); - - state.content.className = mergeClasses( - navItemClassNames.content, selected && contentStyles.selected, - state.content.className, + state.root.className, ); if (state.icon) { diff --git a/packages/react-components/react-nav-preview/src/components/NavSubItem/NavSubItem.types.ts b/packages/react-components/react-nav-preview/src/components/NavSubItem/NavSubItem.types.ts index 3e318e85764941..30a5a3ee2b5dd2 100644 --- a/packages/react-components/react-nav-preview/src/components/NavSubItem/NavSubItem.types.ts +++ b/packages/react-components/react-nav-preview/src/components/NavSubItem/NavSubItem.types.ts @@ -4,12 +4,6 @@ import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utili export type NavSubItemSlots = { root: Slot<'a'>; - - /** - * Component children are placed in this slot - * Avoid using the `children` property in this slot in favour of Component children whenever possible. - */ - content: NonNullable>; }; /** diff --git a/packages/react-components/react-nav-preview/src/components/NavSubItem/renderNavSubItem.tsx b/packages/react-components/react-nav-preview/src/components/NavSubItem/renderNavSubItem.tsx index d335fcf4762ca4..82e45fb66df371 100644 --- a/packages/react-components/react-nav-preview/src/components/NavSubItem/renderNavSubItem.tsx +++ b/packages/react-components/react-nav-preview/src/components/NavSubItem/renderNavSubItem.tsx @@ -11,10 +11,5 @@ import type { NavSubItemState, NavSubItemSlots } from './NavSubItem.types'; export const renderNavSubItem_unstable = (state: NavSubItemState) => { assertSlots(state); - // TODO Add additional slots in the appropriate place - return ( - - - - ); + return ; }; diff --git a/packages/react-components/react-nav-preview/src/components/NavSubItem/useNavSubItem.ts b/packages/react-components/react-nav-preview/src/components/NavSubItem/useNavSubItem.ts index a9e39f67ef92ed..305e5a674147fb 100644 --- a/packages/react-components/react-nav-preview/src/components/NavSubItem/useNavSubItem.ts +++ b/packages/react-components/react-nav-preview/src/components/NavSubItem/useNavSubItem.ts @@ -15,7 +15,7 @@ import type { NavSubItemProps, NavSubItemState } from './NavSubItem.types'; * @param ref - reference to root HTMLButtonElement of NavSubItem */ export const useNavSubItem_unstable = (props: NavSubItemProps, ref: React.Ref): NavSubItemState => { - const { content, onClick, value: subItemValue } = props; + const { onClick, value: subItemValue } = props; const { selectedValue, onRegister, onUnregister, onSelect } = useNavContext_unstable(); @@ -42,15 +42,9 @@ export const useNavSubItem_unstable = (props: NavSubItemProps, ref: React.Ref = { root: 'fui-NavSubItem', - content: 'fui-NavSubItem__content', }; - -/** - * Styles for the root slot - */ -const useStyles = makeResetStyles({ - display: 'flex', - ...typographyStyles.body1, -}); - /** * Styles for the content slot (children) */ -const useContentStyles = makeStyles({ - selected: typographyStyles.body1Strong, +const useNavSubItemSpecificStyles = makeStyles({ + base: { + paddingInlineStart: '36px', + }, + selectedIndicator: { + '::after': { + marginInlineStart: `-${navItemTokens.indicatorOffset + 24}px`, + }, + }, }); /** * Apply styling to the NavSubItem slots based on the state */ export const useNavSubItemStyles_unstable = (state: NavSubItemState): NavSubItemState => { - const rootStyles = useStyles(); + const rootDefaultClassName = useRootDefaultClassName(); const contentStyles = useContentStyles(); + const indicatorStyles = useIndicatorStyles(); + const navSubItemSpecificStyles = useNavSubItemSpecificStyles(); const { selected } = state; state.root.className = mergeClasses( navSubItemClassNames.root, - rootStyles, - - state.root.className, - ); - - state.content.className = mergeClasses( - navSubItemClassNames.content, + rootDefaultClassName, + navSubItemSpecificStyles.base, + selected && indicatorStyles.base, selected && contentStyles.selected, - state.content.className, + selected && navSubItemSpecificStyles.selectedIndicator, + state.root.className, ); return state; diff --git a/packages/react-components/react-nav-preview/src/components/sharedNavStyles.styles.ts b/packages/react-components/react-nav-preview/src/components/sharedNavStyles.styles.ts new file mode 100644 index 00000000000000..f73d707fa41231 --- /dev/null +++ b/packages/react-components/react-nav-preview/src/components/sharedNavStyles.styles.ts @@ -0,0 +1,92 @@ +import { iconFilledClassName, iconRegularClassName } from '@fluentui/react-icons'; +import { tokens, typographyStyles } from '@fluentui/react-theme'; +import { makeResetStyles, makeStyles, shorthands } from '@griffel/react'; + +// Styles shared by several nav components. + +export const navItemTokens = { + indicatorOffset: 18, + indicatorWidth: 4, + indicatorHeight: 20, +}; + +/** + * Styles for the root slot + * Shared across NavItem, NavCategoryItem, and NavSubItem + */ +export const useRootDefaultClassName = makeResetStyles({ + display: 'flex', + textTransform: 'none', + position: 'relative', + justifyContent: 'start', + gap: tokens.spacingVerticalL, + padding: tokens.spacingVerticalMNudge, + backgroundColor: tokens.colorNeutralBackground4, + borderRadius: tokens.borderRadiusMedium, + color: tokens.colorNeutralForeground2, + textDecorationLine: 'none', + border: 'none', + ...typographyStyles.body1, + ':hover': { + backgroundColor: tokens.colorNeutralBackground4Hover, + }, + ':active': { + backgroundColor: tokens.colorNeutralBackground4Pressed, + }, +}); + +/** + * Styles for the content slot (children) + * Shared across NavItem, NavCategoryItem, and NavSubItem + */ +export const useContentStyles = makeStyles({ + selected: typographyStyles.body1Strong, +}); + +/** + * French fry styles + * Shared across NavItem, NavCategoryItem, and NavSubItem + */ +export const useIndicatorStyles = makeStyles({ + base: { + '::after': { + position: 'absolute', + marginInlineStart: `-${navItemTokens.indicatorOffset}px`, + backgroundColor: tokens.colorNeutralForeground2BrandSelected, + height: `${navItemTokens.indicatorHeight}px`, + width: `${navItemTokens.indicatorWidth}px`, + ...shorthands.borderRadius(tokens.borderRadiusCircular), + content: '""', + }, + }, +}); + +/** + * Styles for the icon slot + * Shared across NavItem, NavCategoryItem, and NavSubItem + */ +export const useIconStyles = makeStyles({ + base: { + minHeight: '20px', + minWidth: '20px', + alignItems: 'top', + display: 'inline-flex', + justifyContent: 'center', + ...shorthands.overflow('hidden'), + [`& .${iconFilledClassName}`]: { + display: 'none', + }, + [`& .${iconRegularClassName}`]: { + display: 'inline', + }, + }, + selected: { + [`& .${iconFilledClassName}`]: { + display: 'inline', + color: tokens.colorNeutralForeground2BrandSelected, + }, + [`& .${iconRegularClassName}`]: { + display: 'none', + }, + }, +}); diff --git a/packages/react-components/react-nav-preview/stories/NavDrawer/NavDrawerDefault.stories.tsx b/packages/react-components/react-nav-preview/stories/NavDrawer/NavDrawerDefault.stories.tsx index 81381a1a0b2ed7..099d37754fe96e 100644 --- a/packages/react-components/react-nav-preview/stories/NavDrawer/NavDrawerDefault.stories.tsx +++ b/packages/react-components/react-nav-preview/stories/NavDrawer/NavDrawerDefault.stories.tsx @@ -10,6 +10,7 @@ import { } from '@fluentui/react-nav-preview'; import { DrawerBody } from '@fluentui/react-drawer'; import { makeStyles, shorthands, tokens } from '@fluentui/react-components'; +import { Folder20Filled, Folder20Regular, bundleIcon } from '@fluentui/react-icons'; const useStyles = makeStyles({ root: { ...shorthands.overflow('hidden'), @@ -21,46 +22,52 @@ const useStyles = makeStyles({ }, }); +const Folder = bundleIcon(Folder20Filled, Folder20Regular); + export const Default = (props: Partial) => { const styles = useStyles(); + const someClickHandler = () => { + console.log('someClickHandler'); + }; + return (
- + } target="_blank" onClick={someClickHandler} value="1"> First - + } target="_blank" onClick={someClickHandler} value="2"> Second - + } target="_blank" onClick={someClickHandler} value="3"> Third - NavCategoryItem 1 + }>NavCategoryItem 1 - + Five - + Six - + Seven - NavCategoryItem2 + }>NavCategoryItem2 - + Nine - + Ten - + Eleven