diff --git a/packages/react-components/react-accordion/src/components/Accordion/renderAccordion.tsx b/packages/react-components/react-accordion/src/components/Accordion/renderAccordion.tsx index c2ee7bd559b1c4..87d7519836db48 100644 --- a/packages/react-components/react-accordion/src/components/Accordion/renderAccordion.tsx +++ b/packages/react-components/react-accordion/src/components/Accordion/renderAccordion.tsx @@ -3,7 +3,7 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import { AccordionContext } from './AccordionContext'; import type { AccordionState, AccordionSlots, AccordionContextValues } from './Accordion.types'; @@ -12,11 +12,11 @@ import type { AccordionState, AccordionSlots, AccordionContextValues } from './A * Function that renders the final JSX of the component */ export const renderAccordion_unstable = (state: AccordionState, contextValues: AccordionContextValues) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slotProps.root.children} - + + {state.root.children} + ); }; diff --git a/packages/react-components/react-accordion/src/components/Accordion/useAccordion.ts b/packages/react-components/react-accordion/src/components/Accordion/useAccordion.ts index 0b06b206ac3dfa..f41715b286d190 100644 --- a/packages/react-components/react-accordion/src/components/Accordion/useAccordion.ts +++ b/packages/react-components/react-accordion/src/components/Accordion/useAccordion.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, useControllableState, useEventCallback } from '@fluentui/react-utilities'; +import { getNativeElementProps, useControllableState, useEventCallback, slot } from '@fluentui/react-utilities'; import type { AccordionProps, AccordionState, AccordionToggleData, AccordionToggleEvent } from './Accordion.types'; import type { AccordionItemValue } from '../AccordionItem/AccordionItem.types'; import { useArrowNavigationGroup } from '@fluentui/react-tabster'; @@ -42,11 +42,14 @@ export const useAccordion_unstable = (props: AccordionProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - - {state.expandIconPosition === 'start' && slots.expandIcon && } - {slots.icon && } - {slotProps.root.children} - {state.expandIconPosition === 'end' && slots.expandIcon && } - - + + + {state.expandIconPosition === 'start' && state.expandIcon && } + {state.icon && } + {state.root.children} + {state.expandIconPosition === 'end' && state.expandIcon && } + + ); }; diff --git a/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeader.tsx b/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeader.tsx index 06802efcdd3e75..662e8912bbc1b7 100644 --- a/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeader.tsx +++ b/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeader.tsx @@ -1,10 +1,5 @@ import * as React from 'react'; -import { - getNativeElementProps, - isResolvedShorthand, - resolveShorthand, - useEventCallback, -} from '@fluentui/react-utilities'; +import { getNativeElementProps, isResolvedShorthand, slot, useEventCallback } from '@fluentui/react-utilities'; import { useAccordionItemContext_unstable } from '../AccordionItem/index'; import { ARIAButtonSlotProps, useARIAButtonShorthand } from '@fluentui/react-aria'; import type { AccordionHeaderProps, AccordionHeaderState } from './AccordionHeader.types'; @@ -54,19 +49,23 @@ export const useAccordionHeader_unstable = ( expandIcon: 'span', icon: 'div', }, - root: getNativeElementProps(as || 'div', { - ref, - ...props, - }), - icon: resolveShorthand(icon), - expandIcon: resolveShorthand(expandIcon, { + root: slot( + getNativeElementProps(as || 'div', { + ref, + ...props, + }), + { required: true, elementType: 'div' }, + ), + icon: slot(icon, { elementType: 'div' }), + expandIcon: slot(expandIcon, { required: true, defaultProps: { children: , 'aria-hidden': true, }, + elementType: 'span', }), - button: resolveShorthand>( + button: slot>( { ...useARIAButtonShorthand(button, { required: true, @@ -86,7 +85,7 @@ export const useAccordionHeader_unstable = ( } }), }, - { required: true }, + { required: true, elementType: 'button' }, ), }; }; diff --git a/packages/react-components/react-accordion/src/components/AccordionItem/renderAccordionItem.tsx b/packages/react-components/react-accordion/src/components/AccordionItem/renderAccordionItem.tsx index 659b3851ab40bc..54e29d649aa0d8 100644 --- a/packages/react-components/react-accordion/src/components/AccordionItem/renderAccordionItem.tsx +++ b/packages/react-components/react-accordion/src/components/AccordionItem/renderAccordionItem.tsx @@ -3,7 +3,7 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import { AccordionItemContext } from './AccordionItemContext'; import type { AccordionItemState, AccordionItemSlots, AccordionItemContextValues } from './AccordionItem.types'; @@ -11,13 +11,13 @@ import type { AccordionItemState, AccordionItemSlots, AccordionItemContextValues * Function that renders the final JSX of the component */ export const renderAccordionItem_unstable = (state: AccordionItemState, contextValues: AccordionItemContextValues) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - + - {slotProps.root.children} + {state.root.children} - + ); }; diff --git a/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItem.ts b/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItem.ts index bf1141e0df65bf..4b168edf01afef 100644 --- a/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItem.ts +++ b/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItem.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import { useAccordionContext_unstable } from '../Accordion/AccordionContext'; import type { AccordionItemProps, AccordionItemState } from './AccordionItem.types'; import type { AccordionToggleEvent } from '../Accordion/Accordion.types'; @@ -29,9 +29,12 @@ export const useAccordionItem_unstable = ( components: { root: 'div', }, - root: getNativeElementProps('div', { - ref: ref, - ...props, - }), + root: slot( + getNativeElementProps('div', { + ref, + ...props, + }), + { required: true, elementType: 'div' }, + ), }; }; diff --git a/packages/react-components/react-accordion/src/components/AccordionPanel/renderAccordionPanel.tsx b/packages/react-components/react-accordion/src/components/AccordionPanel/renderAccordionPanel.tsx index 7a4ebb205f5ed2..fa0831c909592c 100644 --- a/packages/react-components/react-accordion/src/components/AccordionPanel/renderAccordionPanel.tsx +++ b/packages/react-components/react-accordion/src/components/AccordionPanel/renderAccordionPanel.tsx @@ -3,13 +3,13 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { AccordionPanelState, AccordionPanelSlots } from './AccordionPanel.types'; /** * Function that renders the final JSX of the component */ export const renderAccordionPanel_unstable = (state: AccordionPanelState) => { - const { slots, slotProps } = getSlotsNext(state); - return state.open ? {slotProps.root.children} : null; + assertSlots(state); + return state.open ? {state.root.children} : null; }; diff --git a/packages/react-components/react-accordion/src/components/AccordionPanel/useAccordionPanel.ts b/packages/react-components/react-accordion/src/components/AccordionPanel/useAccordionPanel.ts index ec9d601e900793..7a29493a11199a 100644 --- a/packages/react-components/react-accordion/src/components/AccordionPanel/useAccordionPanel.ts +++ b/packages/react-components/react-accordion/src/components/AccordionPanel/useAccordionPanel.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import { useTabsterAttributes } from '@fluentui/react-tabster'; import { useAccordionItemContext_unstable } from '../AccordionItem/index'; import { useAccordionContext_unstable } from '../Accordion/AccordionContext'; @@ -23,10 +23,13 @@ export const useAccordionPanel_unstable = ( components: { root: 'div', }, - root: getNativeElementProps('div', { - ref, - ...props, - ...(navigation && focusableProps), - }), + root: slot( + getNativeElementProps('div', { + ref, + ...props, + ...(navigation && focusableProps), + }), + { required: true, elementType: 'div' }, + ), }; }; diff --git a/packages/react-components/react-alert/src/components/Alert/renderAlert.tsx b/packages/react-components/react-alert/src/components/Alert/renderAlert.tsx index f8554b51604c84..bb573f33b810fe 100644 --- a/packages/react-components/react-alert/src/components/Alert/renderAlert.tsx +++ b/packages/react-components/react-alert/src/components/Alert/renderAlert.tsx @@ -2,19 +2,19 @@ /** @jsx createElement */ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { AlertState, AlertSlots } from './Alert.types'; export const renderAlert_unstable = (state: AlertState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slots.icon && } - {slots.avatar && } - {slotProps.root.children} - {slots.action && } - + + {state.icon && } + {state.avatar && } + {state.root.children} + {state.action && } + ); }; diff --git a/packages/react-components/react-alert/src/components/Alert/useAlert.tsx b/packages/react-components/react-alert/src/components/Alert/useAlert.tsx index ef7e1f8724e1f1..6578fe82747632 100644 --- a/packages/react-components/react-alert/src/components/Alert/useAlert.tsx +++ b/packages/react-components/react-alert/src/components/Alert/useAlert.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { Avatar } from '@fluentui/react-avatar'; import { Button } from '@fluentui/react-button'; import { CheckmarkCircleFilled, DismissCircleFilled, InfoFilled, WarningFilled } from '@fluentui/react-icons'; -import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { AlertProps, AlertState } from './Alert.types'; @@ -39,36 +39,27 @@ export const useAlert_unstable = (props: AlertProps, ref: React.Ref break; } - const action = resolveShorthand(props.action, { defaultProps: { appearance: 'transparent' } }); - const avatar = resolveShorthand(props.avatar); + const action = slot(props.action, { defaultProps: { appearance: 'transparent' }, elementType: Button }); + const avatar = slot(props.avatar, { elementType: Avatar }); let icon; - /** Avatar prop takes precedence over the icon or intent prop */ - if (!avatar) { - icon = resolveShorthand(props.icon, { - defaultProps: { - children: defaultIcon, - }, - required: !!props.intent, - }); + /** Avatar prop takes precedence over the icon or intent prop */ if (!avatar) { + icon = slot(props.icon, { defaultProps: { children: defaultIcon }, required: !!props.intent, elementType: 'span' }); } - return { action, appearance, avatar, - components: { - root: 'div', - icon: 'span', - action: Button, - avatar: Avatar, - }, + components: { root: 'div', icon: 'span', action: Button, avatar: Avatar }, icon, intent, - root: getNativeElementProps('div', { - ref, - role: defaultRole, - children: props.children, - ...props, - }), + root: slot( + getNativeElementProps('div', { + ref, + role: defaultRole, + children: props.children, + ...props, + }), + { required: true, elementType: 'div' }, + ), }; }; diff --git a/packages/react-components/react-aria/package.json b/packages/react-components/react-aria/package.json index cf747ccc4e3e29..28ca303033e76b 100644 --- a/packages/react-components/react-aria/package.json +++ b/packages/react-components/react-aria/package.json @@ -33,6 +33,7 @@ "dependencies": { "@fluentui/keyboard-keys": "^9.0.3", "@fluentui/react-utilities": "^9.9.2", + "@fluentui/react-jsx-runtime": "9.0.0-alpha.6", "@swc/helpers": "^0.4.14" }, "peerDependencies": { diff --git a/packages/react-components/react-aria/src/button/useARIAButtonProps.test.tsx b/packages/react-components/react-aria/src/button/useARIAButtonProps.test.tsx index e3ca1eb9e51496..e2424144ebbcb8 100644 --- a/packages/react-components/react-aria/src/button/useARIAButtonProps.test.tsx +++ b/packages/react-components/react-aria/src/button/useARIAButtonProps.test.tsx @@ -1,17 +1,23 @@ -import * as React from 'react'; +/** @jsxRuntime classic */ +/** @jsx createElement */ + +import { createElement } from '@fluentui/react-jsx-runtime'; + import { useARIAButtonProps } from './useARIAButtonProps'; import { renderHook } from '@testing-library/react-hooks'; import { fireEvent, render } from '@testing-library/react'; -import { getSlots, Slot, ComponentProps } from '@fluentui/react-utilities'; +import { Slot, ComponentProps, assertSlots } from '@fluentui/react-utilities'; import { ARIAButtonProps, ARIAButtonSlotProps } from './types'; import { useARIAButtonShorthand } from './useARIAButtonShorthand'; const TestButton = (props: ComponentProps<{ root: Slot }>) => { - const { slots, slotProps } = getSlots<{ root: Slot }>({ - components: { root: 'button' }, + type TestSlots = { root: Slot }; + const state = { + components: { root: props.as ?? 'button' }, root: useARIAButtonShorthand(props, { required: true }), - }); - return ; + }; + assertSlots(state); + return ; }; describe('useARIAButton', () => { diff --git a/packages/react-components/react-aria/src/button/useARIAButtonShorthand.ts b/packages/react-components/react-aria/src/button/useARIAButtonShorthand.ts index 2d42a7297148a9..ab9cb3831aac65 100644 --- a/packages/react-components/react-aria/src/button/useARIAButtonShorthand.ts +++ b/packages/react-components/react-aria/src/button/useARIAButtonShorthand.ts @@ -1,4 +1,5 @@ -import { resolveShorthand } from '@fluentui/react-utilities'; +import * as React from 'react'; +import { isResolvedShorthand, slot } from '@fluentui/react-utilities'; import { useARIAButtonProps } from './useARIAButtonProps'; import type { ResolveShorthandFunction } from '@fluentui/react-utilities'; import type { ARIAButtonProps, ARIAButtonSlotProps, ARIAButtonType } from './types'; @@ -12,8 +13,13 @@ import type { ARIAButtonProps, ARIAButtonSlotProps, ARIAButtonType } from './typ * for multiple scenarios of shorthand properties. Ensuring 1st rule of ARIA for cases * where no attribute addition is required. */ -export const useARIAButtonShorthand: ResolveShorthandFunction = (slot, options) => { - const shorthand = resolveShorthand(slot, options); +// eslint-disable-next-line deprecation/deprecation +export const useARIAButtonShorthand: ResolveShorthandFunction = (value, options) => { + const elementType = isResolvedShorthand(value) ? value.as ?? 'button' : ('button' as const); + const shorthand = slot(value, { + ...options, + elementType: elementType as React.ElementType as React.ComponentType, + }); const shorthandARIAButton = useARIAButtonProps(shorthand?.as ?? 'button', shorthand); return shorthand && shorthandARIAButton; }; diff --git a/packages/react-components/react-aria/stories/useARIAButton/index.stories.tsx b/packages/react-components/react-aria/stories/useARIAButton/index.stories.tsx index 1b5cc18a8adf71..bda6ee3ffe5b0c 100644 --- a/packages/react-components/react-aria/stories/useARIAButton/index.stories.tsx +++ b/packages/react-components/react-aria/stories/useARIAButton/index.stories.tsx @@ -1,8 +1,11 @@ +/** @jsxRuntime classic */ +/** @jsx createElement */ + +import { createElement } from '@fluentui/react-jsx-runtime'; import * as React from 'react'; import { useARIAButtonShorthand } from '../../src/button'; import type { ARIAButtonSlotProps } from '../../src/button'; -import { getSlots } from '@fluentui/react-components'; -import type { ComponentState, Slot } from '@fluentui/react-components'; +import { ComponentState, Slot, assertSlots } from '@fluentui/react-components'; type Slots = { root: Slot<'div'>; @@ -24,11 +27,11 @@ export const Default = (args: DefaultArgs) => { children: React.Fragment, }, }; - const { slots, slotProps } = getSlots(state); + assertSlots(state); return ( - - this is a button - + + this is a button + ); }; @@ -47,11 +50,11 @@ export const Anchor = (args: DefaultArgs) => { }, { required: true }, ); - const { slots, slotProps } = getSlots({ - components: { root: 'a' }, + const state = { root: props, - }); - return this is an anchor; + }; + assertSlots(state); + return this is an anchor; }; export default { diff --git a/packages/react-components/react-avatar/src/components/Avatar/renderAvatar.tsx b/packages/react-components/react-avatar/src/components/Avatar/renderAvatar.tsx index 203a126982e162..bcb65779cce547 100644 --- a/packages/react-components/react-avatar/src/components/Avatar/renderAvatar.tsx +++ b/packages/react-components/react-avatar/src/components/Avatar/renderAvatar.tsx @@ -3,19 +3,19 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { AvatarSlots, AvatarState } from './Avatar.types'; export const renderAvatar_unstable = (state: AvatarState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slots.initials && } - {slots.icon && } - {slots.image && } - {slots.badge && } + + {state.initials && } + {state.icon && } + {state.image && } + {state.badge && } {state.activeAriaLabelElement} - + ); }; diff --git a/packages/react-components/react-avatar/src/components/Avatar/useAvatar.tsx b/packages/react-components/react-avatar/src/components/Avatar/useAvatar.tsx index 7a7b69bd1990b4..023b5fd3dfd003 100644 --- a/packages/react-components/react-avatar/src/components/Avatar/useAvatar.tsx +++ b/packages/react-components/react-avatar/src/components/Avatar/useAvatar.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, mergeCallbacks, resolveShorthand, useId } from '@fluentui/react-utilities'; +import { getNativeElementProps, mergeCallbacks, slot, useId } from '@fluentui/react-utilities'; import { getInitials } from '../../utils/index'; import type { AvatarNamedColor, AvatarProps, AvatarState } from './Avatar.types'; import { PersonRegular } from '@fluentui/react-icons'; @@ -32,84 +32,63 @@ export const useAvatar_unstable = (props: AvatarProps, ref: React.Ref(undefined); - const image: AvatarState['image'] = resolveShorthand(props.image, { - defaultProps: { - alt: '', - role: 'presentation', - 'aria-hidden': true, - hidden: imageHidden, - }, - }); - - // Hide the image if it fails to load and restore it on a successful load + const image: AvatarState['image'] = slot(props.image, { + defaultProps: { alt: '', role: 'presentation', 'aria-hidden': true, hidden: imageHidden }, + elementType: 'img', + }); // Hide the image if it fails to load and restore it on a successful load if (image) { image.onError = mergeCallbacks(image.onError, () => setImageHidden(true)); image.onLoad = mergeCallbacks(image.onLoad, () => setImageHidden(undefined)); - } - - // Resolve the initials slot, defaulted to getInitials. - let initials: AvatarState['initials'] = resolveShorthand(props.initials, { + } // Resolve the initials slot, defaulted to getInitials. + let initials: AvatarState['initials'] = slot(props.initials, { required: true, defaultProps: { children: getInitials(name, dir === 'rtl', { firstInitialOnly: size <= 16 }), id: baseId + '__initials', }, - }); - - // Don't render the initials slot if it's empty + elementType: 'span', + }); // Don't render the initials slot if it's empty if (!initials?.children) { initials = undefined; - } - - // Render the icon slot *only if* there aren't any initials or image to display + } // Render the icon slot *only if* there aren't any initials or image to display let icon: AvatarState['icon'] = undefined; if (!initials && (!image || imageHidden)) { - icon = resolveShorthand(props.icon, { + icon = slot(props.icon, { required: true, - defaultProps: { - children: , - 'aria-hidden': true, - }, + defaultProps: { children: , 'aria-hidden': true }, + elementType: 'span', }); } - - const badge: AvatarState['badge'] = resolveShorthand(props.badge, { - defaultProps: { - size: getBadgeSize(size), - id: baseId + '__badge', - }, + const badge: AvatarState['badge'] = slot(props.badge, { + defaultProps: { size: getBadgeSize(size), id: baseId + '__badge' }, + elementType: PresenceBadge, }); - - let activeAriaLabelElement: AvatarState['activeAriaLabelElement']; - - // Resolve aria-label and/or aria-labelledby if not provided by the user + let activeAriaLabelElement: AvatarState['activeAriaLabelElement']; // Resolve aria-label and/or aria-labelledby if not provided by the user if (!root['aria-label'] && !root['aria-labelledby']) { if (name) { - root['aria-label'] = name; - - // Include the badge in labelledby if it exists + root['aria-label'] = name; // Include the badge in labelledby if it exists if (badge) { root['aria-labelledby'] = root.id + ' ' + badge.id; } } else if (initials) { // root's aria-label should be the name, but fall back to being labelledby the initials if name is missing root['aria-labelledby'] = initials.id + (badge ? ' ' + badge.id : ''); - } - - // Add the active state to the aria label + } // Add the active state to the aria label if (active === 'active' || active === 'inactive') { const activeText = DEFAULT_STRINGS[active]; if (root['aria-labelledby']) { @@ -127,7 +106,6 @@ export const useAvatar_unstable = (props: AvatarProps, ref: React.Ref { if (size >= 96) { return 'extra-large'; diff --git a/packages/react-components/react-avatar/src/components/AvatarGroup/renderAvatarGroup.tsx b/packages/react-components/react-avatar/src/components/AvatarGroup/renderAvatarGroup.tsx index c5937f7e5c7db3..b75b9c4d4897b6 100644 --- a/packages/react-components/react-avatar/src/components/AvatarGroup/renderAvatarGroup.tsx +++ b/packages/react-components/react-avatar/src/components/AvatarGroup/renderAvatarGroup.tsx @@ -3,7 +3,7 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import { AvatarGroupProvider } from '../../contexts/AvatarGroupContext'; import type { AvatarGroupState, AvatarGroupSlots, AvatarGroupContextValues } from './AvatarGroup.types'; @@ -11,11 +11,11 @@ import type { AvatarGroupState, AvatarGroupSlots, AvatarGroupContextValues } fro * Render the final JSX of AvatarGroup */ export const renderAvatarGroup_unstable = (state: AvatarGroupState, contextValues: AvatarGroupContextValues) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - + ); }; diff --git a/packages/react-components/react-avatar/src/components/AvatarGroup/useAvatarGroup.tsx b/packages/react-components/react-avatar/src/components/AvatarGroup/useAvatarGroup.tsx index 6a9d14a4f4f328..aa32174c0232ab 100644 --- a/packages/react-components/react-avatar/src/components/AvatarGroup/useAvatarGroup.tsx +++ b/packages/react-components/react-avatar/src/components/AvatarGroup/useAvatarGroup.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { AvatarGroupProps, AvatarGroupState } from './AvatarGroup.types'; /** @@ -14,24 +14,19 @@ import type { AvatarGroupProps, AvatarGroupState } from './AvatarGroup.types'; export const useAvatarGroup_unstable = (props: AvatarGroupProps, ref: React.Ref): AvatarGroupState => { const { layout = 'spread', size = defaultAvatarGroupSize } = props; - const root = getNativeElementProps( - 'div', - { - role: 'group', - ...props, - ref, - }, - ['size'], + const root = slot( + getNativeElementProps( + 'div', + { + role: 'group', + ...props, + ref, + }, + ['size'], + ), + { required: true, elementType: 'div' }, ); - - return { - layout, - size, - components: { - root: 'div', - }, - root, - }; + return { layout, size, components: { root: 'div' }, root }; }; export const defaultAvatarGroupSize = 32; diff --git a/packages/react-components/react-avatar/src/components/AvatarGroupItem/renderAvatarGroupItem.tsx b/packages/react-components/react-avatar/src/components/AvatarGroupItem/renderAvatarGroupItem.tsx index fe27c39ad144bc..fed66dac5d40e0 100644 --- a/packages/react-components/react-avatar/src/components/AvatarGroupItem/renderAvatarGroupItem.tsx +++ b/packages/react-components/react-avatar/src/components/AvatarGroupItem/renderAvatarGroupItem.tsx @@ -3,19 +3,19 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { AvatarGroupItemState, AvatarGroupItemSlots } from './AvatarGroupItem.types'; /** * Render the final JSX of AvatarGroupItem */ export const renderAvatarGroupItem_unstable = (state: AvatarGroupItemState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - - {state.isOverflowItem && } - + + + {state.isOverflowItem && } + ); }; diff --git a/packages/react-components/react-avatar/src/components/AvatarGroupItem/useAvatarGroupItem.ts b/packages/react-components/react-avatar/src/components/AvatarGroupItem/useAvatarGroupItem.ts index 58978388fd709a..ed0a0c5b13d0db 100644 --- a/packages/react-components/react-avatar/src/components/AvatarGroupItem/useAvatarGroupItem.ts +++ b/packages/react-components/react-avatar/src/components/AvatarGroupItem/useAvatarGroupItem.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import { Avatar } from '../Avatar/Avatar'; import { AvatarGroupContext, useAvatarGroupContext_unstable } from '../../contexts/AvatarGroupContext'; import { defaultAvatarGroupSize } from '../AvatarGroup/useAvatarGroup'; -import { resolveShorthand } from '@fluentui/react-utilities'; +import { slot } from '@fluentui/react-utilities'; import { useHasParentContext } from '@fluentui/react-context-selector'; import type { AvatarGroupItemProps, AvatarGroupItemState } from './AvatarGroupItem.types'; @@ -41,14 +41,15 @@ export const useAvatarGroupItem_unstable = ( avatar: Avatar, overflowLabel: 'span', }, - root: resolveShorthand(props.root, { + root: slot(props.root, { required: true, defaultProps: { style, className, }, + elementType: groupIsOverflow ? 'li' : 'div', }), - avatar: resolveShorthand(props.avatar, { + avatar: slot(props.avatar, { required: true, defaultProps: { ref, @@ -56,14 +57,16 @@ export const useAvatarGroupItem_unstable = ( color: 'colorful', ...avatarSlotProps, }, + elementType: Avatar, }), - overflowLabel: resolveShorthand(props.overflowLabel, { + overflowLabel: slot(props.overflowLabel, { required: true, defaultProps: { // Avatar already has its aria-label set to the name, this will prevent the name to be read twice. 'aria-hidden': true, children: props.name, }, + elementType: 'span', }), }; }; diff --git a/packages/react-components/react-avatar/src/components/AvatarGroupPopover/renderAvatarGroupPopover.tsx b/packages/react-components/react-avatar/src/components/AvatarGroupPopover/renderAvatarGroupPopover.tsx index 4d14b7aa161332..7321152222cab6 100644 --- a/packages/react-components/react-avatar/src/components/AvatarGroupPopover/renderAvatarGroupPopover.tsx +++ b/packages/react-components/react-avatar/src/components/AvatarGroupPopover/renderAvatarGroupPopover.tsx @@ -5,9 +5,8 @@ import { createElement } from '@fluentui/react-jsx-runtime'; import { AvatarGroupProvider } from '../../contexts/AvatarGroupContext'; import { AvatarGroupContextValues } from '../AvatarGroup/AvatarGroup.types'; -import { getSlotsNext } from '@fluentui/react-utilities'; -import { PopoverProps, PopoverTrigger } from '@fluentui/react-popover'; -import { TooltipProps } from '@fluentui/react-tooltip'; +import { assertSlots } from '@fluentui/react-utilities'; +import { PopoverTrigger } from '@fluentui/react-popover'; import type { AvatarGroupPopoverState, AvatarGroupPopoverSlots } from './AvatarGroupPopover.types'; /** @@ -17,20 +16,20 @@ export const renderAvatarGroupPopover_unstable = ( state: AvatarGroupPopoverState, contextValues: AvatarGroupContextValues, ) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - + - - - + + + - + - + - - + + ); }; diff --git a/packages/react-components/react-avatar/src/components/AvatarGroupPopover/useAvatarGroupPopover.tsx b/packages/react-components/react-avatar/src/components/AvatarGroupPopover/useAvatarGroupPopover.tsx index 986a5f3a617341..175554e9e5c537 100644 --- a/packages/react-components/react-avatar/src/components/AvatarGroupPopover/useAvatarGroupPopover.tsx +++ b/packages/react-components/react-avatar/src/components/AvatarGroupPopover/useAvatarGroupPopover.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { useAvatarGroupContext_unstable } from '../../contexts/AvatarGroupContext'; import { defaultAvatarGroupSize } from '../AvatarGroup/useAvatarGroup'; -import { resolveShorthand, useControllableState } from '@fluentui/react-utilities'; +import { slot, useControllableState } from '@fluentui/react-utilities'; import { MoreHorizontalRegular } from '@fluentui/react-icons'; import { OnOpenChangeData, OpenPopoverEvents, Popover, PopoverSurface } from '@fluentui/react-popover'; import type { AvatarGroupPopoverProps, AvatarGroupPopoverState } from './AvatarGroupPopover.types'; @@ -59,42 +59,49 @@ export const useAvatarGroupPopover_unstable = (props: AvatarGroupPopoverProps): popoverSurface: PopoverSurface, tooltip: Tooltip, }, - root: { - // Popover expects a child for its children. The children are added in the renderAvatarGroupPopover. - children: <>, - size: 'small', - trapFocus: true, - ...restOfProps, - open: popoverOpen, - onOpenChange: handleOnPopoverChange, - }, - triggerButton: resolveShorthand(props.triggerButton, { + root: slot( + { + // Popover expects a child for its children. The children are added in the renderAvatarGroupPopover. + children: <>, + size: 'small', + trapFocus: true, + ...restOfProps, + open: popoverOpen, + onOpenChange: handleOnPopoverChange, + }, + { required: true, elementType: Popover }, + ), + triggerButton: slot(props.triggerButton, { required: true, defaultProps: { children: triggerButtonChildren, type: 'button', }, + elementType: 'button', }), - content: resolveShorthand(props.content, { + content: slot(props.content, { required: true, defaultProps: { children, role: 'list', }, + elementType: 'ul', }), - popoverSurface: resolveShorthand(props.popoverSurface, { + popoverSurface: slot(props.popoverSurface, { required: true, defaultProps: { 'aria-label': 'Overflow', tabIndex: 0, }, + elementType: PopoverSurface, }), - tooltip: resolveShorthand(props.tooltip, { + tooltip: slot(props.tooltip, { required: true, defaultProps: { content: 'View more people.', relationship: 'label', }, + elementType: Tooltip, }), }; }; diff --git a/packages/react-components/react-badge/src/components/Badge/renderBadge.tsx b/packages/react-components/react-badge/src/components/Badge/renderBadge.tsx index 5783fbb51bb67a..f82fe51be4cdbb 100644 --- a/packages/react-components/react-badge/src/components/Badge/renderBadge.tsx +++ b/packages/react-components/react-badge/src/components/Badge/renderBadge.tsx @@ -3,17 +3,17 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { BadgeState, BadgeSlots } from './Badge.types'; export const renderBadge_unstable = (state: BadgeState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {state.iconPosition === 'before' && slots.icon && } + + {state.iconPosition === 'before' && state.icon && } {state.root.children} - {state.iconPosition === 'after' && slots.icon && } - + {state.iconPosition === 'after' && state.icon && } + ); }; diff --git a/packages/react-components/react-badge/src/components/Badge/useBadge.ts b/packages/react-components/react-badge/src/components/Badge/useBadge.ts index 9a8db809a2cc88..7ea9bcd530e56c 100644 --- a/packages/react-components/react-badge/src/components/Badge/useBadge.ts +++ b/packages/react-components/react-badge/src/components/Badge/useBadge.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { BadgeProps, BadgeState } from './Badge.types'; /** @@ -24,11 +24,14 @@ export const useBadge_unstable = (props: BadgeProps, ref: React.Ref root: 'div', icon: 'span', }, - root: getNativeElementProps('div', { - ref, - ...props, - }), - icon: resolveShorthand(props.icon), + root: slot( + getNativeElementProps('div', { + ref, + ...props, + }), + { required: true, elementType: 'div' }, + ), + icon: slot(props.icon, { elementType: 'span' }), }; return state; diff --git a/packages/react-components/react-badge/src/components/PresenceBadge/usePresenceBadge.tsx b/packages/react-components/react-badge/src/components/PresenceBadge/usePresenceBadge.tsx index bcb32728e95471..de39ee703e3998 100644 --- a/packages/react-components/react-badge/src/components/PresenceBadge/usePresenceBadge.tsx +++ b/packages/react-components/react-badge/src/components/PresenceBadge/usePresenceBadge.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { resolveShorthand } from '@fluentui/react-utilities'; +import { slot } from '@fluentui/react-utilities'; import { presenceAvailableFilled, presenceAvailableRegular, @@ -69,11 +69,12 @@ export const usePresenceBadge_unstable = ( role: 'img', ...props, size, - icon: resolveShorthand(props.icon, { + icon: slot(props.icon, { defaultProps: { children: IconElement ? : null, }, required: true, + elementType: 'span', }), }, ref, diff --git a/packages/react-components/react-breadcrumb/src/components/Breadcrumb/renderBreadcrumb.tsx b/packages/react-components/react-breadcrumb/src/components/Breadcrumb/renderBreadcrumb.tsx index f05de6ba2ab3b4..afd72b9bdfcad3 100644 --- a/packages/react-components/react-breadcrumb/src/components/Breadcrumb/renderBreadcrumb.tsx +++ b/packages/react-components/react-breadcrumb/src/components/Breadcrumb/renderBreadcrumb.tsx @@ -3,20 +3,19 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import { BreadcrumbProvider } from './BreadcrumbContext'; import type { BreadcrumbState, BreadcrumbSlots, BreadcrumbContextValues } from './Breadcrumb.types'; /** * Render the final JSX of Breadcrumb */ export const renderBreadcrumb_unstable = (state: BreadcrumbState, contextValues: BreadcrumbContextValues) => { - const { slots, slotProps } = getSlotsNext(state); - const { root, list } = slotProps; + assertSlots(state); return ( - + - {slots.list && {root.children}} + {state.list && {state.root.children}} - + ); }; diff --git a/packages/react-components/react-breadcrumb/src/components/Breadcrumb/useBreadcrumb.ts b/packages/react-components/react-breadcrumb/src/components/Breadcrumb/useBreadcrumb.ts index d0d5628eb8ae5e..5aa41bb2090035 100644 --- a/packages/react-components/react-breadcrumb/src/components/Breadcrumb/useBreadcrumb.ts +++ b/packages/react-components/react-breadcrumb/src/components/Breadcrumb/useBreadcrumb.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { BreadcrumbProps, BreadcrumbState } from './Breadcrumb.types'; import { useArrowNavigationGroup } from '@fluentui/react-tabster'; @@ -34,13 +34,16 @@ export const useBreadcrumb_unstable = (props: BreadcrumbProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); - return ; + return ; }; diff --git a/packages/react-components/react-breadcrumb/src/components/BreadcrumbDivider/useBreadcrumbDivider.tsx b/packages/react-components/react-breadcrumb/src/components/BreadcrumbDivider/useBreadcrumbDivider.tsx index 10a323b8befa88..aa7d3deba083be 100644 --- a/packages/react-components/react-breadcrumb/src/components/BreadcrumbDivider/useBreadcrumbDivider.tsx +++ b/packages/react-components/react-breadcrumb/src/components/BreadcrumbDivider/useBreadcrumbDivider.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { BreadcrumbDividerProps, BreadcrumbDividerState } from './BreadcrumbDivider.types'; import { ChevronRight20Regular, @@ -34,12 +34,15 @@ export const useBreadcrumbDivider_unstable = ( components: { root: 'li', }, - root: getNativeElementProps('li', { - ref, - 'aria-hidden': true, - children: icon, - ...props, - }), + root: slot( + getNativeElementProps('li', { + ref, + 'aria-hidden': true, + children: icon, + ...props, + }), + { required: true, elementType: 'li' }, + ), }; }; diff --git a/packages/react-components/react-breadcrumb/src/components/BreadcrumbItem/renderBreadcrumbItem.tsx b/packages/react-components/react-breadcrumb/src/components/BreadcrumbItem/renderBreadcrumbItem.tsx index bec3d568eb4120..ca0592cc31a7aa 100644 --- a/packages/react-components/react-breadcrumb/src/components/BreadcrumbItem/renderBreadcrumbItem.tsx +++ b/packages/react-components/react-breadcrumb/src/components/BreadcrumbItem/renderBreadcrumbItem.tsx @@ -3,14 +3,14 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { BreadcrumbItemState, BreadcrumbItemSlots } from './BreadcrumbItem.types'; /** * Render the final JSX of BreadcrumbItem */ export const renderBreadcrumbItem_unstable = (state: BreadcrumbItemState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); - return {slotProps.root.children}; + return {state.root.children}; }; diff --git a/packages/react-components/react-breadcrumb/src/components/BreadcrumbItem/useBreadcrumbItem.ts b/packages/react-components/react-breadcrumb/src/components/BreadcrumbItem/useBreadcrumbItem.ts index 5d70e079348bd2..6d1be736ed78ad 100644 --- a/packages/react-components/react-breadcrumb/src/components/BreadcrumbItem/useBreadcrumbItem.ts +++ b/packages/react-components/react-breadcrumb/src/components/BreadcrumbItem/useBreadcrumbItem.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { BreadcrumbItemProps, BreadcrumbItemState } from './BreadcrumbItem.types'; import { useBreadcrumbContext_unstable } from '../Breadcrumb/BreadcrumbContext'; @@ -22,10 +22,13 @@ export const useBreadcrumbItem_unstable = ( components: { root: 'li', }, - root: getNativeElementProps('div', { - ref, - ...props, - }), + root: slot( + getNativeElementProps('div', { + ref, + ...props, + }), + { required: true, elementType: 'li' }, + ), size, current, }; diff --git a/packages/react-components/react-breadcrumb/src/components/BreadcrumbLink/renderBreadcrumbLink.tsx b/packages/react-components/react-breadcrumb/src/components/BreadcrumbLink/renderBreadcrumbLink.tsx index e2acf57c918adf..58201ca22e5ddd 100644 --- a/packages/react-components/react-breadcrumb/src/components/BreadcrumbLink/renderBreadcrumbLink.tsx +++ b/packages/react-components/react-breadcrumb/src/components/BreadcrumbLink/renderBreadcrumbLink.tsx @@ -3,20 +3,20 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { BreadcrumbLinkState, BreadcrumbLinkSlots } from './BreadcrumbLink.types'; /** * Render the final JSX of BreadcrumbLink */ export const renderBreadcrumbLink_unstable = (state: BreadcrumbLinkState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); const { iconOnly, iconPosition } = state; return ( - - {iconPosition !== 'after' && slots.icon && } + + {iconPosition !== 'after' && state.icon && } {!iconOnly && state.root.children} - {iconPosition === 'after' && slots.icon && } - + {iconPosition === 'after' && state.icon && } + ); }; diff --git a/packages/react-components/react-breadcrumb/src/components/BreadcrumbLink/useBreadcrumbLink.ts b/packages/react-components/react-breadcrumb/src/components/BreadcrumbLink/useBreadcrumbLink.ts index b6097b6fa8c880..25261b9febd79c 100644 --- a/packages/react-components/react-breadcrumb/src/components/BreadcrumbLink/useBreadcrumbLink.ts +++ b/packages/react-components/react-breadcrumb/src/components/BreadcrumbLink/useBreadcrumbLink.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { BreadcrumbLinkProps, BreadcrumbLinkState } from './BreadcrumbLink.types'; import { Link } from '@fluentui/react-link'; import { useBreadcrumbContext_unstable } from '../Breadcrumb/BreadcrumbContext'; @@ -20,18 +20,17 @@ export const useBreadcrumbLink_unstable = ( const { current = false, disabled = false, icon, overflow = false, ...rest } = props; const linkAppearance = props.appearance || appearance; - const iconShorthand = resolveShorthand(icon); - + const iconShorthand = slot(icon, { elementType: 'span' }); return { - components: { - root: Link, - icon: 'span', - }, - root: getNativeElementProps('a', { - 'aria-current': current ? props['aria-current'] ?? 'page' : undefined, - ref, - ...rest, - }), + components: { root: Link, icon: 'span' }, + root: slot( + getNativeElementProps('a', { + 'aria-current': current ? props['aria-current'] ?? 'page' : undefined, + ref, + ...rest, + }), + { required: true, elementType: Link }, + ), appearance: linkAppearance === 'subtle' ? 'subtle' : 'default', current, disabled, diff --git a/packages/react-components/react-button/src/components/Button/renderButton.tsx b/packages/react-components/react-button/src/components/Button/renderButton.tsx index c003b557e3ebb3..a5343c2d755e28 100644 --- a/packages/react-components/react-button/src/components/Button/renderButton.tsx +++ b/packages/react-components/react-button/src/components/Button/renderButton.tsx @@ -3,21 +3,21 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { ButtonSlots, ButtonState } from './Button.types'; /** * Renders a Button component by passing the state defined props to the appropriate slots. */ export const renderButton_unstable = (state: ButtonState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); const { iconOnly, iconPosition } = state; return ( - - {iconPosition !== 'after' && slots.icon && } + + {iconPosition !== 'after' && state.icon && } {!iconOnly && state.root.children} - {iconPosition === 'after' && slots.icon && } - + {iconPosition === 'after' && state.icon && } + ); }; diff --git a/packages/react-components/react-button/src/components/Button/useButton.ts b/packages/react-components/react-button/src/components/Button/useButton.ts index eb1899903c4834..263579218084de 100644 --- a/packages/react-components/react-button/src/components/Button/useButton.ts +++ b/packages/react-components/react-button/src/components/Button/useButton.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { ARIAButtonSlotProps, useARIAButtonShorthand } from '@fluentui/react-aria'; -import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import { useButtonContext } from '../../contexts/ButtonContext'; import type { ButtonProps, ButtonState } from './Button.types'; @@ -24,8 +24,7 @@ export const useButton_unstable = ( shape = 'rounded', size = contextSize ?? 'medium', } = props; - const iconShorthand = resolveShorthand(icon); - + const iconShorthand = slot(icon, { elementType: 'span' }); return { // Props passed at the top-level appearance, @@ -33,26 +32,21 @@ export const useButton_unstable = ( disabledFocusable, iconPosition, shape, - size, - - // State calculated from a set of props - iconOnly: Boolean(iconShorthand?.children && !props.children), - - // Slots definition - components: { - root: 'button', - icon: 'span', - }, - - root: getNativeElementProps( - as, - useARIAButtonShorthand>(props, { - required: true, - defaultProps: { - ref: ref as React.Ref, - type: 'button', - }, - }), + size, // State calculated from a set of props + iconOnly: Boolean(iconShorthand?.children && !props.children), // Slots definition + components: { root: 'button', icon: 'span' }, + root: slot( + getNativeElementProps( + as, + useARIAButtonShorthand>(props, { + required: true, + defaultProps: { + ref: ref as React.Ref, + type: 'button', + }, + }), + ), + { required: true, elementType: 'button' }, ), icon: iconShorthand, }; diff --git a/packages/react-components/react-button/src/components/CompoundButton/renderCompoundButton.tsx b/packages/react-components/react-button/src/components/CompoundButton/renderCompoundButton.tsx index fe1cdcbe022364..cf94faaa139b85 100644 --- a/packages/react-components/react-button/src/components/CompoundButton/renderCompoundButton.tsx +++ b/packages/react-components/react-button/src/components/CompoundButton/renderCompoundButton.tsx @@ -3,26 +3,27 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { CompoundButtonSlots, CompoundButtonState } from './CompoundButton.types'; /** * Renders a CompoundButton component by passing the state defined props to the appropriate slots. */ export const renderCompoundButton_unstable = (state: CompoundButtonState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); const { iconOnly, iconPosition } = state; return ( - - {iconPosition !== 'after' && slots.icon && } + + {iconPosition !== 'after' && state.icon && } {!iconOnly && ( - - {slotProps.root.children} - {slots.secondaryContent && } - + + {state.root.children} + {state.secondaryContent && } + )} - {iconPosition === 'after' && slots.icon && } - + + {iconPosition === 'after' && state.icon && } + ); }; diff --git a/packages/react-components/react-button/src/components/CompoundButton/useCompoundButton.ts b/packages/react-components/react-button/src/components/CompoundButton/useCompoundButton.ts index e254af37755fa5..ddef5a52b7541a 100644 --- a/packages/react-components/react-button/src/components/CompoundButton/useCompoundButton.ts +++ b/packages/react-components/react-button/src/components/CompoundButton/useCompoundButton.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { resolveShorthand } from '@fluentui/react-utilities'; +import { slot } from '@fluentui/react-utilities'; import { useButton_unstable } from '../Button/index'; import type { CompoundButtonProps, CompoundButtonState } from './CompoundButton.types'; @@ -23,8 +23,8 @@ export const useCompoundButton_unstable = ( contentContainer: 'span', secondaryContent: 'span', }, - contentContainer: resolveShorthand(contentContainer, { required: true }), - secondaryContent: resolveShorthand(secondaryContent), + contentContainer: slot(contentContainer, { required: true, elementType: 'span' }), + secondaryContent: slot(secondaryContent, { elementType: 'span' }), }; // Recalculate iconOnly to take into account secondaryContent. diff --git a/packages/react-components/react-button/src/components/MenuButton/renderMenuButton.tsx b/packages/react-components/react-button/src/components/MenuButton/renderMenuButton.tsx index ecc6cbdbbf352c..b38da2f40c36cf 100644 --- a/packages/react-components/react-button/src/components/MenuButton/renderMenuButton.tsx +++ b/packages/react-components/react-button/src/components/MenuButton/renderMenuButton.tsx @@ -3,21 +3,21 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { MenuButtonSlots, MenuButtonState } from './MenuButton.types'; /** * Renders a MenuButton component by passing the state defined props to the appropriate slots. */ export const renderMenuButton_unstable = (state: MenuButtonState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); const { icon, iconOnly } = state; return ( - - {slots.icon && } - {!iconOnly && slotProps.root.children} - {(!iconOnly || !icon?.children) && slots.menuIcon && } - + + {state.icon && } + {!iconOnly && state.root.children} + {(!iconOnly || !icon?.children) && state.menuIcon && } + ); }; diff --git a/packages/react-components/react-button/src/components/MenuButton/useMenuButton.tsx b/packages/react-components/react-button/src/components/MenuButton/useMenuButton.tsx index 72c828ff06adfd..5b3b0244508139 100644 --- a/packages/react-components/react-button/src/components/MenuButton/useMenuButton.tsx +++ b/packages/react-components/react-button/src/components/MenuButton/useMenuButton.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { ChevronDownRegular } from '@fluentui/react-icons'; -import { resolveShorthand } from '@fluentui/react-utilities'; +import { slot } from '@fluentui/react-utilities'; import { useButton_unstable } from '../Button/index'; import type { MenuButtonProps, MenuButtonState } from './MenuButton.types'; @@ -28,11 +28,12 @@ export const useMenuButton_unstable = ( menuIcon: 'span', }, - menuIcon: resolveShorthand(menuIcon, { + menuIcon: slot(menuIcon, { defaultProps: { children: , }, required: true, + elementType: 'span', }), }; }; diff --git a/packages/react-components/react-button/src/components/SplitButton/renderSplitButton.tsx b/packages/react-components/react-button/src/components/SplitButton/renderSplitButton.tsx index 25897f2ab7ec57..8bfd9572ec4043 100644 --- a/packages/react-components/react-button/src/components/SplitButton/renderSplitButton.tsx +++ b/packages/react-components/react-button/src/components/SplitButton/renderSplitButton.tsx @@ -3,19 +3,19 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { SplitButtonSlots, SplitButtonState } from './SplitButton.types'; /** * Renders a SplitButton component by passing the state defined props to the appropriate slots. */ export const renderSplitButton_unstable = (state: SplitButtonState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slots.primaryActionButton && } - {slots.menuButton && } - + + {state.primaryActionButton && } + {state.menuButton && } + ); }; diff --git a/packages/react-components/react-button/src/components/SplitButton/useSplitButton.ts b/packages/react-components/react-button/src/components/SplitButton/useSplitButton.ts index aad478a4c7df6b..f87766a722e615 100644 --- a/packages/react-components/react-button/src/components/SplitButton/useSplitButton.ts +++ b/packages/react-components/react-button/src/components/SplitButton/useSplitButton.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand, useId } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot, useId } from '@fluentui/react-utilities'; import { Button } from '../Button/Button'; import { MenuButton } from '../MenuButton/MenuButton'; import type { SplitButtonProps, SplitButtonState } from './SplitButton.types'; @@ -29,7 +29,7 @@ export const useSplitButton_unstable = ( const baseId = useId('splitButton-'); - const menuButtonShorthand = resolveShorthand(menuButton, { + const menuButtonShorthand = slot(menuButton, { defaultProps: { appearance, disabled, @@ -39,9 +39,9 @@ export const useSplitButton_unstable = ( size, }, required: true, + elementType: MenuButton, }); - - const primaryActionButtonShorthand = resolveShorthand(primaryActionButton, { + const primaryActionButtonShorthand = slot(primaryActionButton, { defaultProps: { appearance, children, @@ -54,9 +54,8 @@ export const useSplitButton_unstable = ( size, }, required: true, - }); - - // Resolve menu button's aria-labelledby to be labelled by the primary action button if not a label was not provided + elementType: Button, + }); // Resolve menu button's aria-labelledby to be labelled by the primary action button if not a label was not provided // by the user. if ( menuButtonShorthand && @@ -66,7 +65,6 @@ export const useSplitButton_unstable = ( ) { menuButtonShorthand['aria-labelledby'] = primaryActionButtonShorthand.id; } - return { // Props passed at the top-level appearance, @@ -74,16 +72,9 @@ export const useSplitButton_unstable = ( disabledFocusable, iconPosition, shape, - size, - - // Slots definition - components: { - root: 'div', - menuButton: MenuButton, - primaryActionButton: Button, - }, - - root: getNativeElementProps('div', { ref, ...props }), + size, // Slots definition + components: { root: 'div', menuButton: MenuButton, primaryActionButton: Button }, + root: slot(getNativeElementProps('div', { ref, ...props }), { required: true, elementType: 'div' }), menuButton: menuButtonShorthand, primaryActionButton: primaryActionButtonShorthand, }; diff --git a/packages/react-components/react-card/src/components/Card/renderCard.tsx b/packages/react-components/react-card/src/components/Card/renderCard.tsx index 82efe6a178a756..414c378ec9d5a7 100644 --- a/packages/react-components/react-card/src/components/Card/renderCard.tsx +++ b/packages/react-components/react-card/src/components/Card/renderCard.tsx @@ -3,7 +3,7 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { CardContextValue, CardSlots, CardState } from './Card.types'; import { CardProvider } from './CardContext'; @@ -11,15 +11,15 @@ import { CardProvider } from './CardContext'; * Render the final JSX of Card. */ export const renderCard_unstable = (state: CardState, cardContextValue: CardContextValue) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - + - {slots.checkbox ? : null} - {slots.floatingAction ? : null} - {slotProps.root.children} + {state.checkbox ? : null} + {state.floatingAction ? : null} + {state.root.children} - + ); }; diff --git a/packages/react-components/react-card/src/components/Card/useCard.ts b/packages/react-components/react-card/src/components/Card/useCard.ts index 156b30200697d6..b8583059d271ef 100644 --- a/packages/react-components/react-card/src/components/Card/useCard.ts +++ b/packages/react-components/react-card/src/components/Card/useCard.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, useMergedRefs } from '@fluentui/react-utilities'; +import { getNativeElementProps, useMergedRefs, slot } from '@fluentui/react-utilities'; import { useFocusableGroup, useFocusWithin } from '@fluentui/react-tabster'; import type { CardProps, CardState } from './Card.types'; @@ -96,13 +96,16 @@ export const useCard_unstable = (props: CardProps, ref: React.Ref setIsSelectFocused(false), ...selectableCheckboxProps, }, + elementType: 'input', }); }, [checkbox, floatingAction, isCardSelected, isSelectable, onChangeHandler, referenceId, referenceLabel]); @@ -107,10 +108,11 @@ export const useCardSelectable = ( return; } - return resolveShorthand(floatingAction, { + return slot(floatingAction, { defaultProps: { ref: checkboxRef, }, + elementType: 'div', }); }, [floatingAction]); diff --git a/packages/react-components/react-card/src/components/CardFooter/renderCardFooter.tsx b/packages/react-components/react-card/src/components/CardFooter/renderCardFooter.tsx index 2e002dbc7ec8d7..07e9e7d2a1a0cb 100644 --- a/packages/react-components/react-card/src/components/CardFooter/renderCardFooter.tsx +++ b/packages/react-components/react-card/src/components/CardFooter/renderCardFooter.tsx @@ -3,19 +3,19 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { CardFooterSlots, CardFooterState } from './CardFooter.types'; /** * Render the final JSX of CardFooter. */ export const renderCardFooter_unstable = (state: CardFooterState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slotProps.root.children} - {slots.action && } - + + {state.root.children} + {state.action && } + ); }; diff --git a/packages/react-components/react-card/src/components/CardFooter/useCardFooter.ts b/packages/react-components/react-card/src/components/CardFooter/useCardFooter.ts index da1670be343cbb..44a7cf9f7a2df6 100644 --- a/packages/react-components/react-card/src/components/CardFooter/useCardFooter.ts +++ b/packages/react-components/react-card/src/components/CardFooter/useCardFooter.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { CardFooterProps, CardFooterState } from './CardFooter.types'; /** @@ -20,10 +20,13 @@ export const useCardFooter_unstable = (props: CardFooterProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slots.image && } - - {slots.description && } - {slots.action && } - + + {state.image && } + + {state.description && } + {state.action && } + ); }; diff --git a/packages/react-components/react-card/src/components/CardHeader/useCardHeader.ts b/packages/react-components/react-card/src/components/CardHeader/useCardHeader.ts index 3f1f487923d6cf..8837e5e87a688f 100644 --- a/packages/react-components/react-card/src/components/CardHeader/useCardHeader.ts +++ b/packages/react-components/react-card/src/components/CardHeader/useCardHeader.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand, useId } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot, useId } from '@fluentui/react-utilities'; import type { CardHeaderProps, CardHeaderState } from './CardHeader.types'; import { useCardContext_unstable } from '../Card/CardContext'; import { cardHeaderClassNames } from './useCardHeaderStyles.styles'; @@ -40,19 +40,23 @@ export const useCardHeader_unstable = (props: CardHeaderProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slotProps.root.children} - {slots.logo && } - + + {state.root.children} + {state.logo && } + ); }; diff --git a/packages/react-components/react-card/src/components/CardPreview/useCardPreview.ts b/packages/react-components/react-card/src/components/CardPreview/useCardPreview.ts index 2f578992522092..094e83acb53ab0 100644 --- a/packages/react-components/react-card/src/components/CardPreview/useCardPreview.ts +++ b/packages/react-components/react-card/src/components/CardPreview/useCardPreview.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand, useMergedRefs } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot, useMergedRefs } from '@fluentui/react-utilities'; import type { CardPreviewProps, CardPreviewState } from './CardPreview.types'; import { useCardContext_unstable } from '../Card/CardContext'; import { cardPreviewClassNames } from './useCardPreviewStyles.styles'; @@ -50,10 +50,13 @@ export const useCardPreview_unstable = (props: CardPreviewProps, ref: React.Ref< logo: 'div', }, - root: getNativeElementProps('div', { - ref: previewRef, - ...props, - }), - logo: resolveShorthand(logo), + root: slot( + getNativeElementProps('div', { + ref: previewRef, + ...props, + }), + { required: true, elementType: 'div' }, + ), + logo: slot(logo, { elementType: 'div' }), }; }; diff --git a/packages/react-components/react-checkbox/src/components/Checkbox/renderCheckbox.tsx b/packages/react-components/react-checkbox/src/components/Checkbox/renderCheckbox.tsx index 7a55ab5833815f..7119628681678f 100644 --- a/packages/react-components/react-checkbox/src/components/Checkbox/renderCheckbox.tsx +++ b/packages/react-components/react-checkbox/src/components/Checkbox/renderCheckbox.tsx @@ -3,18 +3,18 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import { CheckboxState, CheckboxSlots } from './Checkbox.types'; export const renderCheckbox_unstable = (state: CheckboxState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - - {state.labelPosition === 'before' && slots.label && } - - {state.labelPosition === 'after' && slots.label && } - + + + {state.labelPosition === 'before' && state.label && } + + {state.labelPosition === 'after' && state.label && } + ); }; diff --git a/packages/react-components/react-checkbox/src/components/Checkbox/useCheckbox.tsx b/packages/react-components/react-checkbox/src/components/Checkbox/useCheckbox.tsx index 5124ad0a5fe0c6..76a6f8219c91f2 100644 --- a/packages/react-components/react-checkbox/src/components/Checkbox/useCheckbox.tsx +++ b/packages/react-components/react-checkbox/src/components/Checkbox/useCheckbox.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { useFieldControlProps_unstable } from '@fluentui/react-field'; import { getPartitionedNativeProps, - resolveShorthand, + slot, useControllableState, useEventCallback, useId, @@ -73,14 +73,15 @@ export const useCheckbox_unstable = (props: CheckboxProps, ref: React.Ref(), ...nativeProps.root, }, + elementType: 'span', }), - input: resolveShorthand(props.input, { + input: slot(props.input, { required: true, defaultProps: { type: 'checkbox', @@ -89,8 +90,9 @@ export const useCheckbox_unstable = (props: CheckboxProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - + - - {slots.expandIcon && } - {slots.listbox && + + {state.expandIcon && } + {state.listbox && (state.inlinePopup ? ( - + ) : ( - + ))} - + ); }; diff --git a/packages/react-components/react-combobox/src/components/Combobox/useCombobox.tsx b/packages/react-components/react-combobox/src/components/Combobox/useCombobox.tsx index b394426c170417..71c7082560ddf5 100644 --- a/packages/react-components/react-combobox/src/components/Combobox/useCombobox.tsx +++ b/packages/react-components/react-combobox/src/components/Combobox/useCombobox.tsx @@ -4,18 +4,18 @@ import { ArrowLeft, ArrowRight } from '@fluentui/keyboard-keys'; import { ChevronDownRegular as ChevronDownIcon } from '@fluentui/react-icons'; import { getPartitionedNativeProps, - resolveShorthand, + slot, mergeCallbacks, useEventCallback, useId, useMergedRefs, + Slot, } from '@fluentui/react-utilities'; import { getDropdownActionFromKey } from '../../utils/dropdownKeyActions'; import { useComboboxBaseState } from '../../utils/useComboboxBaseState'; import { useComboboxPopup } from '../../utils/useComboboxPopup'; import { useTriggerListboxSlots } from '../../utils/useTriggerListboxSlots'; import { Listbox } from '../Listbox/Listbox'; -import type { Slot } from '@fluentui/react-utilities'; import type { SelectionEvents } from '../../utils/Selection.types'; import type { OptionValue } from '../../utils/OptionCollection.types'; import type { ComboboxProps, ComboboxState } from './Combobox.types'; @@ -158,7 +158,7 @@ export const useCombobox_unstable = (props: ComboboxProps, ref: React.Ref; let listboxSlot: Slot | undefined; - triggerSlot = resolveShorthand(props.input, { + triggerSlot = slot(props.input, { required: true, defaultProps: { ref: useMergedRefs(props.input?.ref, triggerRef), @@ -166,54 +166,44 @@ export const useCombobox_unstable = (props: ComboboxProps, ref: React.Ref, role: 'button', }, + elementType: 'span', }), ...baseState, }; diff --git a/packages/react-components/react-combobox/src/components/Dropdown/renderDropdown.tsx b/packages/react-components/react-combobox/src/components/Dropdown/renderDropdown.tsx index 5a01a3706f128c..86b1e4d02eee15 100644 --- a/packages/react-components/react-combobox/src/components/Dropdown/renderDropdown.tsx +++ b/packages/react-components/react-combobox/src/components/Dropdown/renderDropdown.tsx @@ -5,7 +5,7 @@ import { Portal } from '@fluentui/react-portal'; import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import { ComboboxContext } from '../../contexts/ComboboxContext'; import type { DropdownContextValues, DropdownState, DropdownSlots } from './Dropdown.types'; @@ -13,24 +13,24 @@ import type { DropdownContextValues, DropdownState, DropdownSlots } from './Drop * Render the final JSX of Dropdown */ export const renderDropdown_unstable = (state: DropdownState, contextValues: DropdownContextValues) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - + - - {slotProps.button.children} - {slots.expandIcon && } - - {slots.listbox && + + {state.button.children} + {state.expandIcon && } + + {state.listbox && (state.inlinePopup ? ( - + ) : ( - + ))} - + ); }; diff --git a/packages/react-components/react-combobox/src/components/Dropdown/useDropdown.tsx b/packages/react-components/react-combobox/src/components/Dropdown/useDropdown.tsx index 29b69dec00c8f1..1684b59dc48460 100644 --- a/packages/react-components/react-combobox/src/components/Dropdown/useDropdown.tsx +++ b/packages/react-components/react-combobox/src/components/Dropdown/useDropdown.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { useFieldControlProps_unstable } from '@fluentui/react-field'; import { ChevronDownRegular as ChevronDownIcon } from '@fluentui/react-icons'; -import { getPartitionedNativeProps, mergeCallbacks, resolveShorthand, useTimeout } from '@fluentui/react-utilities'; +import { getPartitionedNativeProps, mergeCallbacks, slot, useTimeout } from '@fluentui/react-utilities'; import { getDropdownActionFromKey } from '../../utils/dropdownKeyActions'; import { useComboboxBaseState } from '../../utils/useComboboxBaseState'; import { useComboboxPopup } from '../../utils/useComboboxPopup'; @@ -108,53 +108,45 @@ export const useDropdown_unstable = (props: DropdownProps, ref: React.Ref; let listboxSlot: Slot | undefined; - triggerSlot = resolveShorthand(props.button, { + triggerSlot = slot(props.button, { required: true, defaultProps: { type: 'button', children: baseState.value || props.placeholder, ...triggerNativeProps, }, + elementType: 'button', }); - triggerSlot.onKeyDown = mergeCallbacks(onTriggerKeyDown, triggerSlot.onKeyDown); - listboxSlot = baseState.open || baseState.hasFocus - ? resolveShorthand(props.listbox, { + ? slot(props.listbox, { required: true, - defaultProps: { - children: props.children, - style: { width: popupWidth }, - }, + defaultProps: { children: props.children, style: { width: popupWidth } }, + elementType: Listbox, }) : undefined; - [triggerSlot, listboxSlot] = useComboboxPopup(props, triggerSlot, listboxSlot); [triggerSlot, listboxSlot] = useTriggerListboxSlots(props, baseState, ref, triggerSlot, listboxSlot); - const state: DropdownState = { - components: { - root: 'div', - button: 'button', - expandIcon: 'span', - listbox: Listbox, - }, - root: resolveShorthand(props.root, { + components: { root: 'div', button: 'button', expandIcon: 'span', listbox: Listbox }, + root: slot(props.root, { required: true, defaultProps: { 'aria-owns': !props.inlinePopup ? listboxSlot?.id : undefined, children: props.children, ...rootNativeProps, }, + elementType: 'div', }), button: triggerSlot, listbox: listboxSlot, - expandIcon: resolveShorthand(props.expandIcon, { + expandIcon: slot(props.expandIcon, { required: true, defaultProps: { children: , }, + elementType: 'span', }), placeholderVisible: !baseState.value && !!props.placeholder, ...baseState, diff --git a/packages/react-components/react-combobox/src/components/Listbox/renderListbox.tsx b/packages/react-components/react-combobox/src/components/Listbox/renderListbox.tsx index a4cb18ecb9a7e6..2a383014265eea 100644 --- a/packages/react-components/react-combobox/src/components/Listbox/renderListbox.tsx +++ b/packages/react-components/react-combobox/src/components/Listbox/renderListbox.tsx @@ -3,7 +3,7 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { ListboxContextValues, ListboxState, ListboxSlots } from './Listbox.types'; import { ListboxContext } from '../../contexts/ListboxContext'; @@ -11,11 +11,11 @@ import { ListboxContext } from '../../contexts/ListboxContext'; * Render the final JSX of Listbox */ export const renderListbox_unstable = (state: ListboxState, contextValues: ListboxContextValues) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - + ); }; diff --git a/packages/react-components/react-combobox/src/components/Listbox/useListbox.ts b/packages/react-components/react-combobox/src/components/Listbox/useListbox.ts index f49e1ad11e3378..ac922c55dced57 100644 --- a/packages/react-components/react-combobox/src/components/Listbox/useListbox.ts +++ b/packages/react-components/react-combobox/src/components/Listbox/useListbox.ts @@ -1,5 +1,11 @@ import * as React from 'react'; -import { getNativeElementProps, mergeCallbacks, useEventCallback, useMergedRefs } from '@fluentui/react-utilities'; +import { + getNativeElementProps, + mergeCallbacks, + useEventCallback, + useMergedRefs, + slot, +} from '@fluentui/react-utilities'; import { useContextSelector, useHasParentContext } from '@fluentui/react-context-selector'; import { getDropdownActionFromKey, getIndexFromAction } from '../../utils/dropdownKeyActions'; import type { OptionValue } from '../../utils/OptionCollection.types'; @@ -87,14 +93,17 @@ export const useListbox_unstable = (props: ListboxProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slots.checkIcon && } - {slotProps.root.children} - + + {state.checkIcon && } + {state.root.children} + ); }; diff --git a/packages/react-components/react-combobox/src/components/Option/useOption.tsx b/packages/react-components/react-combobox/src/components/Option/useOption.tsx index acc1282bffa4e8..7a861e1a5181c6 100644 --- a/packages/react-components/react-combobox/src/components/Option/useOption.tsx +++ b/packages/react-components/react-combobox/src/components/Option/useOption.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand, useId, useMergedRefs } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot, useId, useMergedRefs } from '@fluentui/react-utilities'; import { useContextSelector } from '@fluentui/react-context-selector'; import { CheckmarkFilled, Checkmark12Filled } from '@fluentui/react-icons'; import { ComboboxContext } from '../../contexts/ComboboxContext'; @@ -115,20 +115,24 @@ export const useOption_unstable = (props: OptionProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slots.label && {slotProps.label.children}} - {slotProps.root.children} - + + {state.label && {state.label.children}} + {state.root.children} + ); }; diff --git a/packages/react-components/react-combobox/src/components/OptionGroup/useOptionGroup.ts b/packages/react-components/react-combobox/src/components/OptionGroup/useOptionGroup.ts index febb5bf49c2423..9411aaa3a2f884 100644 --- a/packages/react-components/react-combobox/src/components/OptionGroup/useOptionGroup.ts +++ b/packages/react-components/react-combobox/src/components/OptionGroup/useOptionGroup.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand, useId } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot, useId } from '@fluentui/react-utilities'; import type { OptionGroupProps, OptionGroupState } from './OptionGroup.types'; /** @@ -20,17 +20,21 @@ export const useOptionGroup_unstable = (props: OptionGroupProps, ref: React.Ref< root: 'div', label: 'span', }, - root: getNativeElementProps('div', { - ref, - role: 'group', - 'aria-labelledby': label ? labelId : undefined, - ...props, - }), - label: resolveShorthand(label, { + root: slot( + getNativeElementProps('div', { + ref, + role: 'group', + 'aria-labelledby': label ? labelId : undefined, + ...props, + }), + { required: true, elementType: 'div' }, + ), + label: slot(label, { defaultProps: { id: labelId, role: 'presentation', }, + elementType: 'span', }), }; }; diff --git a/packages/react-components/react-components/etc/react-components.api.md b/packages/react-components/react-components/etc/react-components.api.md index 966d033f0de62f..da810813703e92 100644 --- a/packages/react-components/react-components/etc/react-components.api.md +++ b/packages/react-components/react-components/etc/react-components.api.md @@ -44,6 +44,7 @@ import { AccordionToggleData } from '@fluentui/react-accordion'; import { AccordionToggleEvent } from '@fluentui/react-accordion'; import { AccordionToggleEventHandler } from '@fluentui/react-accordion'; import { arrowHeights } from '@fluentui/react-popover'; +import { assertSlots } from '@fluentui/react-utilities'; import { Avatar } from '@fluentui/react-avatar'; import { avatarClassNames } from '@fluentui/react-avatar'; import { AvatarGroup } from '@fluentui/react-avatar'; @@ -1103,6 +1104,8 @@ export { AccordionToggleEventHandler } export { arrowHeights } +export { assertSlots } + export { Avatar } export { avatarClassNames } diff --git a/packages/react-components/react-components/src/index.ts b/packages/react-components/react-components/src/index.ts index e1a462d7e1d6f2..6ea3fc15462f89 100644 --- a/packages/react-components/react-components/src/index.ts +++ b/packages/react-components/react-components/src/index.ts @@ -92,8 +92,11 @@ export { getNativeElementProps, getPartitionedNativeProps, getSlots, + assertSlots, IdPrefixProvider, resetIdsForTests, + // resolveShorthand is deprecated but removing it would be a breaking change + // eslint-disable-next-line deprecation/deprecation resolveShorthand, SSRProvider, useId, @@ -106,7 +109,11 @@ export type { ComponentProps, ComponentState, ForwardRefComponent, + // ResolveShorthandFunction is deprecated but removing it would be a breaking change + // eslint-disable-next-line deprecation/deprecation ResolveShorthandFunction, + // ResolveShorthandOptions is deprecated but removing it would be a breaking change + // eslint-disable-next-line deprecation/deprecation ResolveShorthandOptions, Slot, SlotClassNames, diff --git a/packages/react-components/react-data-grid-react-window/src/components/DataGridBody/renderDataGridBody.tsx b/packages/react-components/react-data-grid-react-window/src/components/DataGridBody/renderDataGridBody.tsx index bd92417ba41bc3..9d389d4e055476 100644 --- a/packages/react-components/react-data-grid-react-window/src/components/DataGridBody/renderDataGridBody.tsx +++ b/packages/react-components/react-data-grid-react-window/src/components/DataGridBody/renderDataGridBody.tsx @@ -2,7 +2,7 @@ /** @jsx createElement */ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { DataGridBodyState, DataGridBodySlots } from './DataGridBody.types'; import { FixedSizeList as List, ListChildComponentProps } from 'react-window'; import { TableRowData, TableRowIdContextProvider } from '@fluentui/react-table'; @@ -12,10 +12,10 @@ import { TableRowIndexContextProvider } from '../../contexts/rowIndexContext'; * Render the final JSX of DataGridVirtualizedBody */ export const renderDataGridBody_unstable = (state: DataGridBodyState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - + { ); }} - + ); }; diff --git a/packages/react-components/react-datepicker-compat/src/components/DatePicker/renderDatePicker.tsx b/packages/react-components/react-datepicker-compat/src/components/DatePicker/renderDatePicker.tsx index 89b9b65545eef3..e321fb8fc33a68 100644 --- a/packages/react-components/react-datepicker-compat/src/components/DatePicker/renderDatePicker.tsx +++ b/packages/react-components/react-datepicker-compat/src/components/DatePicker/renderDatePicker.tsx @@ -5,30 +5,29 @@ import * as React from 'react'; import { createElement } from '@fluentui/react-jsx-runtime'; import { Portal } from '@fluentui/react-portal'; -import { getSlotsNext } from '@fluentui/react-utilities'; -import type { CalendarProps } from '../Calendar/Calendar.types'; +import { assertSlots } from '@fluentui/react-utilities'; import type { DatePickerSlots, DatePickerState } from './DatePicker.types'; /** * Render the final JSX of DatePicker */ export const renderDatePicker_unstable = (state: DatePickerState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); const { inlinePopup } = state; return ( <> - - {slots.popupSurface && + + {state.popupSurface && (inlinePopup ? ( - - - + + + ) : ( - - - + + + ))} diff --git a/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePicker.tsx b/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePicker.tsx index 1c1f1a2f3615eb..257edfcbbe3341 100644 --- a/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePicker.tsx +++ b/packages/react-components/react-datepicker-compat/src/components/DatePicker/useDatePicker.tsx @@ -7,7 +7,7 @@ import { defaultDatePickerStrings } from './defaults'; import { Input } from '@fluentui/react-input'; import { mergeCallbacks, - resolveShorthand, + slot, useControllableState, useId, useMergedRefs, @@ -354,7 +354,7 @@ export const useDatePicker_unstable = (props: DatePickerProps, ref: React.Ref dismissDatePickerPopup(), refs: [triggerWrapperRef, popupRef], disabled: !open, - }); - - // When the popup is opened, focus should go to the calendar. + }); // When the popup is opened, focus should go to the calendar. // In v8 this was done by focusing after the callout was positioned, but in v9 this can be simulated by using a // useEffect hook. React.useEffect(() => { if (open && !props.disabled && calendar.current) { calendar.current.focus(); } - }, [disableAutoFocus, open, props.disabled]); - - // When the popup is closed, focus should go back to the input. + }, [disableAutoFocus, open, props.disabled]); // When the popup is closed, focus should go back to the input. React.useEffect(() => { if (!open && !props.disabled) { focus(); @@ -428,7 +424,7 @@ export const useDatePicker_unstable = (props: DatePickerProps, ref: React.Ref ({ @@ -466,17 +461,10 @@ export const useDatePicker_unstable = (props: DatePickerProps, ref: React.Ref>, - popupSurface: 'div', - }, - + components: { root: Input, calendar: Calendar as React.FC>, popupSurface: 'div' }, calendar: calendarShorthand, root: rootShorthand, popupSurface: popupSurfaceShorthand, diff --git a/packages/react-components/react-dialog/src/components/DialogActions/renderDialogActions.tsx b/packages/react-components/react-dialog/src/components/DialogActions/renderDialogActions.tsx index 570ad41b87a276..a489cf2c7811de 100644 --- a/packages/react-components/react-dialog/src/components/DialogActions/renderDialogActions.tsx +++ b/packages/react-components/react-dialog/src/components/DialogActions/renderDialogActions.tsx @@ -3,15 +3,15 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { DialogActionsState, DialogActionsSlots } from './DialogActions.types'; /** * Render the final JSX of DialogActions */ export const renderDialogActions_unstable = (state: DialogActionsState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); // TODO Add additional slots in the appropriate place - return ; + return ; }; diff --git a/packages/react-components/react-dialog/src/components/DialogActions/useDialogActions.ts b/packages/react-components/react-dialog/src/components/DialogActions/useDialogActions.ts index e01526d61078da..86c046f44e136a 100644 --- a/packages/react-components/react-dialog/src/components/DialogActions/useDialogActions.ts +++ b/packages/react-components/react-dialog/src/components/DialogActions/useDialogActions.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { DialogActionsProps, DialogActionsState } from './DialogActions.types'; /** @@ -20,10 +20,13 @@ export const useDialogActions_unstable = ( components: { root: 'div', }, - root: getNativeElementProps('div', { - ref, - ...props, - }), + root: slot( + getNativeElementProps('div', { + ref, + ...props, + }), + { required: true, elementType: 'div' }, + ), position, fluid, }; diff --git a/packages/react-components/react-dialog/src/components/DialogBody/renderDialogBody.tsx b/packages/react-components/react-dialog/src/components/DialogBody/renderDialogBody.tsx index df955d0229de25..93ccdbfd464ce5 100644 --- a/packages/react-components/react-dialog/src/components/DialogBody/renderDialogBody.tsx +++ b/packages/react-components/react-dialog/src/components/DialogBody/renderDialogBody.tsx @@ -3,15 +3,15 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { DialogBodyState, DialogBodySlots } from './DialogBody.types'; /** * Render the final JSX of DialogBody */ export const renderDialogBody_unstable = (state: DialogBodyState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); // TODO Add additional slots in the appropriate place - return ; + return ; }; diff --git a/packages/react-components/react-dialog/src/components/DialogBody/useDialogBody.ts b/packages/react-components/react-dialog/src/components/DialogBody/useDialogBody.ts index d8f93a866b18a7..4aef44bc155a09 100644 --- a/packages/react-components/react-dialog/src/components/DialogBody/useDialogBody.ts +++ b/packages/react-components/react-dialog/src/components/DialogBody/useDialogBody.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { DialogBodyProps, DialogBodyState } from './DialogBody.types'; /** @@ -16,9 +16,12 @@ export const useDialogBody_unstable = (props: DialogBodyProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); - return ; + return ; }; diff --git a/packages/react-components/react-dialog/src/components/DialogContent/useDialogContent.ts b/packages/react-components/react-dialog/src/components/DialogContent/useDialogContent.ts index b188629898581a..0cb414678553b9 100644 --- a/packages/react-components/react-dialog/src/components/DialogContent/useDialogContent.ts +++ b/packages/react-components/react-dialog/src/components/DialogContent/useDialogContent.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import { DialogContentProps, DialogContentState } from './DialogContent.types'; /** @@ -19,9 +19,12 @@ export const useDialogContent_unstable = ( components: { root: 'div', }, - root: getNativeElementProps(props.as ?? 'div', { - ref, - ...props, - }), + root: slot( + getNativeElementProps(props.as ?? 'div', { + ref, + ...props, + }), + { required: true, elementType: 'div' }, + ), }; }; diff --git a/packages/react-components/react-dialog/src/components/DialogSurface/renderDialogSurface.tsx b/packages/react-components/react-dialog/src/components/DialogSurface/renderDialogSurface.tsx index 76721af33f01a8..c217a9c15ea5ea 100644 --- a/packages/react-components/react-dialog/src/components/DialogSurface/renderDialogSurface.tsx +++ b/packages/react-components/react-dialog/src/components/DialogSurface/renderDialogSurface.tsx @@ -3,7 +3,7 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { DialogSurfaceState, DialogSurfaceSlots, DialogSurfaceContextValues } from './DialogSurface.types'; import { DialogSurfaceProvider } from '../../contexts'; import { Portal } from '@fluentui/react-portal'; @@ -12,13 +12,13 @@ import { Portal } from '@fluentui/react-portal'; * Render the final JSX of DialogSurface */ export const renderDialogSurface_unstable = (state: DialogSurfaceState, contextValues: DialogSurfaceContextValues) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - {slots.backdrop && } + {state.backdrop && } - + ); diff --git a/packages/react-components/react-dialog/src/components/DialogSurface/useDialogSurface.ts b/packages/react-components/react-dialog/src/components/DialogSurface/useDialogSurface.ts index 3cc9418d841403..045b83c139c84f 100644 --- a/packages/react-components/react-dialog/src/components/DialogSurface/useDialogSurface.ts +++ b/packages/react-components/react-dialog/src/components/DialogSurface/useDialogSurface.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { getNativeElementProps, - resolveShorthand, + slot, useEventCallback, useMergedRefs, isResolvedShorthand, @@ -64,22 +64,26 @@ export const useDialogSurface_unstable = ( backdrop: 'div', root: 'div', }, - backdrop: resolveShorthand(backdrop, { + backdrop: slot(backdrop, { required: open && modalType !== 'non-modal', defaultProps: { 'aria-hidden': 'true', - onClick: handledBackdropClick, + onClick: handledBackdropClick, // FIXME: this should be an override }, + elementType: 'div', }), - root: getNativeElementProps(as ?? 'div', { - tabIndex: -1, // https://github.com/microsoft/fluentui/issues/25150 - 'aria-modal': modalType !== 'non-modal', - role: modalType === 'alert' ? 'alertdialog' : 'dialog', - 'aria-labelledby': props['aria-label'] ? undefined : dialogTitleID, - ...props, - ...modalAttributes, - onKeyDown: handleKeyDown, - ref: useMergedRefs(ref, dialogRef), - }), + root: slot( + getNativeElementProps(as ?? 'div', { + tabIndex: -1, // https://github.com/microsoft/fluentui/issues/25150 + 'aria-modal': modalType !== 'non-modal', + role: modalType === 'alert' ? 'alertdialog' : 'dialog', + 'aria-labelledby': props['aria-label'] ? undefined : dialogTitleID, + ...props, + ...modalAttributes, + onKeyDown: handleKeyDown, + ref: useMergedRefs(ref, dialogRef), + }), + { required: true, elementType: 'div' }, + ), }; }; diff --git a/packages/react-components/react-dialog/src/components/DialogTitle/renderDialogTitle.tsx b/packages/react-components/react-dialog/src/components/DialogTitle/renderDialogTitle.tsx index bc592e6091c3b5..b10e3822fffdd9 100644 --- a/packages/react-components/react-dialog/src/components/DialogTitle/renderDialogTitle.tsx +++ b/packages/react-components/react-dialog/src/components/DialogTitle/renderDialogTitle.tsx @@ -4,19 +4,19 @@ import { createElement, Fragment } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { DialogTitleState, DialogTitleSlots } from './DialogTitle.types'; /** * Render the final JSX of DialogTitle */ export const renderDialogTitle_unstable = (state: DialogTitleState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( <> - {slotProps.root.children} - {slots.action && } + {state.root.children} + {state.action && } ); }; diff --git a/packages/react-components/react-dialog/src/components/DialogTitle/useDialogTitle.tsx b/packages/react-components/react-dialog/src/components/DialogTitle/useDialogTitle.tsx index 8d9a7beb97c8f5..53c818fd4ae5d3 100644 --- a/packages/react-components/react-dialog/src/components/DialogTitle/useDialogTitle.tsx +++ b/packages/react-components/react-dialog/src/components/DialogTitle/useDialogTitle.tsx @@ -1,9 +1,8 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { DialogTitleProps, DialogTitleState } from './DialogTitle.types'; import { useDialogContext_unstable } from '../../contexts/dialogContext'; import { Dismiss20Regular } from '@fluentui/react-icons'; -import { resolveShorthand } from '@fluentui/react-utilities'; import { DialogTrigger } from '../DialogTrigger/DialogTrigger'; import { useDialogTitleInternalStyles } from './useDialogTitleStyles.styles'; @@ -26,12 +25,15 @@ export const useDialogTitle_unstable = (props: DialogTitleProps, ref: React.Ref< root: 'h2', action: 'div', }, - root: getNativeElementProps(as ?? 'h2', { - ref, - id: useDialogContext_unstable(ctx => ctx.dialogTitleId), - ...props, - }), - action: resolveShorthand(action, { + root: slot( + getNativeElementProps(as ?? 'h2', { + ref, + id: useDialogContext_unstable(ctx => ctx.dialogTitleId), + ...props, + }), + { required: true, elementType: 'h2' }, + ), + action: slot(action, { required: modalType === 'non-modal', defaultProps: { children: ( @@ -47,6 +49,7 @@ export const useDialogTitle_unstable = (props: DialogTitleProps, ref: React.Ref< ), }, + elementType: 'div', }), }; }; diff --git a/packages/react-components/react-divider/src/components/Divider/renderDivider.tsx b/packages/react-components/react-divider/src/components/Divider/renderDivider.tsx index cc176067477f5e..0507acb784302a 100644 --- a/packages/react-components/react-divider/src/components/Divider/renderDivider.tsx +++ b/packages/react-components/react-divider/src/components/Divider/renderDivider.tsx @@ -3,19 +3,15 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import { DividerSlots, DividerState } from './Divider.types'; /** * Renders a Divider component by passing the slot props (defined in `state`) to the appropriate slots. */ export const renderDivider_unstable = (state: DividerState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slotProps.root.children !== undefined && ( - {slotProps.root.children} - )} - + {state.root.children !== undefined && {state.root.children}} ); }; diff --git a/packages/react-components/react-divider/src/components/Divider/useDivider.ts b/packages/react-components/react-divider/src/components/Divider/useDivider.ts index e6db7eed2ed479..af0a244f15bd52 100644 --- a/packages/react-components/react-divider/src/components/Divider/useDivider.ts +++ b/packages/react-components/react-divider/src/components/Divider/useDivider.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand, useId } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot, useId } from '@fluentui/react-utilities'; import type { DividerProps, DividerState } from './Divider.types'; /** @@ -24,19 +24,23 @@ export const useDivider_unstable = (props: DividerProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); - return ; + return ; }; diff --git a/packages/react-components/react-drawer/src/components/Drawer/useDrawer.ts b/packages/react-components/react-drawer/src/components/Drawer/useDrawer.ts index 1e0c7ce6a6e24e..7efb44a86c1c48 100644 --- a/packages/react-components/react-drawer/src/components/Drawer/useDrawer.ts +++ b/packages/react-components/react-drawer/src/components/Drawer/useDrawer.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { resolveShorthand } from '@fluentui/react-utilities'; +import { slot } from '@fluentui/react-utilities'; import type { DrawerProps, DrawerState } from './Drawer.types'; import { DrawerOverlay } from '../DrawerOverlay/DrawerOverlay'; @@ -22,11 +22,12 @@ export const useDrawer_unstable = (props: DrawerProps, ref: React.Ref { - const { slots, slotProps } = getSlots(state); + assertSlots(state); - return ; + return ; }; diff --git a/packages/react-components/react-drawer/src/components/DrawerBody/useDrawerBody.ts b/packages/react-components/react-drawer/src/components/DrawerBody/useDrawerBody.ts index 9ec54bd516fdd5..feb23c9aba611d 100644 --- a/packages/react-components/react-drawer/src/components/DrawerBody/useDrawerBody.ts +++ b/packages/react-components/react-drawer/src/components/DrawerBody/useDrawerBody.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { DrawerBodyProps, DrawerBodyState } from './DrawerBody.types'; /** @@ -17,9 +17,12 @@ export const useDrawerBody_unstable = (props: DrawerBodyProps, ref: React.Ref { - const { slots, slotProps } = getSlots(state); + assertSlots(state); - return ; + return ; }; diff --git a/packages/react-components/react-drawer/src/components/DrawerFooter/useDrawerFooter.ts b/packages/react-components/react-drawer/src/components/DrawerFooter/useDrawerFooter.ts index a2181d5c138932..fd022aad861e57 100644 --- a/packages/react-components/react-drawer/src/components/DrawerFooter/useDrawerFooter.ts +++ b/packages/react-components/react-drawer/src/components/DrawerFooter/useDrawerFooter.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { DrawerFooterProps, DrawerFooterState } from './DrawerFooter.types'; /** @@ -17,9 +17,12 @@ export const useDrawerFooter_unstable = (props: DrawerFooterProps, ref: React.Re root: 'footer', }, - root: getNativeElementProps('footer', { - ref, - ...props, - }), + root: slot( + getNativeElementProps('footer', { + ref, + ...props, + }), + { required: true, elementType: 'footer' }, + ), }; }; diff --git a/packages/react-components/react-drawer/src/components/DrawerHeader/renderDrawerHeader.tsx b/packages/react-components/react-drawer/src/components/DrawerHeader/renderDrawerHeader.tsx index abbad67ac71f59..4d32df95be0f31 100644 --- a/packages/react-components/react-drawer/src/components/DrawerHeader/renderDrawerHeader.tsx +++ b/packages/react-components/react-drawer/src/components/DrawerHeader/renderDrawerHeader.tsx @@ -1,12 +1,15 @@ -import * as React from 'react'; -import { getSlots } from '@fluentui/react-utilities'; +/** @jsxRuntime classic */ +/** @jsx createElement */ + +import { createElement } from '@fluentui/react-jsx-runtime'; +import { assertSlots } from '@fluentui/react-utilities'; import type { DrawerHeaderState, DrawerHeaderSlots } from './DrawerHeader.types'; /** * Render the final JSX of DrawerHeader */ export const renderDrawerHeader_unstable = (state: DrawerHeaderState) => { - const { slots, slotProps } = getSlots(state); + assertSlots(state); - return ; + return ; }; diff --git a/packages/react-components/react-drawer/src/components/DrawerHeader/useDrawerHeader.ts b/packages/react-components/react-drawer/src/components/DrawerHeader/useDrawerHeader.ts index 76eb8a39308422..997cec8a4fa9eb 100644 --- a/packages/react-components/react-drawer/src/components/DrawerHeader/useDrawerHeader.ts +++ b/packages/react-components/react-drawer/src/components/DrawerHeader/useDrawerHeader.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { DrawerHeaderProps, DrawerHeaderState } from './DrawerHeader.types'; /** @@ -17,9 +17,12 @@ export const useDrawerHeader_unstable = (props: DrawerHeaderProps, ref: React.Re root: 'header', }, - root: getNativeElementProps('header', { - ref, - ...props, - }), + root: slot( + getNativeElementProps('header', { + ref, + ...props, + }), + { required: true, elementType: 'header' }, + ), }; }; diff --git a/packages/react-components/react-drawer/src/components/DrawerHeaderNavigation/renderDrawerHeaderNavigation.tsx b/packages/react-components/react-drawer/src/components/DrawerHeaderNavigation/renderDrawerHeaderNavigation.tsx index 9f9d5bca765580..9a28e88bb55610 100644 --- a/packages/react-components/react-drawer/src/components/DrawerHeaderNavigation/renderDrawerHeaderNavigation.tsx +++ b/packages/react-components/react-drawer/src/components/DrawerHeaderNavigation/renderDrawerHeaderNavigation.tsx @@ -1,12 +1,15 @@ -import * as React from 'react'; -import { getSlots } from '@fluentui/react-utilities'; +/** @jsxRuntime classic */ +/** @jsx createElement */ + +import { createElement } from '@fluentui/react-jsx-runtime'; +import { assertSlots } from '@fluentui/react-utilities'; import type { DrawerHeaderNavigationState, DrawerHeaderNavigationSlots } from './DrawerHeaderNavigation.types'; /** * Render the final JSX of DrawerHeaderNavigation */ export const renderDrawerHeaderNavigation_unstable = (state: DrawerHeaderNavigationState) => { - const { slots, slotProps } = getSlots(state); + assertSlots(state); - return ; + return ; }; diff --git a/packages/react-components/react-drawer/src/components/DrawerHeaderNavigation/useDrawerHeaderNavigation.ts b/packages/react-components/react-drawer/src/components/DrawerHeaderNavigation/useDrawerHeaderNavigation.ts index 4d8470d3d438cf..2162d706ef268d 100644 --- a/packages/react-components/react-drawer/src/components/DrawerHeaderNavigation/useDrawerHeaderNavigation.ts +++ b/packages/react-components/react-drawer/src/components/DrawerHeaderNavigation/useDrawerHeaderNavigation.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { DrawerHeaderNavigationProps, DrawerHeaderNavigationState } from './DrawerHeaderNavigation.types'; /** @@ -20,9 +20,12 @@ export const useDrawerHeaderNavigation_unstable = ( root: 'nav', }, - root: getNativeElementProps('nav', { - ref, - ...props, - }), + root: slot( + getNativeElementProps('nav', { + ref, + ...props, + }), + { required: true, elementType: 'nav' }, + ), }; }; diff --git a/packages/react-components/react-drawer/src/components/DrawerHeaderTitle/renderDrawerHeaderTitle.tsx b/packages/react-components/react-drawer/src/components/DrawerHeaderTitle/renderDrawerHeaderTitle.tsx index ddc27dbbc3628a..9cb34f46c0e55f 100644 --- a/packages/react-components/react-drawer/src/components/DrawerHeaderTitle/renderDrawerHeaderTitle.tsx +++ b/packages/react-components/react-drawer/src/components/DrawerHeaderTitle/renderDrawerHeaderTitle.tsx @@ -1,17 +1,20 @@ -import * as React from 'react'; -import { getSlots } from '@fluentui/react-utilities'; +/** @jsxRuntime classic */ +/** @jsx createElement */ + +import { createElement } from '@fluentui/react-jsx-runtime'; +import { assertSlots } from '@fluentui/react-utilities'; import type { DrawerHeaderTitleState, DrawerHeaderTitleSlots } from './DrawerHeaderTitle.types'; /** * Render the final JSX of DrawerHeaderTitle */ export const renderDrawerHeaderTitle_unstable = (state: DrawerHeaderTitleState) => { - const { slots, slotProps } = getSlots(state); + assertSlots(state); return ( - - {slots.heading && } - {slots.action && } - + + {state.heading && } + {state.action && } + ); }; diff --git a/packages/react-components/react-drawer/src/components/DrawerHeaderTitle/useDrawerHeaderTitle.ts b/packages/react-components/react-drawer/src/components/DrawerHeaderTitle/useDrawerHeaderTitle.ts index 71c7a5f761f645..913a9f82913eec 100644 --- a/packages/react-components/react-drawer/src/components/DrawerHeaderTitle/useDrawerHeaderTitle.ts +++ b/packages/react-components/react-drawer/src/components/DrawerHeaderTitle/useDrawerHeaderTitle.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { DrawerHeaderTitleProps, DrawerHeaderTitleState } from './DrawerHeaderTitle.types'; import { useDialogTitle_unstable } from '@fluentui/react-dialog'; @@ -16,6 +16,7 @@ export const useDrawerHeaderTitle_unstable = ( props: DrawerHeaderTitleProps, ref: React.Ref, ): DrawerHeaderTitleState => { + // eslint-disable-next-line deprecation/deprecation const { root: heading, action, components: titleComponents } = useDialogTitle_unstable(props, ref); return { @@ -25,19 +26,24 @@ export const useDrawerHeaderTitle_unstable = ( action: titleComponents.action, }, - root: getNativeElementProps('div', { - ref, - ...props, - }), - heading: resolveShorthand(props.heading, { + root: slot( + getNativeElementProps('div', { + ref, + ...props, + }), + { required: true, elementType: 'div' }, + ), + heading: slot(props.heading, { required: true, defaultProps: { ...heading, className: undefined, // remove className from heading }, + elementType: titleComponents.root, }), - action: resolveShorthand(props.action, { + action: slot(props.action, { defaultProps: action, + elementType: titleComponents.action, }), }; }; diff --git a/packages/react-components/react-drawer/src/components/DrawerHeaderTitle/useDrawerHeaderTitleStyles.styles.ts b/packages/react-components/react-drawer/src/components/DrawerHeaderTitle/useDrawerHeaderTitleStyles.styles.ts index e144fe41bc74d6..0d6bb1b24afcc0 100644 --- a/packages/react-components/react-drawer/src/components/DrawerHeaderTitle/useDrawerHeaderTitleStyles.styles.ts +++ b/packages/react-components/react-drawer/src/components/DrawerHeaderTitle/useDrawerHeaderTitleStyles.styles.ts @@ -32,6 +32,7 @@ const useStyles = makeStyles({ export const useDrawerHeaderTitleStyles_unstable = (state: DrawerHeaderTitleState): DrawerHeaderTitleState => { const styles = useStyles(); + // eslint-disable-next-line deprecation/deprecation const { heading: root = {}, action, components } = state; useDialogTitleStyles_unstable({ diff --git a/packages/react-components/react-drawer/src/components/DrawerInline/renderDrawerInline.tsx b/packages/react-components/react-drawer/src/components/DrawerInline/renderDrawerInline.tsx index 22dc8d0a3c5099..d933b37b306e38 100644 --- a/packages/react-components/react-drawer/src/components/DrawerInline/renderDrawerInline.tsx +++ b/packages/react-components/react-drawer/src/components/DrawerInline/renderDrawerInline.tsx @@ -1,16 +1,19 @@ -import * as React from 'react'; -import { getSlots } from '@fluentui/react-utilities'; +/** @jsxRuntime classic */ +/** @jsx createElement */ + +import { createElement } from '@fluentui/react-jsx-runtime'; +import { assertSlots } from '@fluentui/react-utilities'; import type { DrawerInlineState, DrawerInlineSlots } from './DrawerInline.types'; /** * Render the final JSX of DrawerInline */ export const renderDrawerInline_unstable = (state: DrawerInlineState) => { - const { slots, slotProps } = getSlots(state); + assertSlots(state); if (!state.open) { return null; } - return ; + return ; }; diff --git a/packages/react-components/react-drawer/src/components/DrawerInline/useDrawerInline.ts b/packages/react-components/react-drawer/src/components/DrawerInline/useDrawerInline.ts index 34ea024fe5fdd2..802771f7be826d 100644 --- a/packages/react-components/react-drawer/src/components/DrawerInline/useDrawerInline.ts +++ b/packages/react-components/react-drawer/src/components/DrawerInline/useDrawerInline.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, useControllableState } from '@fluentui/react-utilities'; +import { getNativeElementProps, useControllableState, slot } from '@fluentui/react-utilities'; import type { DrawerInlineProps, DrawerInlineState } from './DrawerInline.types'; import { getDefaultDrawerProps } from '../../util/getDefaultDrawerProps'; @@ -27,10 +27,13 @@ export const useDrawerInline_unstable = (props: DrawerInlineProps, ref: React.Re root: 'div', }, - root: getNativeElementProps('div', { - ref, - ...props, - }), + root: slot( + getNativeElementProps('div', { + ref, + ...props, + }), + { required: true, elementType: 'div' }, + ), size, position, diff --git a/packages/react-components/react-drawer/src/components/DrawerOverlay/renderDrawerOverlay.tsx b/packages/react-components/react-drawer/src/components/DrawerOverlay/renderDrawerOverlay.tsx index 7638ecfa34b13a..4b81eeac2136b3 100644 --- a/packages/react-components/react-drawer/src/components/DrawerOverlay/renderDrawerOverlay.tsx +++ b/packages/react-components/react-drawer/src/components/DrawerOverlay/renderDrawerOverlay.tsx @@ -1,5 +1,9 @@ -import * as React from 'react'; -import { getSlots } from '@fluentui/react-utilities'; +/** @jsxRuntime classic */ +/** @jsx createElement */ + +import { createElement } from '@fluentui/react-jsx-runtime'; + +import { assertSlots } from '@fluentui/react-utilities'; import type { DrawerOverlayState, DrawerOverlaySlots } from './DrawerOverlay.types'; import { Dialog } from '@fluentui/react-dialog'; @@ -7,11 +11,11 @@ import { Dialog } from '@fluentui/react-dialog'; * Render the final JSX of DrawerOverlay */ export const renderDrawerOverlay_unstable = (state: DrawerOverlayState) => { - const { slots, slotProps } = getSlots(state); + assertSlots(state); return ( - + ); }; diff --git a/packages/react-components/react-drawer/src/components/DrawerOverlay/useDrawerOverlay.ts b/packages/react-components/react-drawer/src/components/DrawerOverlay/useDrawerOverlay.ts index 8f13d0447a4e5a..7ab9d24b21b5bd 100644 --- a/packages/react-components/react-drawer/src/components/DrawerOverlay/useDrawerOverlay.ts +++ b/packages/react-components/react-drawer/src/components/DrawerOverlay/useDrawerOverlay.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { DrawerOverlayProps, DrawerOverlayState } from './DrawerOverlay.types'; import { DialogProps, DialogSurface } from '@fluentui/react-dialog'; import { getDefaultDrawerProps } from '../../util/getDefaultDrawerProps'; @@ -25,10 +25,13 @@ export const useDrawerOverlay_unstable = ( root: DialogSurface, }, - root: getNativeElementProps('div', { - ref, - ...props, - }), + root: slot( + getNativeElementProps('div', { + ref, + ...props, + }), + { required: true, elementType: DialogSurface }, + ), dialog: { open, defaultOpen, diff --git a/packages/react-components/react-field/src/components/Field/renderField.tsx b/packages/react-components/react-field/src/components/Field/renderField.tsx index 668687b69580a6..2851747c97f7de 100644 --- a/packages/react-components/react-field/src/components/Field/renderField.tsx +++ b/packages/react-components/react-field/src/components/Field/renderField.tsx @@ -3,7 +3,7 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import { FieldContextProvider, getFieldControlProps } from '../../contexts/index'; import type { FieldContextValues, FieldSlots, FieldState } from './Field.types'; @@ -11,7 +11,7 @@ import type { FieldContextValues, FieldSlots, FieldState } from './Field.types'; * Render the final JSX of Field */ export const renderField_unstable = (state: FieldState, contextValues: FieldContextValues) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); let { children } = state; if (typeof children === 'function') { @@ -20,17 +20,18 @@ export const renderField_unstable = (state: FieldState, contextValues: FieldCont return ( - - {slots.label && } + + {state.label && } {children} - {slots.validationMessage && ( - - {slots.validationMessageIcon && } - {slotProps.validationMessage.children} - + {state.validationMessage && ( + + {state.validationMessageIcon && } + {state.validationMessage.children} + )} - {slots.hint && } - + + {state.hint && } + ); }; diff --git a/packages/react-components/react-field/src/components/Field/useField.tsx b/packages/react-components/react-field/src/components/Field/useField.tsx index b0b5642d2c78f0..0f6224fc9d0b06 100644 --- a/packages/react-components/react-field/src/components/Field/useField.tsx +++ b/packages/react-components/react-field/src/components/Field/useField.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { CheckmarkCircle12Filled, ErrorCircle12Filled, Warning12Filled } from '@fluentui/react-icons'; import { Label } from '@fluentui/react-label'; -import { getNativeElementProps, resolveShorthand, useId } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot, useId } from '@fluentui/react-utilities'; import type { FieldProps, FieldState } from './Field.types'; const validationMessageIcons = { @@ -33,38 +33,25 @@ export const useField_unstable = (props: FieldProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); - return ; + return ; }; diff --git a/packages/react-components/react-image/src/components/Image/useImage.ts b/packages/react-components/react-image/src/components/Image/useImage.ts index 0dfe16bd2e7294..e74c59f639f9e2 100644 --- a/packages/react-components/react-image/src/components/Image/useImage.ts +++ b/packages/react-components/react-image/src/components/Image/useImage.ts @@ -1,6 +1,6 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; -import type { ImageProps, ImageState } from './Image.types'; +import { ExtractSlotProps, getNativeElementProps, slot } from '@fluentui/react-utilities'; +import type { ImageProps, ImageSlots, ImageState } from './Image.types'; /** * Given user props, returns state and render function for an Image. @@ -17,10 +17,13 @@ export const useImage_unstable = (props: ImageProps, ref: React.Ref>('img', { + ref, + ...props, + }), + { required: true, elementType: 'img' }, + ), }; return state; diff --git a/packages/react-components/react-infobutton/src/components/InfoButton/renderInfoButton.tsx b/packages/react-components/react-infobutton/src/components/InfoButton/renderInfoButton.tsx index 86a27d7306e0b9..a618f81365517c 100644 --- a/packages/react-components/react-infobutton/src/components/InfoButton/renderInfoButton.tsx +++ b/packages/react-components/react-infobutton/src/components/InfoButton/renderInfoButton.tsx @@ -3,23 +3,22 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import { PopoverTrigger } from '@fluentui/react-popover'; -import type { PopoverProps } from '@fluentui/react-popover'; import type { InfoButtonState, InfoButtonSlots } from './InfoButton.types'; /** * Render the final JSX of InfoButton */ export const renderInfoButton_unstable = (state: InfoButtonState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - + - + - - + + ); }; diff --git a/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButton.tsx b/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButton.tsx index 4acba3a4f1aa44..8562437daca070 100644 --- a/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButton.tsx +++ b/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButton.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { DefaultInfoButtonIcon12, DefaultInfoButtonIcon16, DefaultInfoButtonIcon20 } from './DefaultInfoButtonIcons'; -import { getNativeElementProps, mergeCallbacks, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, mergeCallbacks } from '@fluentui/react-utilities'; import { Popover, PopoverSurface } from '@fluentui/react-popover'; -import { useControllableState } from '@fluentui/react-utilities'; +import { useControllableState, slot } from '@fluentui/react-utilities'; import type { InfoButtonProps, InfoButtonState } from './InfoButton.types'; import type { PopoverProps } from '@fluentui/react-popover'; @@ -39,27 +39,32 @@ export const useInfoButton_unstable = (props: InfoButtonProps, ref: React.Ref>, }), - info: resolveShorthand(props.info, { + info: slot(props.info, { required: true, defaultProps: { role: 'note', tabIndex: -1, }, + elementType: PopoverSurface, }), }; diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx b/packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx index 11df0bd9ca6aba..404853ed51e79c 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx @@ -3,19 +3,19 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { InfoLabelSlots, InfoLabelState } from './InfoLabel.types'; /** * Render the final JSX of InfoLabel */ export const renderInfoLabel_unstable = (state: InfoLabelState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - - {slots.infoButton && } - + + + {state.infoButton && } + ); }; diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts index 545442eb6a0e8f..601f040b11aa9a 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts @@ -1,9 +1,10 @@ import * as React from 'react'; import { Label } from '@fluentui/react-label'; -import { resolveShorthand, useId } from '@fluentui/react-utilities'; +import { slot, useId } from '@fluentui/react-utilities'; import { InfoButton } from '../InfoButton/InfoButton'; import type { InfoLabelProps, InfoLabelState } from './InfoLabel.types'; +import { PopoverSurface } from '@fluentui/react-popover'; /** * Create the state required to render InfoLabel. @@ -27,15 +28,16 @@ export const useInfoLabel_unstable = (props: InfoLabelProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slots.contentBefore && } - - {slots.contentAfter && } - + + {state.contentBefore && } + + {state.contentAfter && } + ); }; diff --git a/packages/react-components/react-input/src/components/Input/useInput.ts b/packages/react-components/react-input/src/components/Input/useInput.ts index f5b425d4f56b3a..02bb3c9566be6f 100644 --- a/packages/react-components/react-input/src/components/Input/useInput.ts +++ b/packages/react-components/react-input/src/components/Input/useInput.ts @@ -1,11 +1,6 @@ import * as React from 'react'; import { useFieldControlProps_unstable } from '@fluentui/react-field'; -import { - getPartitionedNativeProps, - resolveShorthand, - useControllableState, - useEventCallback, -} from '@fluentui/react-utilities'; +import { getPartitionedNativeProps, slot, useControllableState, useEventCallback } from '@fluentui/react-utilities'; import type { InputProps, InputState } from './Input.types'; import { useOverrides_unstable as useOverrides } from '@fluentui/react-shared-contexts'; @@ -57,19 +52,21 @@ export const useInput_unstable = (props: InputProps, ref: React.Ref { +describe('createElement with assertSlots', () => { describe('general behavior tests', () => { it('handles a string', () => { const result = render(
Hello world
); @@ -67,21 +65,23 @@ describe('createElement', () => { describe('custom behavior tests', () => { it('keeps children from "defaultProps" in a render callback', () => { - type TestComponentSlots = { slot: Slot<'div'> }; - type TestComponentState = ComponentState; + type TestComponentSlots = { + someSlot: NonNullable>; + }; type TestComponentProps = ComponentProps>; + type TestComponentState = ComponentState; const TestComponent = (props: TestComponentProps) => { const state: TestComponentState = { - components: { slot: 'div' }, - - slot: resolveShorthand(props.slot, { + components: { someSlot: 'div' }, + someSlot: slot(props.someSlot, { + required: true, + elementType: 'div', defaultProps: { children: 'Default Children', id: 'slot' }, }), }; - const { slots, slotProps } = getSlotsNext(state); - - return ; + assertSlots(state); + return ; }; const children = jest.fn().mockImplementation((Component, props) => ( @@ -89,7 +89,7 @@ describe('createElement', () => { )); - const result = render(); + const result = render(); expect(children).toHaveBeenCalledTimes(1); expect(children).toHaveBeenCalledWith('div', { children: 'Default Children', id: 'slot' }); @@ -108,23 +108,21 @@ describe('createElement', () => { }); it('keeps children from a render template in a render callback', () => { - type TestComponentSlots = { outer: Slot<'div'>; inner: Slot<'div'> }; + type TestComponentSlots = { outer: NonNullable>; inner: NonNullable> }; type TestComponentState = ComponentState; type TestComponentProps = ComponentProps>; const TestComponent = (props: TestComponentProps) => { const state: TestComponentState = { - components: { inner: 'div', outer: 'div' }, - - inner: resolveShorthand(props.inner, { defaultProps: { id: 'inner' } }), - outer: resolveShorthand(props.outer, { defaultProps: { id: 'outer' } }), + components: { outer: 'div', inner: 'div' }, + inner: slot(props.inner, { required: true, defaultProps: { id: 'inner' }, elementType: 'div' }), + outer: slot(props.outer, { required: true, defaultProps: { id: 'outer' }, elementType: 'div' }), }; - const { slots, slotProps } = getSlotsNext(state); - + assertSlots(state); return ( - - - + + + ); }; @@ -164,5 +162,29 @@ describe('createElement', () => { `); }); + + it("should support 'as' property to opt-out of base element type", () => { + type TestComponentSlots = { slot: NonNullable> }; + type TestComponentState = ComponentState; + type TestComponentProps = ComponentProps>; + + const TestComponent = (props: TestComponentProps) => { + const state: TestComponentState = { + components: { slot: 'div' }, + slot: slot(props.slot, { + required: true, + elementType: 'div', + }), + }; + assertSlots(state); + return ; + }; + + const result = render(); + + expect(result.container.firstChild).toMatchInlineSnapshot(``); + }); + + it.todo("should pass 'as' property to base element that aren't html element"); }); }); diff --git a/packages/react-components/react-jsx-runtime/src/createElement.ts b/packages/react-components/react-jsx-runtime/src/createElement.ts index 368920c38b5645..c089e2a438de28 100644 --- a/packages/react-components/react-jsx-runtime/src/createElement.ts +++ b/packages/react-components/react-jsx-runtime/src/createElement.ts @@ -1,38 +1,45 @@ import * as React from 'react'; -import { SlotRenderFunction, UnknownSlotProps, SLOT_RENDER_FUNCTION_SYMBOL } from '@fluentui/react-utilities'; - -type WithMetadata = Props & { - [SLOT_RENDER_FUNCTION_SYMBOL]: SlotRenderFunction; -}; +import { UnknownSlotProps, isSlot, SlotComponent, SLOT_COMPONENT_METADATA_SYMBOL } from '@fluentui/react-utilities'; export function createElement

( type: React.ElementType

, props?: P | null, ...children: React.ReactNode[] ): React.ReactElement

| null { - return hasRenderFunction(props) - ? createElementFromRenderFunction(type, props, children) - : React.createElement(type, props, ...children); + if (isSlot

(type)) { + return createElementFromSlotComponent(type, children); + } + return React.createElement(type, props, ...children); } -function createElementFromRenderFunction

( - type: React.ElementType

, - props: WithMetadata

, +function createElementFromSlotComponent( + slotComponent: SlotComponent, overrideChildren: React.ReactNode[], -): React.ReactElement

| null { - const { [SLOT_RENDER_FUNCTION_SYMBOL]: renderFunction, ...renderProps } = props; +): React.ReactElement | null { + const { [SLOT_COMPONENT_METADATA_SYMBOL]: metadata, as, ...propsWithoutMetadata } = slotComponent; + const props = propsWithoutMetadata as UnknownSlotProps as Props; + const { elementType: baseElementType, renderFunction } = metadata; - if (overrideChildren.length > 0) { - renderProps.children = React.createElement(React.Fragment, {}, ...overrideChildren); + const elementType = + baseElementType === undefined || typeof baseElementType === 'string' + ? as ?? baseElementType ?? 'div' + : baseElementType; + + if (typeof elementType !== 'string' && as) { + props.as = as; } - return React.createElement( - React.Fragment, - {}, - renderFunction(type, renderProps as UnknownSlotProps as P), - ) as React.ReactElement

; -} + if (renderFunction) { + if (overrideChildren.length > 0) { + props.children = React.createElement(React.Fragment, {}, ...overrideChildren); + } + + return React.createElement( + React.Fragment, + {}, + renderFunction(elementType as React.ElementType, props), + ) as React.ReactElement; + } -export function hasRenderFunction(props?: Props | null): props is WithMetadata { - return Boolean(props?.hasOwnProperty(SLOT_RENDER_FUNCTION_SYMBOL)); + return React.createElement(elementType, props, ...overrideChildren); } diff --git a/packages/react-components/react-label/src/components/Label/renderLabel.tsx b/packages/react-components/react-label/src/components/Label/renderLabel.tsx index 3dd9889ba0495f..8432c3f351fc9a 100644 --- a/packages/react-components/react-label/src/components/Label/renderLabel.tsx +++ b/packages/react-components/react-label/src/components/Label/renderLabel.tsx @@ -3,19 +3,19 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { LabelState, LabelSlots } from './Label.types'; /** * Render the final JSX of Label */ export const renderLabel_unstable = (state: LabelState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - + {state.root.children} - {slots.required && } - + {state.required && } + ); }; diff --git a/packages/react-components/react-label/src/components/Label/useLabel.tsx b/packages/react-components/react-label/src/components/Label/useLabel.tsx index 3efd2b9504b939..f097ae693da253 100644 --- a/packages/react-components/react-label/src/components/Label/useLabel.tsx +++ b/packages/react-components/react-label/src/components/Label/useLabel.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import type { LabelProps, LabelState } from './Label.types'; -import { resolveShorthand } from '@fluentui/react-utilities'; /** * Create the state required to render Label. @@ -16,12 +15,13 @@ export const useLabel_unstable = (props: LabelProps, ref: React.Ref const { disabled = false, required = false, weight = 'regular', size = 'medium' } = props; return { disabled, - required: resolveShorthand(required === true ? '*' : required || undefined, { + required: slot(required === true ? '*' : required || undefined, { defaultProps: { 'aria-hidden': 'true' }, + elementType: 'span', }), weight, size, components: { root: 'label', required: 'span' }, - root: getNativeElementProps('label', { ref, ...props }), + root: slot(getNativeElementProps('label', { ref, ...props }), { required: true, elementType: 'label' }), }; }; diff --git a/packages/react-components/react-link/src/components/Link/renderLink.tsx b/packages/react-components/react-link/src/components/Link/renderLink.tsx index b1fb6f737b35e1..1d9f8dd1a0c38a 100644 --- a/packages/react-components/react-link/src/components/Link/renderLink.tsx +++ b/packages/react-components/react-link/src/components/Link/renderLink.tsx @@ -3,14 +3,14 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { LinkSlots, LinkState } from './Link.types'; /** * Renders a Link component by passing the state defined props to the appropriate slots. */ export const renderLink_unstable = (state: LinkState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); - return ; + return ; }; diff --git a/packages/react-components/react-link/src/components/Link/useLink.ts b/packages/react-components/react-link/src/components/Link/useLink.ts index aca38ef20e76cc..0fd922df51b839 100644 --- a/packages/react-components/react-link/src/components/Link/useLink.ts +++ b/packages/react-components/react-link/src/components/Link/useLink.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import { useLinkState_unstable } from './useLinkState'; import type { LinkProps, LinkState } from './Link.types'; @@ -28,12 +28,15 @@ export const useLink_unstable = ( root: 'a', }, - root: getNativeElementProps(as, { - ref, - type, - ...props, - as, - }), + root: slot( + getNativeElementProps(as, { + ref, + type, + ...props, + as, + }), + { required: true, elementType: 'a' }, + ), }; useLinkState_unstable(state); diff --git a/packages/react-components/react-menu/src/components/MenuDivider/renderMenuDivider.tsx b/packages/react-components/react-menu/src/components/MenuDivider/renderMenuDivider.tsx index 97951142214664..d7b932668a1487 100644 --- a/packages/react-components/react-menu/src/components/MenuDivider/renderMenuDivider.tsx +++ b/packages/react-components/react-menu/src/components/MenuDivider/renderMenuDivider.tsx @@ -2,7 +2,7 @@ /** @jsx createElement */ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import { MenuDividerSlots, MenuDividerState } from './MenuDivider.types'; /** @@ -10,7 +10,7 @@ import { MenuDividerSlots, MenuDividerState } from './MenuDivider.types'; * slots to children. */ export const renderMenuDivider_unstable = (state: MenuDividerState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); - return ; + return ; }; diff --git a/packages/react-components/react-menu/src/components/MenuDivider/useMenuDivider.ts b/packages/react-components/react-menu/src/components/MenuDivider/useMenuDivider.ts index 471b23d2870bfa..8ab09d3c93c0d7 100644 --- a/packages/react-components/react-menu/src/components/MenuDivider/useMenuDivider.ts +++ b/packages/react-components/react-menu/src/components/MenuDivider/useMenuDivider.ts @@ -1,4 +1,4 @@ -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import * as React from 'react'; import type { MenuDividerProps, MenuDividerState } from './MenuDivider.types'; @@ -10,11 +10,14 @@ export const useMenuDivider_unstable = (props: MenuDividerProps, ref: React.Ref< components: { root: 'div', }, - root: getNativeElementProps('div', { - role: 'presentation', - 'aria-hidden': true, - ...props, - ref, - }), + root: slot( + getNativeElementProps('div', { + role: 'presentation', + 'aria-hidden': true, + ...props, + ref, + }), + { required: true, elementType: 'div' }, + ), }; }; diff --git a/packages/react-components/react-menu/src/components/MenuGroup/renderMenuGroup.tsx b/packages/react-components/react-menu/src/components/MenuGroup/renderMenuGroup.tsx index 0d8b3f998425a9..c794243732d692 100644 --- a/packages/react-components/react-menu/src/components/MenuGroup/renderMenuGroup.tsx +++ b/packages/react-components/react-menu/src/components/MenuGroup/renderMenuGroup.tsx @@ -2,7 +2,7 @@ /** @jsx createElement */ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import { MenuGroupContextValues, MenuGroupSlots, MenuGroupState } from './MenuGroup.types'; import { MenuGroupContextProvider } from '../../contexts/menuGroupContext'; @@ -11,11 +11,11 @@ import { MenuGroupContextProvider } from '../../contexts/menuGroupContext'; * slots to children. */ export const renderMenuGroup_unstable = (state: MenuGroupState, contextValues: MenuGroupContextValues) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - + ); }; diff --git a/packages/react-components/react-menu/src/components/MenuGroup/useMenuGroup.ts b/packages/react-components/react-menu/src/components/MenuGroup/useMenuGroup.ts index 0cdc5999db0f66..8d9a6928ab1001 100644 --- a/packages/react-components/react-menu/src/components/MenuGroup/useMenuGroup.ts +++ b/packages/react-components/react-menu/src/components/MenuGroup/useMenuGroup.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, useId } from '@fluentui/react-utilities'; +import { getNativeElementProps, useId, slot } from '@fluentui/react-utilities'; import { MenuGroupProps, MenuGroupState } from './MenuGroup.types'; /** @@ -12,12 +12,15 @@ export function useMenuGroup_unstable(props: MenuGroupProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); - return ; + return ; }; diff --git a/packages/react-components/react-menu/src/components/MenuGroupHeader/useMenuGroupHeader.ts b/packages/react-components/react-menu/src/components/MenuGroupHeader/useMenuGroupHeader.ts index 0419e65c8a1717..529f6b9b371af5 100644 --- a/packages/react-components/react-menu/src/components/MenuGroupHeader/useMenuGroupHeader.ts +++ b/packages/react-components/react-menu/src/components/MenuGroupHeader/useMenuGroupHeader.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { useMenuGroupContext_unstable } from '../../contexts/menuGroupContext'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import { MenuGroupHeaderProps, MenuGroupHeaderState } from './MenuGroupHeader.types'; /** @@ -16,10 +16,13 @@ export function useMenuGroupHeader_unstable( components: { root: 'div', }, - root: getNativeElementProps('div', { - ref, - id, - ...props, - }), + root: slot( + getNativeElementProps('div', { + ref, + id, + ...props, + }), + { required: true, elementType: 'div' }, + ), }; } diff --git a/packages/react-components/react-menu/src/components/MenuItem/renderMenuItem.tsx b/packages/react-components/react-menu/src/components/MenuItem/renderMenuItem.tsx index d4f743b48d552b..d5af45c2aa9e9c 100644 --- a/packages/react-components/react-menu/src/components/MenuItem/renderMenuItem.tsx +++ b/packages/react-components/react-menu/src/components/MenuItem/renderMenuItem.tsx @@ -2,22 +2,22 @@ /** @jsx createElement */ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { MenuItemSlots, MenuItemState } from './MenuItem.types'; /** * Function that renders the final JSX of the component */ export const renderMenuItem_unstable = (state: MenuItemState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slots.checkmark && } - {slots.icon && } - {slots.content && } - {slots.secondaryContent && } - {slots.submenuIndicator && } - + + {state.checkmark && } + {state.icon && } + {state.content && } + {state.secondaryContent && } + {state.submenuIndicator && } + ); }; diff --git a/packages/react-components/react-menu/src/components/MenuItem/useMenuItem.tsx b/packages/react-components/react-menu/src/components/MenuItem/useMenuItem.tsx index e3fd1488a9e62b..c308a05081cf2d 100644 --- a/packages/react-components/react-menu/src/components/MenuItem/useMenuItem.tsx +++ b/packages/react-components/react-menu/src/components/MenuItem/useMenuItem.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useEventCallback, resolveShorthand, useMergedRefs, getNativeElementProps } from '@fluentui/react-utilities'; +import { useEventCallback, slot, useMergedRefs, getNativeElementProps } from '@fluentui/react-utilities'; import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; import { useCharacterSearch } from './useCharacterSearch'; import { useMenuTriggerContext_unstable } from '../../contexts/menuTriggerContext'; @@ -46,54 +46,59 @@ export const useMenuItem_unstable = (props: MenuItemProps, ref: React.Ref>, - onKeyDown: useEventCallback(event => { - props.onKeyDown?.(event); - if (!event.isDefaultPrevented() && (event.key === Space || event.key === Enter)) { - dismissedWithKeyboardRef.current = true; - } - }), - onMouseEnter: useEventCallback(event => { - innerRef.current?.focus(); + root: slot( + getNativeElementProps( + as, + useARIAButtonProps(as, { + role: 'menuitem', + ...props, + disabled: false, + disabledFocusable: disabled, + ref: useMergedRefs(ref, innerRef) as React.Ref>, + onKeyDown: useEventCallback(event => { + props.onKeyDown?.(event); + if (!event.isDefaultPrevented() && (event.key === Space || event.key === Enter)) { + dismissedWithKeyboardRef.current = true; + } + }), + onMouseEnter: useEventCallback(event => { + innerRef.current?.focus(); - props.onMouseEnter?.(event); - }), - onClick: useEventCallback(event => { - if (!hasSubmenu && !persistOnClick) { - setOpen(event, { - open: false, - keyboard: dismissedWithKeyboardRef.current, - bubble: true, - type: 'menuItemClick', - event, - }); - dismissedWithKeyboardRef.current = false; - } + props.onMouseEnter?.(event); + }), + onClick: useEventCallback(event => { + if (!hasSubmenu && !persistOnClick) { + setOpen(event, { + open: false, + keyboard: dismissedWithKeyboardRef.current, + bubble: true, + type: 'menuItemClick', + event, + }); + dismissedWithKeyboardRef.current = false; + } - props.onClick?.(event); + props.onClick?.(event); + }), }), - }), + ), + { required: true, elementType: 'div' }, ), - icon: resolveShorthand(props.icon, { required: hasIcons }), - checkmark: resolveShorthand(props.checkmark, { required: hasCheckmarks }), - submenuIndicator: resolveShorthand(props.submenuIndicator, { + icon: slot(props.icon, { required: hasIcons, elementType: 'span' }), + checkmark: slot(props.checkmark, { required: hasCheckmarks, elementType: 'span' }), + submenuIndicator: slot(props.submenuIndicator, { required: hasSubmenu, defaultProps: { children: dir === 'ltr' ? : , }, + elementType: 'span', }), - content: resolveShorthand(props.content, { + content: slot(props.content, { required: !!props.children, defaultProps: { children: props.children }, + elementType: 'span', }), - secondaryContent: resolveShorthand(props.secondaryContent), + secondaryContent: slot(props.secondaryContent, { elementType: 'span' }), }; useCharacterSearch(state, innerRef); return state; diff --git a/packages/react-components/react-menu/src/components/MenuItemCheckbox/renderMenuItemCheckbox.tsx b/packages/react-components/react-menu/src/components/MenuItemCheckbox/renderMenuItemCheckbox.tsx index edb8753e7f1bf3..9ee6774cb441b8 100644 --- a/packages/react-components/react-menu/src/components/MenuItemCheckbox/renderMenuItemCheckbox.tsx +++ b/packages/react-components/react-menu/src/components/MenuItemCheckbox/renderMenuItemCheckbox.tsx @@ -2,20 +2,20 @@ /** @jsx createElement */ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { MenuItemCheckboxState } from './MenuItemCheckbox.types'; import type { MenuItemSlots } from '../MenuItem/MenuItem.types'; /** Function that renders the final JSX of the component */ export const renderMenuItemCheckbox_unstable = (state: MenuItemCheckboxState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slots.checkmark && } - {slots.icon && } - {slots.content && } - {slots.secondaryContent && } - + + {state.checkmark && } + {state.icon && } + {state.content && } + {state.secondaryContent && } + ); }; diff --git a/packages/react-components/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.tsx b/packages/react-components/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.tsx index 426ce40b6d1e81..ce2610b2fbd107 100644 --- a/packages/react-components/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.tsx +++ b/packages/react-components/react-menu/src/components/MenuItemCheckbox/useMenuItemCheckbox.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { resolveShorthand } from '@fluentui/react-utilities'; +import { slot } from '@fluentui/react-utilities'; import { Checkmark16Filled } from '@fluentui/react-icons'; import { useMenuListContext_unstable } from '../../contexts/menuListContext'; import { useMenuItem_unstable } from '../MenuItem/useMenuItem'; @@ -26,9 +26,10 @@ export const useMenuItemCheckbox_unstable = ( persistOnClick: true, ...props, 'aria-checked': checked, - checkmark: resolveShorthand(props.checkmark, { + checkmark: slot(props.checkmark, { defaultProps: { children: }, required: true, + elementType: 'span', }), onClick: (e: React.MouseEvent>) => { toggleCheckbox?.(e, name, value, checked); diff --git a/packages/react-components/react-menu/src/components/MenuItemRadio/renderMenuItemRadio.tsx b/packages/react-components/react-menu/src/components/MenuItemRadio/renderMenuItemRadio.tsx index 2b6eb72c823027..170f9d0be0310e 100644 --- a/packages/react-components/react-menu/src/components/MenuItemRadio/renderMenuItemRadio.tsx +++ b/packages/react-components/react-menu/src/components/MenuItemRadio/renderMenuItemRadio.tsx @@ -2,7 +2,7 @@ /** @jsx createElement */ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { MenuItemRadioState } from './MenuItemRadio.types'; import type { MenuItemSlots } from '../MenuItem/MenuItem.types'; @@ -11,14 +11,14 @@ import type { MenuItemSlots } from '../MenuItem/MenuItem.types'; * slots to children. */ export const renderMenuItemRadio_unstable = (state: MenuItemRadioState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slots.checkmark && } - {slots.icon && } - {slots.content && } - {slots.secondaryContent && } - + + {state.checkmark && } + {state.icon && } + {state.content && } + {state.secondaryContent && } + ); }; diff --git a/packages/react-components/react-menu/src/components/MenuItemRadio/useMenuItemRadio.tsx b/packages/react-components/react-menu/src/components/MenuItemRadio/useMenuItemRadio.tsx index b8e89dc42ce230..74bb80c7098a0b 100644 --- a/packages/react-components/react-menu/src/components/MenuItemRadio/useMenuItemRadio.tsx +++ b/packages/react-components/react-menu/src/components/MenuItemRadio/useMenuItemRadio.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { resolveShorthand } from '@fluentui/react-utilities'; +import { slot } from '@fluentui/react-utilities'; import { Checkmark16Filled } from '@fluentui/react-icons'; import { useMenuListContext_unstable } from '../../contexts/menuListContext'; import { useMenuItem_unstable } from '../MenuItem/useMenuItem'; @@ -28,9 +28,10 @@ export const useMenuItemRadio_unstable = ( ...props, role: 'menuitemradio', 'aria-checked': checked, - checkmark: resolveShorthand(props.checkmark, { + checkmark: slot(props.checkmark, { defaultProps: { children: }, required: true, + elementType: 'span', }), onClick: (e: React.MouseEvent>) => { selectRadio?.(e, name, value, checked); diff --git a/packages/react-components/react-menu/src/components/MenuList/renderMenuList.tsx b/packages/react-components/react-menu/src/components/MenuList/renderMenuList.tsx index 9eaf5f74ac0094..a7b365ffe70383 100644 --- a/packages/react-components/react-menu/src/components/MenuList/renderMenuList.tsx +++ b/packages/react-components/react-menu/src/components/MenuList/renderMenuList.tsx @@ -2,7 +2,7 @@ /** @jsx createElement */ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import { MenuListContextValues, MenuListSlots, MenuListState } from './MenuList.types'; import { MenuListProvider } from '../../contexts/menuListContext'; @@ -10,11 +10,11 @@ import { MenuListProvider } from '../../contexts/menuListContext'; * Function that renders the final JSX of the component */ export const renderMenuList_unstable = (state: MenuListState, contextValues: MenuListContextValues) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - + ); }; diff --git a/packages/react-components/react-menu/src/components/MenuList/useMenuList.ts b/packages/react-components/react-menu/src/components/MenuList/useMenuList.ts index 561439c243da9d..1d1b13afa91896 100644 --- a/packages/react-components/react-menu/src/components/MenuList/useMenuList.ts +++ b/packages/react-components/react-menu/src/components/MenuList/useMenuList.ts @@ -4,6 +4,7 @@ import { useEventCallback, useControllableState, getNativeElementProps, + slot, } from '@fluentui/react-utilities'; import { useArrowNavigationGroup, useFocusFinders } from '@fluentui/react-tabster'; import { useHasParentContext } from '@fluentui/react-context-selector'; @@ -108,13 +109,16 @@ export const useMenuList_unstable = (props: MenuListProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); if (state.inline) { - return ; + return ; } return ( - + ); }; diff --git a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts index c5221404070cf9..092e7643277905 100644 --- a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts +++ b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { ArrowLeft, Tab, ArrowRight, Escape } from '@fluentui/keyboard-keys'; -import { getNativeElementProps, useEventCallback, useMergedRefs } from '@fluentui/react-utilities'; +import { getNativeElementProps, useEventCallback, useMergedRefs, slot } from '@fluentui/react-utilities'; import { MenuPopoverProps, MenuPopoverState } from './MenuPopover.types'; import { useMenuContext_unstable } from '../../contexts/menuContext'; import { dispatchMenuEnterEvent } from '../../utils/index'; @@ -57,47 +57,34 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< const inline = useMenuContext_unstable(context => context.inline) ?? false; const mountNode = useMenuContext_unstable(context => context.mountNode); - const rootProps = getNativeElementProps('div', { - role: 'presentation', - ...props, - ref: useMergedRefs(ref, popoverRef, mouseOverListenerCallbackRef), - }); - + const rootProps = slot( + getNativeElementProps('div', { + role: 'presentation', + ...props, + ref: useMergedRefs(ref, popoverRef, mouseOverListenerCallbackRef), + }), + { required: true, elementType: 'div' }, + ); const { onMouseEnter: onMouseEnterOriginal, onKeyDown: onKeyDownOriginal } = rootProps; - rootProps.onMouseEnter = useEventCallback((event: React.MouseEvent) => { if (openOnHover) { setOpen(event, { open: true, keyboard: false, type: 'menuPopoverMouseEnter', event }); } - onMouseEnterOriginal?.(event); }); - rootProps.onKeyDown = useEventCallback((event: React.KeyboardEvent) => { const key = event.key; - if (key === Escape || (isSubmenu && key === CloseArrowKey)) { if (open && popoverRef.current?.contains(event.target as HTMLElement)) { - setOpen(event, { open: false, keyboard: true, type: 'menuPopoverKeyDown', event }); - // stop propagation to avoid conflicting with other elements that listen for `Escape` + setOpen(event, { open: false, keyboard: true, type: 'menuPopoverKeyDown', event }); // stop propagation to avoid conflicting with other elements that listen for `Escape` // e,g: Dialog, Popover and Tooltip event.stopPropagation(); } } - if (key === Tab) { setOpen(event, { open: false, keyboard: true, type: 'menuPopoverKeyDown', event }); } - onKeyDownOriginal?.(event); }); - - return { - inline, - mountNode, - components: { - root: 'div', - }, - root: rootProps, - }; + return { inline, mountNode, components: { root: 'div' }, root: rootProps }; }; diff --git a/packages/react-components/react-menu/src/components/MenuSplitGroup/renderMenuSplitGroup.tsx b/packages/react-components/react-menu/src/components/MenuSplitGroup/renderMenuSplitGroup.tsx index 8ab8854ebb731d..2f203e4f5545dd 100644 --- a/packages/react-components/react-menu/src/components/MenuSplitGroup/renderMenuSplitGroup.tsx +++ b/packages/react-components/react-menu/src/components/MenuSplitGroup/renderMenuSplitGroup.tsx @@ -2,14 +2,14 @@ /** @jsx createElement */ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { MenuSplitGroupState, MenuSplitGroupSlots } from './MenuSplitGroup.types'; /** * Render the final JSX of MenuSplitGroup */ export const renderMenuSplitGroup_unstable = (state: MenuSplitGroupState) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); - return ; + return ; }; diff --git a/packages/react-components/react-menu/src/components/MenuSplitGroup/useMenuSplitGroup.ts b/packages/react-components/react-menu/src/components/MenuSplitGroup/useMenuSplitGroup.ts index 2aece5d09c2a2c..04b7e6bfc25d54 100644 --- a/packages/react-components/react-menu/src/components/MenuSplitGroup/useMenuSplitGroup.ts +++ b/packages/react-components/react-menu/src/components/MenuSplitGroup/useMenuSplitGroup.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, getRTLSafeKey, useMergedRefs } from '@fluentui/react-utilities'; +import { getNativeElementProps, getRTLSafeKey, useMergedRefs, slot } from '@fluentui/react-utilities'; import { useFocusFinders } from '@fluentui/react-tabster'; import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; import type { MenuSplitGroupProps, MenuSplitGroupState } from './MenuSplitGroup.types'; @@ -54,11 +54,14 @@ export const useMenuSplitGroup_unstable = ( components: { root: 'div', }, - root: getNativeElementProps('div', { - role: 'group', - ref: useMergedRefs(ref, innerRef), - onKeyDown, - ...props, - }), + root: slot( + getNativeElementProps('div', { + role: 'group', + ref: useMergedRefs(ref, innerRef), + onKeyDown, + ...props, + }), + { required: true, elementType: 'div' }, + ), }; }; diff --git a/packages/react-components/react-migration-v0-v9/src/components/ItemLayout/ItemLayout.tsx b/packages/react-components/react-migration-v0-v9/src/components/ItemLayout/ItemLayout.tsx index ed5a33f3eb0fc3..3940216eaf1ec0 100755 --- a/packages/react-components/react-migration-v0-v9/src/components/ItemLayout/ItemLayout.tsx +++ b/packages/react-components/react-migration-v0-v9/src/components/ItemLayout/ItemLayout.tsx @@ -5,12 +5,12 @@ import { createElement } from '@fluentui/react-jsx-runtime'; import * as React from 'react'; import { mergeClasses } from '@fluentui/react-components'; import { + assertSlots, ComponentProps, ComponentState, getNativeElementProps, - getSlotsNext, + slot, Slot, - resolveShorthand, } from '@fluentui/react-utilities'; import { useItemLayoutStyles } from './ItemLayout.styles'; @@ -45,15 +45,16 @@ export const ItemLayout = React.forwardRef((pro startMedia: 'div', endMedia: 'div', }, - root: getNativeElementProps('div', { ...props, ref }), - contentMedia: resolveShorthand(props.contentMedia), - contentWrapper: resolveShorthand(props.contentWrapper, { + root: slot(getNativeElementProps('div', { ...props, ref }), { required: true, elementType: 'div' }), + contentMedia: slot(props.contentMedia, { elementType: 'div' }), + contentWrapper: slot(props.contentWrapper, { required: true, + elementType: 'div', }), - header: resolveShorthand(props.header), - headerMedia: resolveShorthand(props.headerMedia), - startMedia: resolveShorthand(props.startMedia), - endMedia: resolveShorthand(props.endMedia), + header: slot(props.header, { elementType: 'div' }), + headerMedia: slot(props.headerMedia, { elementType: 'div' }), + startMedia: slot(props.startMedia, { elementType: 'div' }), + endMedia: slot(props.endMedia, { elementType: 'div' }), }; const styles = useItemLayoutStyles(); @@ -82,21 +83,19 @@ export const ItemLayout = React.forwardRef((pro state.endMedia.className = mergeClasses(styles.endMedia, state.endMedia.className); } - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); return ( - - {slots.startMedia && } - - {slots.header && } - {slots.headerMedia && } - {slots.contentWrapper && ( - {state.root.children} - )} - {slots.contentMedia && } - - {slots.endMedia && } - + + {state.startMedia && } + + {state.header && } + {state.headerMedia && } + {state.contentWrapper && {state.root.children}} + {state.contentMedia && } + + {state.endMedia && } + ); }); diff --git a/packages/react-components/react-persona/src/components/Persona/renderPersona.tsx b/packages/react-components/react-persona/src/components/Persona/renderPersona.tsx index 0004764b621a40..7afc2499d6bded 100644 --- a/packages/react-components/react-persona/src/components/Persona/renderPersona.tsx +++ b/packages/react-components/react-persona/src/components/Persona/renderPersona.tsx @@ -3,7 +3,7 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { PersonaState, PersonaSlots } from './Persona.types'; /** @@ -11,20 +11,18 @@ import type { PersonaState, PersonaSlots } from './Persona.types'; */ export const renderPersona_unstable = (state: PersonaState) => { const { presenceOnly, textPosition } = state; - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); - const coin = presenceOnly - ? slots.presence && - : slots.avatar && ; + const coin = presenceOnly ? state.presence && : state.avatar && ; return ( - + {(textPosition === 'after' || textPosition === 'below') && coin} - {slots.primaryText && } - {slots.secondaryText && } - {slots.tertiaryText && } - {slots.quaternaryText && } + {state.primaryText && } + {state.secondaryText && } + {state.tertiaryText && } + {state.quaternaryText && } {textPosition === 'before' && coin} - + ); }; diff --git a/packages/react-components/react-persona/src/components/Persona/usePersona.ts b/packages/react-components/react-persona/src/components/Persona/usePersona.ts index e4f1947b2c5bf2..f4845a04e7f5ac 100644 --- a/packages/react-components/react-persona/src/components/Persona/usePersona.ts +++ b/packages/react-components/react-persona/src/components/Persona/usePersona.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { Avatar } from '@fluentui/react-avatar'; -import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import { PresenceBadge } from '@fluentui/react-badge'; import type { PersonaProps, PersonaState } from './Persona.types'; @@ -16,25 +16,23 @@ import type { PersonaProps, PersonaState } from './Persona.types'; export const usePersona_unstable = (props: PersonaProps, ref: React.Ref): PersonaState => { const { name, presenceOnly = false, size = 'medium', textAlignment = 'start', textPosition = 'after' } = props; - const primaryText = resolveShorthand(props.primaryText, { + const primaryText = slot(props.primaryText, { required: true, defaultProps: { children: name, }, + elementType: 'span', }); - const secondaryText = resolveShorthand(props.secondaryText); - const tertiaryText = resolveShorthand(props.tertiaryText); - const quaternaryText = resolveShorthand(props.quaternaryText); - + const secondaryText = slot(props.secondaryText, { elementType: 'span' }); + const tertiaryText = slot(props.tertiaryText, { elementType: 'span' }); + const quaternaryText = slot(props.quaternaryText, { elementType: 'span' }); const numTextLines = [primaryText, secondaryText, tertiaryText, quaternaryText].filter(Boolean).length; - return { numTextLines, presenceOnly, size, textAlignment, textPosition, - components: { root: 'div', avatar: Avatar, @@ -45,29 +43,34 @@ export const usePersona_unstable = (props: PersonaProps, ref: React.Ref { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); const surface = ( - + {state.withArrow &&

} - {slotProps.root.children} - + {state.root.children} + ); if (state.inline) { diff --git a/packages/react-components/react-popover/src/components/PopoverSurface/usePopoverSurface.ts b/packages/react-components/react-popover/src/components/PopoverSurface/usePopoverSurface.ts index 425f6fec93691b..0fa2a8d4b1d4f1 100644 --- a/packages/react-components/react-popover/src/components/PopoverSurface/usePopoverSurface.ts +++ b/packages/react-components/react-popover/src/components/PopoverSurface/usePopoverSurface.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, useMergedRefs } from '@fluentui/react-utilities'; +import { getNativeElementProps, useMergedRefs, slot } from '@fluentui/react-utilities'; import { useModalAttributes } from '@fluentui/react-tabster'; import { usePopoverContext_unstable } from '../../popoverContext'; import type { PopoverSurfaceProps, PopoverSurfaceState } from './PopoverSurface.types'; @@ -44,13 +44,16 @@ export const usePopoverSurface_unstable = ( components: { root: 'div', }, - root: getNativeElementProps('div', { - ref: useMergedRefs(ref, contentRef), - role: trapFocus ? 'dialog' : 'group', - 'aria-modal': trapFocus ? true : undefined, - ...modalAttributes, - ...props, - }), + root: slot( + getNativeElementProps('div', { + ref: useMergedRefs(ref, contentRef), + role: trapFocus ? 'dialog' : 'group', + 'aria-modal': trapFocus ? true : undefined, + ...modalAttributes, + ...props, + }), + { required: true, elementType: 'div' }, + ), }; const { diff --git a/packages/react-components/react-progress/src/components/ProgressBar/renderProgressBar.tsx b/packages/react-components/react-progress/src/components/ProgressBar/renderProgressBar.tsx index 614c7840d5b298..9e5e418d168952 100644 --- a/packages/react-components/react-progress/src/components/ProgressBar/renderProgressBar.tsx +++ b/packages/react-components/react-progress/src/components/ProgressBar/renderProgressBar.tsx @@ -3,13 +3,13 @@ import { createElement } from '@fluentui/react-jsx-runtime'; -import { getSlotsNext } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { ProgressBarState, ProgressBarSlots } from './ProgressBar.types'; /** * Render the final JSX of ProgressBar */ export const renderProgressBar_unstable = (state: ProgressBarState) => { - const { slots, slotProps } = getSlotsNext(state); - return {slots.bar && }; + assertSlots(state); + return {state.bar && }; }; diff --git a/packages/react-components/react-progress/src/components/ProgressBar/useProgressBar.tsx b/packages/react-components/react-progress/src/components/ProgressBar/useProgressBar.tsx index 3ad38841f12773..a70554bf77d555 100644 --- a/packages/react-components/react-progress/src/components/ProgressBar/useProgressBar.tsx +++ b/packages/react-components/react-progress/src/components/ProgressBar/useProgressBar.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useFieldContext_unstable } from '@fluentui/react-field'; -import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; import { clampValue, clampMax } from '../../utils/index'; import type { ProgressBarProps, ProgressBarState } from './ProgressBar.types'; @@ -25,37 +25,32 @@ export const useProgressBar_unstable = (props: ProgressBarProps, ref: React.Ref< const max = clampMax(props.max ?? 1); const value = clampValue(props.value, max); - const root = getNativeElementProps('div', { - ref, - role: 'progressbar', - 'aria-valuemin': value !== undefined ? 0 : undefined, - 'aria-valuemax': value !== undefined ? max : undefined, - 'aria-valuenow': value, - 'aria-labelledby': field?.labelId, - ...props, - }); - + const root = slot( + getNativeElementProps('div', { + ref, + role: 'progressbar', + 'aria-valuemin': value !== undefined ? 0 : undefined, + 'aria-valuemax': value !== undefined ? max : undefined, + 'aria-valuenow': value, + 'aria-labelledby': field?.labelId, + ...props, + }), + { required: true, elementType: 'div' }, + ); if (field && (field.validationMessageId || field.hintId)) { // Prepend the field's validation message and/or hint to the user's aria-describedby root['aria-describedby'] = [field?.validationMessageId, field?.hintId, root['aria-describedby']] .filter(Boolean) .join(' '); } - - const bar = resolveShorthand(props.bar, { - required: true, - }); - + const bar = slot(props.bar, { required: true, elementType: 'div' }); const state: ProgressBarState = { color, max, shape, thickness, value, - components: { - root: 'div', - bar: 'div', - }, + components: { root: 'div', bar: 'div' }, root, bar, }; diff --git a/packages/react-components/react-provider/src/components/FluentProvider/renderFluentProvider.tsx b/packages/react-components/react-provider/src/components/FluentProvider/renderFluentProvider.tsx index 919c49d34f4c9b..3596d45e0bc028 100644 --- a/packages/react-components/react-provider/src/components/FluentProvider/renderFluentProvider.tsx +++ b/packages/react-components/react-provider/src/components/FluentProvider/renderFluentProvider.tsx @@ -2,7 +2,7 @@ /** @jsx createElement */ import { createElement } from '@fluentui/react-jsx-runtime'; -import { canUseDOM, getSlotsNext } from '@fluentui/react-utilities'; +import { canUseDOM, assertSlots } from '@fluentui/react-utilities'; import { TextDirectionProvider } from '@griffel/react'; import { OverridesProvider_unstable as OverridesProvider, @@ -22,7 +22,7 @@ export const renderFluentProvider_unstable = ( state: FluentProviderState, contextValues: FluentProviderContextValues, ) => { - const { slots, slotProps } = getSlotsNext(state); + assertSlots(state); // Typescript (vscode) incorrectly references the FluentProviderProps.customStyleHooks_unstable // instead of FluentProviderContextValues.customStyleHooks_unstable and thinks it is @@ -38,7 +38,7 @@ export const renderFluentProvider_unstable = ( - + {canUseDOM() ? null : (