From cc8588157713850ca41b5510d1336998bacda5c0 Mon Sep 17 00:00:00 2001 From: Mason Tejera <17346018+mltejera@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:28:34 -0700 Subject: [PATCH] Split Nav Item Implementation (#32873) --- ...-a6ef1dd9-e087-455c-8f57-279b716f32ae.json | 7 + .../library/etc/react-nav-preview.api.md | 12 +- .../src/components/NavItem/NavItem.types.ts | 3 + .../SplitNavItem/SplitNavItem.test.tsx | 30 +- .../SplitNavItem/SplitNavItem.types.ts | 39 ++- .../__snapshots__/SplitNavItem.test.tsx.snap | 11 - .../SplitNavItem/renderSplitNavItem.tsx | 10 +- .../SplitNavItem/useSplitNavItem.ts | 34 --- .../SplitNavItem/useSplitNavItem.tsx | 85 ++++++ .../useSplitNavItemStyles.styles.ts | 115 +++++++- .../src/components/sharedNavStyles.styles.ts | 2 + .../stories/src/Nav/index.stories.tsx | 7 +- ...rDefault.stories.tsx => Basic.stories.tsx} | 2 +- ...led.stories.tsx => Controlled.stories.tsx} | 2 +- .../src/NavDrawer/SplitNavItems.stories.tsx | 257 ++++++++++++++++++ ...ies.tsx => VariableSizedItems.stories.tsx} | 2 +- 16 files changed, 540 insertions(+), 78 deletions(-) create mode 100644 change/@fluentui-react-nav-preview-a6ef1dd9-e087-455c-8f57-279b716f32ae.json delete mode 100644 packages/react-components/react-nav-preview/library/src/components/SplitNavItem/__snapshots__/SplitNavItem.test.tsx.snap delete mode 100644 packages/react-components/react-nav-preview/library/src/components/SplitNavItem/useSplitNavItem.ts create mode 100644 packages/react-components/react-nav-preview/library/src/components/SplitNavItem/useSplitNavItem.tsx rename packages/react-components/react-nav-preview/stories/src/NavDrawer/{NavDrawerDefault.stories.tsx => Basic.stories.tsx} (99%) rename packages/react-components/react-nav-preview/stories/src/NavDrawer/{NavDrawerControlled.stories.tsx => Controlled.stories.tsx} (99%) create mode 100644 packages/react-components/react-nav-preview/stories/src/NavDrawer/SplitNavItems.stories.tsx rename packages/react-components/react-nav-preview/stories/src/NavDrawer/{NavDrawerSize.stories.tsx => VariableSizedItems.stories.tsx} (99%) diff --git a/change/@fluentui-react-nav-preview-a6ef1dd9-e087-455c-8f57-279b716f32ae.json b/change/@fluentui-react-nav-preview-a6ef1dd9-e087-455c-8f57-279b716f32ae.json new file mode 100644 index 0000000000000..b8cf63925b0e1 --- /dev/null +++ b/change/@fluentui-react-nav-preview-a6ef1dd9-e087-455c-8f57-279b716f32ae.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: Build out SplitNavItem", + "packageName": "@fluentui/react-nav-preview", + "email": "matejera@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-nav-preview/library/etc/react-nav-preview.api.md b/packages/react-components/react-nav-preview/library/etc/react-nav-preview.api.md index 18e875c7569cb..bd6b53aa90287 100644 --- a/packages/react-components/react-nav-preview/library/etc/react-nav-preview.api.md +++ b/packages/react-components/react-nav-preview/library/etc/react-nav-preview.api.md @@ -31,9 +31,11 @@ import type { EventData } from '@fluentui/react-utilities'; import { EventHandler } from '@fluentui/react-utilities'; import type { ForwardRefComponent } from '@fluentui/react-utilities'; import type { InlineDrawerSlots } from '@fluentui/react-drawer'; +import { MenuButtonProps } from '@fluentui/react-button'; 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'; // @public export const AppItem: ForwardRefComponent; @@ -391,15 +393,21 @@ export const SplitNavItem: ForwardRefComponent; export const splitNavItemClassNames: SlotClassNames; // @public -export type SplitNavItemProps = ComponentProps & {}; +export type SplitNavItemProps = ComponentProps; // @public (undocumented) export type SplitNavItemSlots = { root: Slot<'div'>; + navItem?: Slot; + actionButton?: Slot; + toggleButton?: Slot; + menuButton?: Slot; }; // @public -export type SplitNavItemState = ComponentState; +export type SplitNavItemState = ComponentState & { + size: NavSize; +}; // @public export const useAppItem_unstable: (props: AppItemProps, ref: React_2.Ref) => AppItemState; diff --git a/packages/react-components/react-nav-preview/library/src/components/NavItem/NavItem.types.ts b/packages/react-components/react-nav-preview/library/src/components/NavItem/NavItem.types.ts index 5d2de4834adf8..c4248ee931023 100644 --- a/packages/react-components/react-nav-preview/library/src/components/NavItem/NavItem.types.ts +++ b/packages/react-components/react-nav-preview/library/src/components/NavItem/NavItem.types.ts @@ -19,6 +19,9 @@ export type NavItemSlots = { * NavItem Props */ export type NavItemProps = ComponentProps & { + /** + * Destination where the nav item points to. + */ href?: string; /** * The value that identifies this navCategoryItem when selected. diff --git a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/SplitNavItem.test.tsx b/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/SplitNavItem.test.tsx index 99e7e7ea47dba..1ee3e352958ba 100644 --- a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/SplitNavItem.test.tsx +++ b/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/SplitNavItem.test.tsx @@ -1,18 +1,30 @@ -import * as React from 'react'; -import { render } from '@testing-library/react'; import { isConformant } from '../../testing/isConformant'; import { SplitNavItem } from './SplitNavItem'; +import { splitNavItemClassNames } from './useSplitNavItemStyles.styles'; describe('SplitNavItem', () => { isConformant({ Component: SplitNavItem, displayName: 'SplitNavItem', - }); - - // TODO add more tests here, and create visual regression tests in /apps/vr-tests - - it('renders a default state', () => { - const result = render(Default SplitNavItem); - expect(result.container).toMatchSnapshot(); + testOptions: { + 'has-static-classnames': [ + { + props: { + icon: 'Test Icon', + navItem: 'Some Content', + actionButton: 'Some Content', + toggleButton: 'Some Content', + menuButton: 'Some Content', + }, + expectedClassNames: { + root: splitNavItemClassNames.root, + navItem: splitNavItemClassNames.navItem, + actionButton: splitNavItemClassNames.actionButton, + toggleButton: splitNavItemClassNames.toggleButton, + menuButton: splitNavItemClassNames.menuButton, + }, + }, + ], + }, }); }); diff --git a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/SplitNavItem.types.ts b/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/SplitNavItem.types.ts index 8251c28d98ec0..463c64e77992d 100644 --- a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/SplitNavItem.types.ts +++ b/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/SplitNavItem.types.ts @@ -1,17 +1,48 @@ import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import { NavItemProps } from '../NavItem/NavItem.types'; +import { ButtonProps, MenuButtonProps, ToggleButtonProps } from '@fluentui/react-button'; +import { NavSize } from '../Nav/Nav.types'; export type SplitNavItemSlots = { + /** + * Root of the component, wrapping the children. + */ root: Slot<'div'>; + + /** + * Primary navigation item in SplitNavItem. + */ + navItem?: Slot; + + /** + * Basic button slot. + */ + actionButton?: Slot; + + /** + * Toggle button slot + */ + toggleButton?: Slot; + + /** + * Menu button slot to stuff more things in when the other two aren't enough. + */ + menuButton?: Slot; }; /** * SplitNavItem Props */ -export type SplitNavItemProps = ComponentProps & {}; +export type SplitNavItemProps = ComponentProps; /** * State used in rendering SplitNavItem */ -export type SplitNavItemState = ComponentState; -// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from SplitNavItemProps. -// & Required> +export type SplitNavItemState = ComponentState & { + /** + * The size of the NavItem + * + * @default 'medium' + */ + size: NavSize; +}; diff --git a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/__snapshots__/SplitNavItem.test.tsx.snap b/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/__snapshots__/SplitNavItem.test.tsx.snap deleted file mode 100644 index 7c8a75e7d8391..0000000000000 --- a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/__snapshots__/SplitNavItem.test.tsx.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SplitNavItem renders a default state 1`] = ` -
-
- Default SplitNavItem -
-
-`; diff --git a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/renderSplitNavItem.tsx b/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/renderSplitNavItem.tsx index 01ea2b7b9e956..2c5eb7a7ad00d 100644 --- a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/renderSplitNavItem.tsx +++ b/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/renderSplitNavItem.tsx @@ -10,6 +10,12 @@ import type { SplitNavItemState, SplitNavItemSlots } from './SplitNavItem.types' export const renderSplitNavItem_unstable = (state: SplitNavItemState) => { assertSlots(state); - // TODO Add additional slots in the appropriate place - return ; + return ( + + {state.navItem && } + {state.actionButton && } + {state.toggleButton && } + {state.menuButton && } + + ); }; diff --git a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/useSplitNavItem.ts b/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/useSplitNavItem.ts deleted file mode 100644 index 66d80112a6f7e..0000000000000 --- a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/useSplitNavItem.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from 'react'; -import { getIntrinsicElementProps, slot } from '@fluentui/react-utilities'; -import type { SplitNavItemProps, SplitNavItemState } from './SplitNavItem.types'; - -/** - * Create the state required to render SplitNavItem. - * - * The returned state can be modified with hooks such as useSplitNavItemStyles_unstable, - * before being passed to renderSplitNavItem_unstable. - * - * @param props - props from this instance of SplitNavItem - * @param ref - reference to root HTMLDivElement of SplitNavItem - */ -export const useSplitNavItem_unstable = ( - props: SplitNavItemProps, - ref: React.Ref, -): SplitNavItemState => { - return { - // TODO add appropriate props/defaults - components: { - // TODO add each slot's element type or component - root: 'div', - }, - // TODO add appropriate slots, for example: - // mySlot: resolveShorthand(props.mySlot), - root: slot.always( - getIntrinsicElementProps('div', { - ref, - ...props, - }), - { elementType: 'div' }, - ), - }; -}; diff --git a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/useSplitNavItem.tsx b/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/useSplitNavItem.tsx new file mode 100644 index 0000000000000..c7664addc8809 --- /dev/null +++ b/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/useSplitNavItem.tsx @@ -0,0 +1,85 @@ +import * as React from 'react'; +import { getIntrinsicElementProps, slot } from '@fluentui/react-utilities'; +import type { SplitNavItemProps, SplitNavItemState } from './SplitNavItem.types'; +import { useNavContext_unstable } from '../NavContext'; +import { Button, MenuButton, ToggleButton } from '@fluentui/react-button'; +import { MoreHorizontalFilled, Pin20Regular } from '@fluentui/react-icons'; +import { NavItem } from '../NavItem/index'; + +/** + * Create the state required to render SplitNavItem. + * + * The returned state can be modified with hooks such as useSplitNavItemStyles_unstable, + * before being passed to renderSplitNavItem_unstable. + * + * @param props - props from this instance of SplitNavItem + * @param ref - reference to root HTMLDivElement of SplitNavItem + */ +export const useSplitNavItem_unstable = ( + props: SplitNavItemProps, + ref: React.Ref, +): SplitNavItemState => { + const { navItem, actionButton, toggleButton, menuButton, children } = props; + + const { size = 'medium' } = useNavContext_unstable(); + + const navItemShorthand = slot.optional(navItem, { + defaultProps: { + children, + }, + renderByDefault: true, + elementType: NavItem, + }); + + const actionButtonShorthand = slot.optional(actionButton, { + defaultProps: { + icon: , + size: 'small', + appearance: 'transparent', + }, + elementType: Button, + }); + + const toggleButtonShorthand = slot.optional(toggleButton, { + defaultProps: { + icon: , + size: 'small', + appearance: 'transparent', + }, + elementType: ToggleButton, + }); + + const menuButtonShorthand = slot.optional(menuButton, { + defaultProps: { + icon: , + size: 'small', + appearance: 'transparent', + }, + elementType: MenuButton, + }); + + return { + components: { + root: 'div', + navItem: NavItem, + actionButton: Button, + toggleButton: ToggleButton, + menuButton: MenuButton, + }, + root: slot.always( + getIntrinsicElementProps('div', { + ref, + ...props, + // because we're passing in children to the NavItem, + // We can be explicit about the children prop here + children: null, + }), + { elementType: 'div' }, + ), + navItem: navItemShorthand, + actionButton: actionButtonShorthand, + toggleButton: toggleButtonShorthand, + menuButton: menuButtonShorthand, + size, + }; +}; diff --git a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/useSplitNavItemStyles.styles.ts b/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/useSplitNavItemStyles.styles.ts index 651ee88bda8cb..8424625806c79 100644 --- a/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/useSplitNavItemStyles.styles.ts +++ b/packages/react-components/react-nav-preview/library/src/components/SplitNavItem/useSplitNavItemStyles.styles.ts @@ -1,22 +1,78 @@ import { makeStyles, mergeClasses } from '@griffel/react'; import type { SlotClassNames } from '@fluentui/react-utilities'; import type { SplitNavItemSlots, SplitNavItemState } from './SplitNavItem.types'; +import { navItemTokens, useRootDefaultClassName } from '../sharedNavStyles.styles'; +import { tokens } from '@fluentui/react-theme'; export const splitNavItemClassNames: SlotClassNames = { root: 'fui-SplitNavItem', - // TODO: add class names for all slots on SplitNavItemSlots. - // Should be of the form `: 'fui-SplitNavItem__` + navItem: 'fui-SplitNavItem__navItem', + actionButton: 'fui-SplitNavItem__actionButton', + toggleButton: 'fui-SplitNavItem__toggleButton', + menuButton: 'fui-SplitNavItem__menuButton', }; +// Don't use makeResetStyles here because the sub components call it once and +// This links says that makeResetStyles should only be called once per element +// https://griffel.js.org/react/api/make-reset-styles/#limitations /** * Styles for the root slot */ -const useStyles = makeStyles({ - root: { - // TODO Add default styles for the root element +const useSplitNaveItemStyles = makeStyles({ + baseRoot: { + gap: 'unset', + alignItems: 'start', + padding: 'unset', + textAlign: 'unset', + backgroundColor: navItemTokens.backgroundColor, + + transitionDuration: navItemTokens.animationTokens.animationDuration, + transitionTimingFunction: navItemTokens.animationTokens.animationTimingFunction, + transitionProperty: 'background', + + ':hover .fui-NavItem': { + backgroundColor: navItemTokens.backgroundColorHover, + transitionDuration: navItemTokens.animationTokens.animationDuration, + transitionTimingFunction: navItemTokens.animationTokens.animationTimingFunction, + transitionProperty: 'background', + }, + + ':active .fui-NavItem': { + backgroundColor: navItemTokens.backgroundColorPressed, + transitionDuration: navItemTokens.animationTokens.animationDuration, + transitionTimingFunction: navItemTokens.animationTokens.animationTimingFunction, + transitionProperty: 'background', + }, }, + basenavItem: { + // styles that we want to disagree with the default on + display: 'flex', + textTransform: 'none', + textAlign: 'left', + position: 'relative', + justifyContent: 'start', + gap: tokens.spacingVerticalL, + }, + baseSecondary: { + maxWidth: '24px', + minWidth: '24px', + paddingInline: '4px', + marginBlockStart: '4px', + + transitionDuration: navItemTokens.animationTokens.animationDuration, + transitionTimingFunction: navItemTokens.animationTokens.animationTimingFunction, + transitionProperty: 'background', - // TODO add additional classes for different states and/or slots + ':hover': { + backgroundColor: navItemTokens.backgroundColorHover, + }, + ':active': { + backgroundColor: navItemTokens.backgroundColorPressed, + }, + }, + baseMedium: { + paddingBlockStart: '6px', + }, }); /** @@ -25,11 +81,50 @@ const useStyles = makeStyles({ export const useSplitNavItemStyles_unstable = (state: SplitNavItemState): SplitNavItemState => { 'use no memo'; - const styles = useStyles(); - state.root.className = mergeClasses(splitNavItemClassNames.root, styles.root, state.root.className); + const splitNavItemStyles = useSplitNaveItemStyles(); + const sharedRootClassNames = useRootDefaultClassName(); + + state.root.className = mergeClasses( + splitNavItemClassNames.root, + sharedRootClassNames, + splitNavItemStyles.baseRoot, + state.root.className, + ); + + if (state.navItem) { + state.navItem.className = mergeClasses( + splitNavItemClassNames.navItem, + splitNavItemStyles.basenavItem, + state.navItem.className, + ); + } + + if (state.actionButton) { + state.actionButton.className = mergeClasses( + splitNavItemClassNames.actionButton, + splitNavItemStyles.baseSecondary, + state.size === 'medium' && splitNavItemStyles.baseMedium, + state.actionButton.className, + ); + } + + if (state.toggleButton) { + state.toggleButton.className = mergeClasses( + splitNavItemClassNames.toggleButton, + splitNavItemStyles.baseSecondary, + state.size === 'medium' && splitNavItemStyles.baseMedium, + state.toggleButton.className, + ); + } - // TODO Add class names to slots, for example: - // state.mySlot.className = mergeClasses(styles.mySlot, state.mySlot.className); + if (state.menuButton) { + state.menuButton.className = mergeClasses( + splitNavItemClassNames.menuButton, + splitNavItemStyles.baseSecondary, + state.size === 'medium' && splitNavItemStyles.baseMedium, + state.menuButton.className, + ); + } return state; }; diff --git a/packages/react-components/react-nav-preview/library/src/components/sharedNavStyles.styles.ts b/packages/react-components/react-nav-preview/library/src/components/sharedNavStyles.styles.ts index ca6d2e34866ff..1c35f09e77f6c 100644 --- a/packages/react-components/react-nav-preview/library/src/components/sharedNavStyles.styles.ts +++ b/packages/react-components/react-nav-preview/library/src/components/sharedNavStyles.styles.ts @@ -27,6 +27,8 @@ export const useRootDefaultClassName = makeResetStyles({ textTransform: 'none', position: 'relative', justifyContent: 'start', + alignItems: 'flex-start', + textAlign: 'left', gap: tokens.spacingVerticalL, padding: `${tokens.spacingVerticalMNudge} ${tokens.spacingHorizontalS} ${tokens.spacingVerticalMNudge} ${tokens.spacingHorizontalMNudge}`, backgroundColor: navItemTokens.backgroundColor, diff --git a/packages/react-components/react-nav-preview/stories/src/Nav/index.stories.tsx b/packages/react-components/react-nav-preview/stories/src/Nav/index.stories.tsx index 2b325469d66ef..c63459f634e14 100644 --- a/packages/react-components/react-nav-preview/stories/src/Nav/index.stories.tsx +++ b/packages/react-components/react-nav-preview/stories/src/Nav/index.stories.tsx @@ -4,9 +4,10 @@ import { Nav } from '@fluentui/react-nav-preview'; // import descriptionMd from './NavDescription.md'; // import bestPracticesMd from './NavBestPractices.md'; -export { NavDrawerDefault } from '../NavDrawer/NavDrawerDefault.stories'; -export { NavDrawerSize } from '../NavDrawer/NavDrawerSize.stories'; -export { NavDrawerControlled } from '../NavDrawer/NavDrawerControlled.stories'; +export { Basic } from '../NavDrawer/Basic.stories'; +export { VariableSizedItems } from '../NavDrawer/VariableSizedItems.stories'; +export { Controlled } from '../NavDrawer/Controlled.stories'; +export { SplitNavItems } from '../NavDrawer/SplitNavItems.stories'; export default { title: 'Preview Components/Nav', diff --git a/packages/react-components/react-nav-preview/stories/src/NavDrawer/NavDrawerDefault.stories.tsx b/packages/react-components/react-nav-preview/stories/src/NavDrawer/Basic.stories.tsx similarity index 99% rename from packages/react-components/react-nav-preview/stories/src/NavDrawer/NavDrawerDefault.stories.tsx rename to packages/react-components/react-nav-preview/stories/src/NavDrawer/Basic.stories.tsx index 2acd6b95715d0..624c093f2ac6e 100644 --- a/packages/react-components/react-nav-preview/stories/src/NavDrawer/NavDrawerDefault.stories.tsx +++ b/packages/react-components/react-nav-preview/stories/src/NavDrawer/Basic.stories.tsx @@ -85,7 +85,7 @@ const Reports = bundleIcon(DocumentBulletListMultiple20Filled, DocumentBulletLis type DrawerType = Required['type']; -export const NavDrawerDefault = (props: Partial) => { +export const Basic = (props: Partial) => { const styles = useStyles(); const typeLableId = useId('type-label'); diff --git a/packages/react-components/react-nav-preview/stories/src/NavDrawer/NavDrawerControlled.stories.tsx b/packages/react-components/react-nav-preview/stories/src/NavDrawer/Controlled.stories.tsx similarity index 99% rename from packages/react-components/react-nav-preview/stories/src/NavDrawer/NavDrawerControlled.stories.tsx rename to packages/react-components/react-nav-preview/stories/src/NavDrawer/Controlled.stories.tsx index 77fd944465474..74fbff871859f 100644 --- a/packages/react-components/react-nav-preview/stories/src/NavDrawer/NavDrawerControlled.stories.tsx +++ b/packages/react-components/react-nav-preview/stories/src/NavDrawer/Controlled.stories.tsx @@ -128,7 +128,7 @@ const getRandomPage = (): SelectedPage => { } }; -export const NavDrawerControlled = (props: Partial) => { +export const Controlled = (props: Partial) => { const styles = useStyles(); const multipleLabelId = useId('multiple-label'); diff --git a/packages/react-components/react-nav-preview/stories/src/NavDrawer/SplitNavItems.stories.tsx b/packages/react-components/react-nav-preview/stories/src/NavDrawer/SplitNavItems.stories.tsx new file mode 100644 index 0000000000000..f74f308050ff9 --- /dev/null +++ b/packages/react-components/react-nav-preview/stories/src/NavDrawer/SplitNavItems.stories.tsx @@ -0,0 +1,257 @@ +import * as React from 'react'; +import { + Hamburger, + NavDrawer, + NavDrawerBody, + NavDrawerHeader, + NavDrawerProps, + NavSize, + AppItem, + AppItemStatic, + SplitNavItem, + SplitNavItemProps, + NavItemProps, +} from '@fluentui/react-nav-preview'; +import { + Label, + Menu, + MenuButtonProps, + MenuItem, + MenuList, + MenuPopover, + MenuTrigger, + Radio, + RadioGroup, + Switch, + Tooltip, + makeStyles, + tokens, + useId, +} from '@fluentui/react-components'; +import { + Board20Filled, + Board20Regular, + BoxMultiple20Filled, + BoxMultiple20Regular, + DataArea20Filled, + DataArea20Regular, + DocumentBulletListMultiple20Filled, + DocumentBulletListMultiple20Regular, + HeartPulse20Filled, + HeartPulse20Regular, + MegaphoneLoud20Filled, + MegaphoneLoud20Regular, + People20Filled, + People20Regular, + PersonLightbulb20Filled, + PersonLightbulb20Regular, + PersonSearch20Filled, + PersonSearch20Regular, + PreviewLink20Filled, + PreviewLink20Regular, + bundleIcon, + PersonCircle32Regular, + PersonCircle24Regular, + Pin20Filled, + Pin20Regular, +} from '@fluentui/react-icons'; + +const useStyles = makeStyles({ + root: { + overflow: 'hidden', + display: 'flex', + height: '600px', + }, + content: { + flex: '1', + padding: '16px', + display: 'grid', + justifyContent: 'flex-start', + alignItems: 'flex-start', + }, + field: { + display: 'flex', + marginTop: '4px', + marginLeft: '8px', + flexDirection: 'column', + gridRowGap: tokens.spacingVerticalS, + }, +}); + +const Dashboard = bundleIcon(Board20Filled, Board20Regular); +const Announcements = bundleIcon(MegaphoneLoud20Filled, MegaphoneLoud20Regular); +const EmployeeSpotlight = bundleIcon(PersonLightbulb20Filled, PersonLightbulb20Regular); +const Search = bundleIcon(PersonSearch20Filled, PersonSearch20Regular); +const PerformanceReviews = bundleIcon(PreviewLink20Filled, PreviewLink20Regular); +const Interviews = bundleIcon(People20Filled, People20Regular); +const HealthPlans = bundleIcon(HeartPulse20Filled, HeartPulse20Regular); +const TrainingPrograms = bundleIcon(BoxMultiple20Filled, BoxMultiple20Regular); +const Analytics = bundleIcon(DataArea20Filled, DataArea20Regular); +const Reports = bundleIcon(DocumentBulletListMultiple20Filled, DocumentBulletListMultiple20Regular); + +const splitNavItems: SplitNavItemProps[] = [ + { + navItem: { value: '1', icon: }, + children: 'Dashboard', + }, + { + navItem: { value: '2', icon: }, + children: 'Announcements', + }, + { + navItem: { value: '3', icon: }, + children: 'Employee Spotlight', + }, + { + navItem: { value: '4', icon: }, + children: 'Profile Search', + }, + { + navItem: { value: '5', icon: }, + children: 'Performance Reviews', + }, + { + navItem: { value: '9', icon: }, + children: 'Interviews', + }, + { + navItem: { value: '10', icon: }, + children: 'Health Plans', + }, + { + navItem: { value: '15', icon: }, + children: 'Training Programs', + }, + { + navItem: { value: '19', icon: }, + children: 'Workforce Data', + }, + { + navItem: { value: '20', icon: }, + children: 'Reports', + }, +]; + +const SomeMenuPopover = () => { + return ( + + + Item a + Item b + + + ); +}; + +export const SplitNavItems = (props: Partial) => { + const styles = useStyles(); + + const labelId = useId('type-label'); + const linkLabelId = useId('link-label'); + const appItemIconLabelId = useId('app-item-icon-label'); + const appItemStaticLabelId = useId('app-item-static-label'); + + const [size, setNavSize] = React.useState('small'); + const [enabledLinks, setEnabledLinks] = React.useState(true); + const [isAppItemIconPresent, setIsAppItemIconPresent] = React.useState(true); + const [isAppItemStatic, setIsAppItemStatic] = React.useState(true); + const [pinnedValues, setPinnedValues] = React.useState([]); + + const linkDestination = enabledLinks ? 'https://www.bing.com' : ''; + + const appItemIcon = isAppItemIconPresent ? ( + size === 'small' ? ( + + ) : ( + + ) + ) : undefined; + + const appItem = isAppItemStatic ? ( + Contoso HR + ) : ( + + Contoso HR + + ); + + const handlePinClick = (value: string) => { + if (pinnedValues.includes(value)) { + setPinnedValues(pinnedValues.filter(v => v !== value)); + } else { + setPinnedValues([...pinnedValues, value]); + } + }; + + const generateSplitNavItems = () => { + return splitNavItems.map((item: SplitNavItemProps, index) => { + const value: string = (item.navItem as NavItemProps).value; + + return ( + + + {(triggerProps: MenuButtonProps) => ( + handlePinClick(value), + icon: pinnedValues.includes(value) ? : , + }} + menuButton={triggerProps} + > + {item.children} + + )} + + + + ); + }); + }; + + return ( +
+ + + + + + + + {appItem} + {generateSplitNavItems()} + + +
+
+ + setNavSize(data.value as NavSize)} aria-labelledby={labelId}> + + + + + setEnabledLinks(!!data.checked)} + label={enabledLinks ? 'Enabled' : 'Disabled'} + aria-labelledby={linkLabelId} + /> + + setIsAppItemStatic(!!data.checked)} + label={isAppItemStatic ? 'Static' : 'Href'} + aria-labelledby={appItemStaticLabelId} + /> + + setIsAppItemIconPresent(!!data.checked)} + label={isAppItemIconPresent ? 'Present' : 'Absent'} + aria-labelledby={appItemIconLabelId} + /> +
+
+
+ ); +}; diff --git a/packages/react-components/react-nav-preview/stories/src/NavDrawer/NavDrawerSize.stories.tsx b/packages/react-components/react-nav-preview/stories/src/NavDrawer/VariableSizedItems.stories.tsx similarity index 99% rename from packages/react-components/react-nav-preview/stories/src/NavDrawer/NavDrawerSize.stories.tsx rename to packages/react-components/react-nav-preview/stories/src/NavDrawer/VariableSizedItems.stories.tsx index 4e06a52ef48b4..5f3b9074de887 100644 --- a/packages/react-components/react-nav-preview/stories/src/NavDrawer/NavDrawerSize.stories.tsx +++ b/packages/react-components/react-nav-preview/stories/src/NavDrawer/VariableSizedItems.stories.tsx @@ -85,7 +85,7 @@ const CareerDevelopment = bundleIcon(PeopleStar20Filled, PeopleStar20Regular); const Analytics = bundleIcon(DataArea20Filled, DataArea20Regular); const Reports = bundleIcon(DocumentBulletListMultiple20Filled, DocumentBulletListMultiple20Regular); -export const NavDrawerSize = (props: Partial) => { +export const VariableSizedItems = (props: Partial) => { const styles = useStyles(); const labelId = useId('type-label');