From a587916638be1e057153682730a9bcebe393f13d Mon Sep 17 00:00:00 2001 From: Bernardo Sunderhus Date: Mon, 29 May 2023 22:31:56 +0000 Subject: [PATCH 1/4] feat: implements slot methods --- .../src/button/useARIAButtonProps.test.tsx | 2 +- .../src/createElement.test.tsx | 197 +++++++++++++++++- .../react-jsx-runtime/src/createElement.ts | 68 ++++-- .../etc/react-utilities.api.md | 40 +++- .../react-utilities/src/compose/constants.ts | 5 +- .../react-utilities/src/compose/getSlots.ts | 18 +- .../src/compose/getSlotsNext.test.tsx | 110 +++++++--- .../src/compose/getSlotsNext.ts | 5 + .../react-utilities/src/compose/index.ts | 1 + .../src/compose/resolveShorthand.test.tsx | 8 +- .../src/compose/resolveShorthand.ts | 35 +--- .../react-utilities/src/compose/slot.ts | 174 ++++++++++++++++ .../react-utilities/src/compose/types.ts | 16 ++ .../react-utilities/src/index.ts | 8 +- 14 files changed, 577 insertions(+), 110 deletions(-) create mode 100644 packages/react-components/react-utilities/src/compose/slot.ts 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..975a30043db570 100644 --- a/packages/react-components/react-aria/src/button/useARIAButtonProps.test.tsx +++ b/packages/react-components/react-aria/src/button/useARIAButtonProps.test.tsx @@ -8,7 +8,7 @@ import { useARIAButtonShorthand } from './useARIAButtonShorthand'; const TestButton = (props: ComponentProps<{ root: Slot }>) => { const { slots, slotProps } = getSlots<{ root: Slot }>({ - components: { root: 'button' }, + components: { root: props.as ?? 'button' }, root: useARIAButtonShorthand(props, { required: true }), }); return ; diff --git a/packages/react-components/react-jsx-runtime/src/createElement.test.tsx b/packages/react-components/react-jsx-runtime/src/createElement.test.tsx index ee4e3221aa7726..916814c221520c 100644 --- a/packages/react-components/react-jsx-runtime/src/createElement.test.tsx +++ b/packages/react-components/react-jsx-runtime/src/createElement.test.tsx @@ -1,14 +1,20 @@ -/* eslint-disable jsdoc/check-tag-names */ /** @jsxRuntime classic */ /** @jsxFrag Fragment */ /** @jsx createElement */ -/* eslint-enable jsdoc/check-tag-names */ import { render } from '@testing-library/react'; -import { ComponentProps, ComponentState, Slot, getSlotsNext, resolveShorthand } from '@fluentui/react-utilities'; +import { + ComponentProps, + ComponentState, + Slot, + assertSlots, + getSlotsNext, + resolveShorthand, + slot, +} from '@fluentui/react-utilities'; import { createElement } from './createElement'; -describe('createElement', () => { +describe('createElement with getSlotsNext', () => { describe('general behavior tests', () => { it('handles a string', () => { const result = render(
Hello world
); @@ -166,3 +172,186 @@ describe('createElement', () => { }); }); }); + +describe('createElement with assertSlots', () => { + describe('general behavior tests', () => { + it('handles a string', () => { + const result = render(
Hello world
); + + expect(result.container.firstChild).toMatchInlineSnapshot(` +
+ Hello world +
+ `); + }); + + it('handles an array', () => { + const result = render( +
+ {Array.from({ length: 3 }, (_, i) => ( +
{i}
+ ))} +
, + ); + + expect(result.container.firstChild).toMatchInlineSnapshot(` +
+
+ 0 +
+
+ 1 +
+
+ 2 +
+
+ `); + }); + + it('handles an array of children', () => { + const result = render( +
+
1
+
2
+
, + ); + + expect(result.container.firstChild).toMatchInlineSnapshot(` +
+
+ 1 +
+
+ 2 +
+
+ `); + }); + }); + + describe('custom behavior tests', () => { + it('keeps children from "defaultProps" in a render callback', () => { + type TestComponentSlots = { + someSlot: NonNullable>; + }; + type TestComponentProps = ComponentProps>; + type TestComponentState = ComponentState; + + const TestComponent = (props: TestComponentProps) => { + const state: TestComponentState = { + components: { someSlot: 'div' }, + someSlot: slot(props.someSlot, { + required: true, + elementType: 'div', + defaultProps: { children: 'Default Children', id: 'slot' }, + }), + }; + assertSlots(state); + return ; + }; + + const children = jest.fn().mockImplementation((Component, props) => ( +
+ +
+ )); + const result = render(); + + expect(children).toHaveBeenCalledTimes(1); + expect(children).toHaveBeenCalledWith('div', { children: 'Default Children', id: 'slot' }); + + expect(result.container.firstChild).toMatchInlineSnapshot(` +
+
+ Default Children +
+
+ `); + }); + + it('keeps children from a render template in a render callback', () => { + type TestComponentSlots = { outer: NonNullable>; inner: NonNullable> }; + type TestComponentState = ComponentState; + type TestComponentProps = ComponentProps>; + + const TestComponent = (props: TestComponentProps) => { + const state: TestComponentState = { + 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' }), + }; + assertSlots(state); + return ( + + + + ); + }; + + const children = jest.fn().mockImplementation((Component, props) => ( +
+ +
+ )); + const result = render(); + + expect(children).toHaveBeenCalledTimes(1); + expect(children.mock.calls[0][0]).toBe('div'); + expect(children.mock.calls[0][1].id).toBe('outer'); + expect(children.mock.calls[0][1].children).toMatchInlineSnapshot(` + +
+ Inner children +
+
+ `); + + expect(result.container.firstChild).toMatchInlineSnapshot(` +
+
+
+ Inner children +
+
+
+ `); + }); + + 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..7889b80e194805 100644 --- a/packages/react-components/react-jsx-runtime/src/createElement.ts +++ b/packages/react-components/react-jsx-runtime/src/createElement.ts @@ -1,38 +1,60 @@ 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, + slot, +} 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); + } + // TODO: + // this is for backwards compatibility with getSlotsNext + // it should be removed once getSlotsNext is obsolete + if (isSlot

(props)) { + return createElementFromSlotComponent( + slot(props, { required: true, elementType: type as React.ComponentType

}), + 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-utilities/etc/react-utilities.api.md b/packages/react-components/react-utilities/etc/react-utilities.api.md index 1c29e73b0cddcd..42e70b04496e57 100644 --- a/packages/react-components/react-utilities/etc/react-utilities.api.md +++ b/packages/react-components/react-utilities/etc/react-utilities.api.md @@ -10,6 +10,9 @@ import * as React_2 from 'react'; // @internal export function applyTriggerPropsToChildren(children: TriggerProps['children'], triggerChildProps: TriggerChildProps): React_2.ReactElement | null; +// @internal +export function assertSlots(state: unknown): asserts state is SlotComponents; + // @public export function canUseDOM(): boolean; @@ -73,7 +76,7 @@ export function getSlots(state: ComponentState): { slotProps: ObjectSlotProps; }; -// @public +// @internal export function getSlotsNext(state: ComponentState): { slots: Slots; slotProps: ObjectSlotProps; @@ -104,6 +107,9 @@ export function isMouseEvent(event: TouchOrMouseEvent): event is MouseEvent | Re // @public export function isResolvedShorthand>(shorthand?: Shorthand): shorthand is ExtractSlotProps; +// @public +export function isSlot(element: unknown): element is SlotComponent; + // @public export function isTouchEvent(event: TouchOrMouseEvent): event is TouchEvent | React_2.TouchEvent; @@ -172,14 +178,42 @@ export type Slot>; }[AlternateAs] | null : 'Error: First parameter to Slot must not be not a union of types. See documentation of Slot type.'; -// @internal -export const SLOT_RENDER_FUNCTION_SYMBOL: unique symbol; +// @public +export function slot(value: Props | SlotComponent | SlotShorthandValue | undefined, options: { + required: true; +} & SlotOptions): SlotComponent; + +// @public (undocumented) +export function slot(value: Props | SlotComponent | SlotShorthandValue | undefined | null, options?: { + required?: boolean; +} & SlotOptions): SlotComponent | undefined; + +// @public +export const SLOT_COMPONENT_METADATA_SYMBOL: unique symbol; // @public export type SlotClassNames = { [SlotName in keyof Slots]-?: string; }; +// @public (undocumented) +export type SlotComponent = Props & { + (props: React_2.PropsWithChildren<{}>): React_2.ReactElement | null; + [SLOT_COMPONENT_METADATA_SYMBOL]: Readonly>; +}; + +// @public (undocumented) +export type SlotComponentMetadata = { + renderFunction?: SlotRenderFunction; + elementType: React_2.ComponentType | (Props extends AsIntrinsicElement ? As : keyof JSX.IntrinsicElements); +}; + +// @public (undocumented) +export type SlotOptions = { + elementType?: React_2.ComponentType | (Props extends AsIntrinsicElement ? As : keyof JSX.IntrinsicElements); + defaultProps?: Partial; +}; + // @public export type SlotPropsRecord = Record; diff --git a/packages/react-components/react-utilities/src/compose/constants.ts b/packages/react-components/react-utilities/src/compose/constants.ts index f5c1593f9203ae..3eb781dc5fc0bd 100644 --- a/packages/react-components/react-utilities/src/compose/constants.ts +++ b/packages/react-components/react-utilities/src/compose/constants.ts @@ -1,5 +1,4 @@ /** - * @internal - * Internal reference for the render function + * Internal value that indicates a given component is a slot component */ -export const SLOT_RENDER_FUNCTION_SYMBOL = Symbol('fui.slotRenderFunction'); +export const SLOT_COMPONENT_METADATA_SYMBOL = Symbol('fui.slotComponentMetadata'); diff --git a/packages/react-components/react-utilities/src/compose/getSlots.ts b/packages/react-components/react-utilities/src/compose/getSlots.ts index 8c24dd77422ef1..e3e996a674d8e7 100644 --- a/packages/react-components/react-utilities/src/compose/getSlots.ts +++ b/packages/react-components/react-utilities/src/compose/getSlots.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { omit } from '../utils/omit'; -import { SLOT_RENDER_FUNCTION_SYMBOL } from './constants'; +import { SLOT_COMPONENT_METADATA_SYMBOL } from './constants'; import type { AsIntrinsicElement, ComponentState, @@ -11,6 +11,7 @@ import type { UnionToIntersection, UnknownSlotProps, } from './types'; +import { isSlot } from './slot'; export type Slots = { [K in keyof S]: ExtractSlotProps extends AsIntrinsicElement @@ -76,12 +77,11 @@ function getSlot( return [null, undefined as R[K]]; } - const { - children, - as: asProp, - [SLOT_RENDER_FUNCTION_SYMBOL]: renderFunction, - ...rest - } = props as typeof props & { [SLOT_RENDER_FUNCTION_SYMBOL]?: SlotRenderFunction }; + type NonUndefined = T extends undefined ? never : T; + // TS Error: Property 'as' does not exist on type 'UnknownSlotProps | undefined'.ts(2339) + const { as: asProp, children, ...rest } = props as NonUndefined; + + const metadata = isSlot(props) ? props[SLOT_COMPONENT_METADATA_SYMBOL] : undefined; const slot = ( state.components?.[slotName] === undefined || typeof state.components[slotName] === 'string' @@ -89,8 +89,8 @@ function getSlot( : state.components[slotName] ) as React.ElementType; - if (renderFunction || typeof children === 'function') { - const render = renderFunction || (children as SlotRenderFunction); + if (metadata?.renderFunction || typeof children === 'function') { + const render = (metadata?.renderFunction || children) as SlotRenderFunction; return [ React.Fragment, { diff --git a/packages/react-components/react-utilities/src/compose/getSlotsNext.test.tsx b/packages/react-components/react-utilities/src/compose/getSlotsNext.test.tsx index a66534b2786982..54169b2d6aee62 100644 --- a/packages/react-components/react-utilities/src/compose/getSlotsNext.test.tsx +++ b/packages/react-components/react-utilities/src/compose/getSlotsNext.test.tsx @@ -1,8 +1,13 @@ import * as React from 'react'; import { getSlotsNext } from './getSlotsNext'; -import type { Slot } from './types'; +import type { ExtractSlotProps, Slot, SlotComponent, UnknownSlotProps } from './types'; import { resolveShorthand } from './resolveShorthand'; -import { SLOT_RENDER_FUNCTION_SYMBOL } from './constants'; +import { SLOT_COMPONENT_METADATA_SYMBOL } from './constants'; + +const resolveShorthandMock = (props: Props): SlotComponent => { + // casting is required here as SlotComponent is a callable + return { [SLOT_COMPONENT_METADATA_SYMBOL]: {}, ...props } as SlotComponent; +}; describe('getSlotsNext', () => { type FooProps = { id?: string; children?: React.ReactNode }; @@ -10,17 +15,27 @@ describe('getSlotsNext', () => { it('returns provided component type for root if the as prop is not provided', () => { type Slots = { root: Slot<'div'> }; - expect(getSlotsNext({ root: {}, components: { root: 'div' } })).toEqual({ + expect( + getSlotsNext({ + root: resolveShorthandMock>({}), + components: { root: 'div' }, + }), + ).toEqual({ slots: { root: 'div' }, - slotProps: { root: {} }, + slotProps: { root: { [SLOT_COMPONENT_METADATA_SYMBOL]: {} } }, }); }); it('returns root slot as a span with no props', () => { type Slots = { root: Slot<'div', 'span'> }; - expect(getSlotsNext({ root: { as: 'span' }, components: { root: 'div' } })).toEqual({ + expect( + getSlotsNext({ + root: resolveShorthandMock>({ as: 'span' }), + components: { root: 'div' }, + }), + ).toEqual({ slots: { root: 'span' }, - slotProps: { root: {} }, + slotProps: { root: { [SLOT_COMPONENT_METADATA_SYMBOL]: {} } }, }); }); @@ -28,18 +43,26 @@ describe('getSlotsNext', () => { type Slots = { root: Slot<'button'> }; const invalidProp = { href: 'href' } as React.ButtonHTMLAttributes; expect( - getSlotsNext({ root: { as: 'button', id: 'id', ...invalidProp }, components: { root: 'button' } }), + getSlotsNext({ + root: resolveShorthandMock>({ as: 'button', id: 'id', ...invalidProp }), + components: { root: 'button' }, + }), ).toEqual({ slots: { root: 'button' }, - slotProps: { root: { id: 'id', href: 'href' } }, + slotProps: { root: { id: 'id', href: 'href', [SLOT_COMPONENT_METADATA_SYMBOL]: {} } }, }); }); it('returns root slot as an anchor, leaving the href intact', () => { type Slots = { root: Slot<'a'> }; - expect(getSlotsNext({ root: { as: 'a', id: 'id', href: 'href' }, components: { root: 'a' } })).toEqual({ + expect( + getSlotsNext({ + root: resolveShorthandMock>({ as: 'a', id: 'id', href: 'href' }), + components: { root: 'a' }, + }), + ).toEqual({ slots: { root: 'a' }, - slotProps: { root: { id: 'id', href: 'href' } }, + slotProps: { root: { id: 'id', href: 'href', [SLOT_COMPONENT_METADATA_SYMBOL]: {} } }, }); }); @@ -50,13 +73,13 @@ describe('getSlotsNext', () => { }; expect( getSlotsNext({ - icon: {}, + icon: resolveShorthandMock>({}), components: { root: 'div', icon: Foo }, - root: { as: 'div' }, + root: resolveShorthandMock>({ as: 'div' }), }), ).toEqual({ slots: { root: 'div', icon: Foo }, - slotProps: { root: {}, icon: {} }, + slotProps: { root: { [SLOT_COMPONENT_METADATA_SYMBOL]: {} }, icon: { [SLOT_COMPONENT_METADATA_SYMBOL]: {} } }, }); }); @@ -68,12 +91,15 @@ describe('getSlotsNext', () => { expect( getSlotsNext({ components: { icon: 'button', root: 'div' }, - root: { as: 'span' }, - icon: { id: 'id', children: 'children' }, + root: resolveShorthandMock>({ as: 'span' }), + icon: resolveShorthandMock>({ id: 'id', children: 'children' }), }), ).toEqual({ slots: { root: 'span', icon: 'button' }, - slotProps: { root: {}, icon: { id: 'id', children: 'children' } }, + slotProps: { + root: { [SLOT_COMPONENT_METADATA_SYMBOL]: {} }, + icon: { id: 'id', children: 'children', [SLOT_COMPONENT_METADATA_SYMBOL]: {} }, + }, }); }); @@ -84,13 +110,16 @@ describe('getSlotsNext', () => { }; expect( getSlotsNext({ - root: { as: 'div' }, + root: resolveShorthandMock>({ as: 'div' }), components: { root: 'div', icon: 'a' }, - icon: { id: 'id', href: 'href', children: 'children' }, + icon: resolveShorthandMock>({ id: 'id', href: 'href', children: 'children' }), }), ).toEqual({ slots: { root: 'div', icon: 'a' }, - slotProps: { root: {}, icon: { id: 'id', href: 'href', children: 'children' } }, + slotProps: { + root: { [SLOT_COMPONENT_METADATA_SYMBOL]: {} }, + icon: { id: 'id', href: 'href', children: 'children', [SLOT_COMPONENT_METADATA_SYMBOL]: {} }, + }, }); }); @@ -102,12 +131,15 @@ describe('getSlotsNext', () => { expect( getSlotsNext({ components: { root: 'div', icon: Foo }, - root: { as: 'div' }, - icon: { id: 'id', href: 'href', children: 'children' }, + root: resolveShorthandMock>({ as: 'div' }), + icon: resolveShorthandMock>({ id: 'id', href: 'href', children: 'children' }), }), ).toEqual({ slots: { root: 'div', icon: Foo }, - slotProps: { root: {}, icon: { id: 'id', href: 'href', children: 'children' } }, + slotProps: { + root: { [SLOT_COMPONENT_METADATA_SYMBOL]: {} }, + icon: { id: 'id', href: 'href', children: 'children', [SLOT_COMPONENT_METADATA_SYMBOL]: {} }, + }, }); }); @@ -120,30 +152,40 @@ describe('getSlotsNext', () => { expect( getSlotsNext({ components: { root: 'div', icon: Foo }, - root: { as: 'div' }, - icon: { id: 'bar', children: renderIcon }, + root: resolveShorthandMock>({ as: 'div' }), + icon: resolveShorthandMock>({ id: 'bar', children: renderIcon }), }), ).toEqual({ slots: { root: 'div', icon: Foo }, - slotProps: { root: {}, icon: { id: 'bar', children: renderIcon } }, + slotProps: { + root: { [SLOT_COMPONENT_METADATA_SYMBOL]: {} }, + icon: { id: 'bar', children: renderIcon, [SLOT_COMPONENT_METADATA_SYMBOL]: {} }, + }, }); }); it('can use slot children functions from resolveShorthand to replace default slot rendering', () => { type Slots = { root: Slot<'div'>; - icon: Slot<'a'>; + icon?: Slot<'a'>; }; const renderFunction = (C: React.ElementType, p: {}) => ; expect( getSlotsNext({ components: { root: 'div', icon: Foo }, - root: resolveShorthand({ as: 'div' }, { required: true }), - icon: resolveShorthand({ id: 'bar', children: renderFunction }), + root: resolveShorthand>({ as: 'div' }, { required: true }), + icon: resolveShorthand>({ id: 'bar', children: renderFunction }), }), ).toEqual({ slots: { root: 'div', icon: Foo }, - slotProps: { root: {}, icon: { children: undefined, id: 'bar', [SLOT_RENDER_FUNCTION_SYMBOL]: renderFunction } }, + slotProps: { + root: { [SLOT_COMPONENT_METADATA_SYMBOL]: {} }, + icon: { + children: undefined, + id: 'bar', + [SLOT_COMPONENT_METADATA_SYMBOL]: { renderFunction }, + }, + }, }); }); @@ -155,14 +197,18 @@ describe('getSlotsNext', () => { }; expect( getSlotsNext({ - root: { as: 'div' }, + root: resolveShorthandMock>({ as: 'div' }), components: { root: 'div', input: 'input', icon: 'a' }, - input: {}, + input: resolveShorthandMock>({}), icon: undefined, }), ).toEqual({ slots: { root: 'div', input: 'input', icon: null }, - slotProps: { root: {}, input: {}, icon: undefined }, + slotProps: { + root: { [SLOT_COMPONENT_METADATA_SYMBOL]: {} }, + input: { [SLOT_COMPONENT_METADATA_SYMBOL]: {} }, + icon: undefined, + }, }); }); }); diff --git a/packages/react-components/react-utilities/src/compose/getSlotsNext.ts b/packages/react-components/react-utilities/src/compose/getSlotsNext.ts index a972061be827da..732c70b5913357 100644 --- a/packages/react-components/react-utilities/src/compose/getSlotsNext.ts +++ b/packages/react-components/react-utilities/src/compose/getSlotsNext.ts @@ -4,6 +4,11 @@ import { ObjectSlotProps, Slots } from './getSlots'; /** * Similar to `getSlots`, main difference is that it's compatible with new custom jsx pragma + * + * @internal + * This is an internal temporary method, this method will cease to exist eventually! + * + * * ❗️❗️ **DO NOT USE IT EXTERNALLY** ❗️❗️ */ export function getSlotsNext( state: ComponentState, diff --git a/packages/react-components/react-utilities/src/compose/index.ts b/packages/react-components/react-utilities/src/compose/index.ts index fec1ddf85added..fc39aa1a92ea96 100644 --- a/packages/react-components/react-utilities/src/compose/index.ts +++ b/packages/react-components/react-utilities/src/compose/index.ts @@ -4,3 +4,4 @@ export * from './types'; export * from './isResolvedShorthand'; export * from './constants'; export * from './getSlotsNext'; +export * from './slot'; diff --git a/packages/react-components/react-utilities/src/compose/resolveShorthand.test.tsx b/packages/react-components/react-utilities/src/compose/resolveShorthand.test.tsx index 4d564af2496234..24ae758d6bc5b0 100644 --- a/packages/react-components/react-utilities/src/compose/resolveShorthand.test.tsx +++ b/packages/react-components/react-utilities/src/compose/resolveShorthand.test.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { resolveShorthand } from './resolveShorthand'; import type { Slot } from './types'; +import { SLOT_COMPONENT_METADATA_SYMBOL } from './constants'; type TestProps = { slotA?: Slot<'div'>; @@ -17,6 +18,7 @@ describe('resolveShorthand', () => { expect(resolvedProps).toEqual({ children: 'hello', + [SLOT_COMPONENT_METADATA_SYMBOL]: { elementType: 'div' }, }); }); @@ -26,6 +28,7 @@ describe('resolveShorthand', () => { expect(resolvedProps).toEqual({ children:

hello
, + [SLOT_COMPONENT_METADATA_SYMBOL]: { elementType: 'div' }, }); }); @@ -35,6 +38,7 @@ describe('resolveShorthand', () => { expect(resolvedProps).toEqual({ children: 42, + [SLOT_COMPONENT_METADATA_SYMBOL]: { elementType: 'div' }, }); }); @@ -43,7 +47,7 @@ describe('resolveShorthand', () => { const props: TestProps = { slotA }; const resolvedProps = resolveShorthand(props.slotA); - expect(resolvedProps).toEqual(slotA); + expect(resolvedProps).toEqual({ [SLOT_COMPONENT_METADATA_SYMBOL]: { elementType: 'div' } }); expect(resolvedProps).not.toBe(slotA); }); @@ -65,6 +69,6 @@ describe('resolveShorthand', () => { const props: TestProps = { slotA: undefined }; const resolvedProps = resolveShorthand(props.slotA, { required: true }); - expect(resolvedProps).toEqual({}); + expect(resolvedProps).toEqual({ [SLOT_COMPONENT_METADATA_SYMBOL]: { elementType: 'div' } }); }); }); diff --git a/packages/react-components/react-utilities/src/compose/resolveShorthand.ts b/packages/react-components/react-utilities/src/compose/resolveShorthand.ts index 76b109240de9f9..d9c1c8e48bbc95 100644 --- a/packages/react-components/react-utilities/src/compose/resolveShorthand.ts +++ b/packages/react-components/react-utilities/src/compose/resolveShorthand.ts @@ -1,6 +1,5 @@ -import { isValidElement } from 'react'; -import type { SlotRenderFunction, SlotShorthandValue, UnknownSlotProps } from './types'; -import { SLOT_RENDER_FUNCTION_SYMBOL } from './constants'; +import { slot } from './slot'; +import type { SlotShorthandValue, UnknownSlotProps } from './types'; export type ResolveShorthandOptions = Required extends true ? { required: true; defaultProps?: Props } @@ -19,32 +18,4 @@ export type ResolveShorthandFunction { - const { required = false, defaultProps } = options || {}; - if (value === null || (value === undefined && !required)) { - return undefined; - } - - let resolvedShorthand: UnknownSlotProps & { - [SLOT_RENDER_FUNCTION_SYMBOL]?: SlotRenderFunction; - } = {}; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (typeof value === 'string' || typeof value === 'number' || Array.isArray(value) || isValidElement(value)) { - resolvedShorthand.children = value; - } else if (typeof value === 'object') { - resolvedShorthand = value; - } - - resolvedShorthand = { - ...defaultProps, - ...resolvedShorthand, - }; - - if (typeof resolvedShorthand.children === 'function') { - resolvedShorthand[SLOT_RENDER_FUNCTION_SYMBOL] = resolvedShorthand.children as SlotRenderFunction; - resolvedShorthand.children = defaultProps?.children; - } - - return resolvedShorthand; -}; +export const resolveShorthand = slot as ResolveShorthandFunction; diff --git a/packages/react-components/react-utilities/src/compose/slot.ts b/packages/react-components/react-utilities/src/compose/slot.ts new file mode 100644 index 00000000000000..90817174cf59b0 --- /dev/null +++ b/packages/react-components/react-utilities/src/compose/slot.ts @@ -0,0 +1,174 @@ +import { isValidElement } from 'react'; +import type { + AsIntrinsicElement, + ComponentState, + ExtractSlotProps, + SlotComponent, + SlotComponentMetadata, + SlotPropsRecord, + SlotRenderFunction, + SlotShorthandValue, + UnknownSlotProps, +} from './types'; +import { SLOT_COMPONENT_METADATA_SYMBOL } from './constants'; +import * as React from 'react'; + +export type SlotOptions = { + elementType: + | React.ComponentType + | (Props extends AsIntrinsicElement ? As : keyof JSX.IntrinsicElements); + defaultProps?: Partial; +}; + +/** + * Creates a slot from a slot shorthand or properties (`props.SLOT_NAME` or `props` itself) + * + * @param options - optional values you can pass to alter the signature of a slot, those values are: + * + * * `elementType` - the base element type of a slot, defaults to `'div'` + * * `defaultProps` - similar to a React component declaration, you can provide a slot default properties to be merged with the shorthand/properties provided + * * `required` - a boolean that indicates if a slot will be rendered even if it's base value is `undefined`. + * By default if `props.SLOT_NAME` is `undefined` then `state.SLOT_NAME` becomes `undefined` + * and nothing will be rendered, but if `required = true` then `state.SLOT_NAME` becomes an object + * with the values provided by `options.defaultProps` (or `{}`). This is useful for cases such as providing a default content + * in case no shorthand is provided, like the case of the `expandIcon` slot for the `AccordionHeader` + * + * @example of a required nullable slot + * ```tsx + * // AccordionHeader.types.ts + * type AccordionHeaderSlots = { + * expandIcon?: Slot<'span'> + * } + * // useAccordionHeader.ts + * const state = { + * expandIcon: slot(expandIconShorthand, { + * required: true, + * elementType: 'span', + * defaultProps: { children: , 'aria-hidden': true} + * }) + * } + * // renderAccordionHeader + * state.expandIcon && + * ``` + */ +export function slot( + value: Props | SlotComponent | SlotShorthandValue | undefined, + options: { required: true } & SlotOptions, +): SlotComponent; +export function slot( + value: Props | SlotComponent | SlotShorthandValue | undefined | null, + options: { required?: boolean } & SlotOptions, +): SlotComponent | undefined; +export function slot( + value: SlotComponent, + options?: { required: true } & Partial>, +): SlotComponent; +export function slot( + value: Props | SlotComponent | SlotShorthandValue | undefined | null, + options: { required?: boolean } & Partial> = {}, +): SlotComponent | undefined { + const { required = false, defaultProps, elementType } = options; + + if (value === null || (value === undefined && !required)) { + return undefined; + } + + const metadata: SlotComponentMetadata = isSlot(value) + ? value[SLOT_COMPONENT_METADATA_SYMBOL] + : { + elementType: elementType ?? ('div' as React.ElementType as React.ComponentType), + }; + + /** + * Casting is required here as SlotComponent is a function, not an object. + * Although SlotComponent has a function signature, it is still just an object. + * This is required to make a slot callable (JSX compatible), this is the exact same approach + * that is used on `@types/react` components + */ + const propsWithMetadata = { + ...defaultProps, + [SLOT_COMPONENT_METADATA_SYMBOL]: metadata, + } as SlotComponent; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (typeof value === 'string' || typeof value === 'number' || Array.isArray(value) || isValidElement(value)) { + propsWithMetadata.children = value; + } else if (typeof value === 'object') { + Object.assign(propsWithMetadata, value); + if (typeof value.children === 'function') { + metadata.renderFunction = value.children as SlotRenderFunction; + propsWithMetadata.children = defaultProps?.children; + } + } + + return propsWithMetadata; +} + +/** + * Guard method to ensure a given element is a slot. This is mainly used internally + */ +export function isSlot(element: unknown): element is SlotComponent { + return Boolean((element as {} | undefined)?.hasOwnProperty(SLOT_COMPONENT_METADATA_SYMBOL)); +} + +type SlotComponents = { + [K in keyof Slots]: SlotComponent>; +}; + +/** + * @internal + * Assertion method to ensure state slots properties are properly declared. + * A properly declared slot must be declared by using the `slot` method. + * + * @example + * ```tsx + * export const renderInput_unstable = (state: InputState) => { + assertSlots(state); + return ( + + {state.contentBefore && } + + {state.contentAfter && } + + ); + }; + * ``` + */ +export function assertSlots(state: unknown): asserts state is SlotComponents { + /** + * This verification is not necessary in production + * as we're verifying static properties that will not change between environments + */ + if (process.env.NODE_ENV !== 'production') { + const typedState = state as ComponentState; + for (const slotName of Object.keys(typedState.components)) { + const slotElement = typedState[slotName]; + if (slotElement === undefined) { + continue; + } + if (!isSlot(slotElement)) { + throw new Error( + `${assertSlots.name} error: state.${slotName} is not a slot.\n` + + `Be sure to create slots properly by using ${slot.name}`, + ); + } else { + const metadata = slotElement[SLOT_COMPONENT_METADATA_SYMBOL]; + if (metadata.elementType !== typedState.components[slotName]) { + throw new TypeError( + `${assertSlots.name} error: state.${slotName} element type differs from state.components.${slotName}, ${metadata.elementType} !== ${typedState.components[slotName]}. \n` + + `Be sure to create slots properly by using ${slot.name} with the correct elementType`, + ); + } + } + } + } +} + +export function getSlotMetadata

( + value: P | SlotComponent

| SlotShorthandValue | undefined | null, +): SlotComponentMetadata

{ + if (isSlot

(value)) { + return value[SLOT_COMPONENT_METADATA_SYMBOL]; + } + return { elementType: 'div' as React.ElementType

as React.ComponentType

}; +} diff --git a/packages/react-components/react-utilities/src/compose/types.ts b/packages/react-components/react-utilities/src/compose/types.ts index 8169dad5cc4e95..995caaaa92f4a5 100644 --- a/packages/react-components/react-utilities/src/compose/types.ts +++ b/packages/react-components/react-utilities/src/compose/types.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import { SLOT_COMPONENT_METADATA_SYMBOL } from './constants'; export type SlotRenderFunction = ( Component: React.ElementType, @@ -233,3 +234,18 @@ export type ForwardRefComponent = ObscureEventName extends keyof Props export type SlotClassNames = { [SlotName in keyof Slots]-?: string; }; + +export type SlotComponent = Props & { + /** + * **NOTE**: Slot components are not callable. + */ + (props: React.PropsWithChildren<{}>): React.ReactElement | null; + [SLOT_COMPONENT_METADATA_SYMBOL]: Readonly>; +}; + +export type SlotComponentMetadata = { + renderFunction?: SlotRenderFunction; + elementType: + | React.ComponentType + | (Props extends AsIntrinsicElement ? As : keyof JSX.IntrinsicElements); +}; diff --git a/packages/react-components/react-utilities/src/index.ts b/packages/react-components/react-utilities/src/index.ts index a9a94d94460dbb..f824f90a33678d 100644 --- a/packages/react-components/react-utilities/src/index.ts +++ b/packages/react-components/react-utilities/src/index.ts @@ -1,9 +1,12 @@ export { + slot, + isSlot, getSlots, getSlotsNext, + assertSlots, resolveShorthand, isResolvedShorthand, - SLOT_RENDER_FUNCTION_SYMBOL, + SLOT_COMPONENT_METADATA_SYMBOL, } from './compose/index'; export type { ExtractSlotProps, @@ -19,6 +22,9 @@ export type { SlotRenderFunction, SlotShorthandValue, UnknownSlotProps, + SlotComponent, + SlotComponentMetadata, + SlotOptions, } from './compose/index'; export { From ee5a2589308a89f546a6b2d4f839154f813d4611 Mon Sep 17 00:00:00 2001 From: Bernardo Sunderhus Date: Mon, 29 May 2023 19:19:02 +0000 Subject: [PATCH 2/4] chore: updates v9 packages --- .../components/Accordion/renderAccordion.tsx | 10 +- .../src/components/Accordion/useAccordion.ts | 15 +-- .../AccordionHeader/renderAccordionHeader.tsx | 20 ++-- .../AccordionHeader/useAccordionHeader.tsx | 27 +++--- .../AccordionItem/renderAccordionItem.tsx | 10 +- .../AccordionItem/useAccordionItem.ts | 13 ++- .../AccordionPanel/renderAccordionPanel.tsx | 6 +- .../AccordionPanel/useAccordionPanel.ts | 15 +-- .../src/components/Alert/renderAlert.tsx | 16 +-- .../src/components/Alert/useAlert.tsx | 39 +++----- .../react-components/react-aria/package.json | 1 + .../src/button/useARIAButtonProps.test.tsx | 16 ++- .../src/button/useARIAButtonShorthand.ts | 11 ++- .../stories/useARIAButton/index.stories.tsx | 23 +++-- .../src/components/Avatar/renderAvatar.tsx | 16 +-- .../src/components/Avatar/useAvatar.tsx | 97 +++++++------------ .../AvatarGroup/renderAvatarGroup.tsx | 6 +- .../components/AvatarGroup/useAvatarGroup.tsx | 31 +++--- .../AvatarGroupItem/renderAvatarGroupItem.tsx | 12 +-- .../AvatarGroupItem/useAvatarGroupItem.ts | 11 ++- .../renderAvatarGroupPopover.tsx | 23 +++-- .../useAvatarGroupPopover.tsx | 35 ++++--- .../src/components/Badge/renderBadge.tsx | 12 +-- .../src/components/Badge/useBadge.ts | 15 +-- .../PresenceBadge/usePresenceBadge.tsx | 5 +- .../Breadcrumb/renderBreadcrumb.tsx | 11 +-- .../components/Breadcrumb/useBreadcrumb.ts | 19 ++-- .../renderBreadcrumbDivider.tsx | 6 +- .../useBreadcrumbDivider.tsx | 17 ++-- .../BreadcrumbItem/renderBreadcrumbItem.tsx | 6 +- .../BreadcrumbItem/useBreadcrumbItem.ts | 13 ++- .../BreadcrumbLink/renderBreadcrumbLink.tsx | 12 +-- .../BreadcrumbLink/useBreadcrumbLink.ts | 23 +++-- .../src/components/Button/renderButton.tsx | 12 +-- .../src/components/Button/useButton.ts | 40 ++++---- .../CompoundButton/renderCompoundButton.tsx | 21 ++-- .../CompoundButton/useCompoundButton.ts | 6 +- .../MenuButton/renderMenuButton.tsx | 14 +-- .../components/MenuButton/useMenuButton.tsx | 5 +- .../SplitButton/renderSplitButton.tsx | 12 +-- .../components/SplitButton/useSplitButton.ts | 27 ++---- .../src/components/Card/renderCard.tsx | 14 +-- .../react-card/src/components/Card/useCard.ts | 19 ++-- .../src/components/Card/useCardSelectable.ts | 8 +- .../CardFooter/renderCardFooter.tsx | 12 +-- .../components/CardFooter/useCardFooter.ts | 15 +-- .../CardHeader/renderCardHeader.tsx | 16 +-- .../components/CardHeader/useCardHeader.ts | 22 +++-- .../CardPreview/renderCardPreview.tsx | 12 +-- .../components/CardPreview/useCardPreview.ts | 15 +-- .../components/Checkbox/renderCheckbox.tsx | 16 +-- .../src/components/Checkbox/useCheckbox.tsx | 14 ++- .../components/Combobox/renderCombobox.tsx | 18 ++-- .../src/components/Combobox/useCombobox.tsx | 36 +++---- .../components/Dropdown/renderDropdown.tsx | 22 ++--- .../src/components/Dropdown/useDropdown.tsx | 30 +++--- .../src/components/Listbox/renderListbox.tsx | 6 +- .../src/components/Listbox/useListbox.ts | 27 ++++-- .../src/components/Option/renderOption.tsx | 12 +-- .../src/components/Option/useOption.tsx | 24 +++-- .../OptionGroup/renderOptionGroup.tsx | 12 +-- .../components/OptionGroup/useOptionGroup.ts | 20 ++-- .../DataGridBody/renderDataGridBody.tsx | 8 +- .../DatePicker/renderDatePicker.tsx | 21 ++-- .../components/DatePicker/useDatePicker.tsx | 32 ++---- .../DialogActions/renderDialogActions.tsx | 6 +- .../DialogActions/useDialogActions.ts | 13 ++- .../DialogBody/renderDialogBody.tsx | 6 +- .../components/DialogBody/useDialogBody.ts | 13 ++- .../DialogContent/renderDialogContent.tsx | 6 +- .../DialogContent/useDialogContent.ts | 13 ++- .../DialogSurface/renderDialogSurface.tsx | 8 +- .../DialogSurface/useDialogSurface.ts | 30 +++--- .../DialogTitle/renderDialogTitle.tsx | 8 +- .../components/DialogTitle/useDialogTitle.tsx | 19 ++-- .../src/components/Divider/renderDivider.tsx | 10 +- .../src/components/Divider/useDivider.ts | 22 +++-- .../src/components/Drawer/renderDrawer.tsx | 6 +- .../src/components/Drawer/useDrawer.ts | 5 +- .../DrawerBody/renderDrawerBody.tsx | 11 ++- .../components/DrawerBody/useDrawerBody.ts | 13 ++- .../DrawerFooter/renderDrawerFooter.tsx | 11 ++- .../DrawerFooter/useDrawerFooter.ts | 13 ++- .../DrawerHeader/renderDrawerHeader.tsx | 11 ++- .../DrawerHeader/useDrawerHeader.ts | 13 ++- .../renderDrawerHeaderNavigation.tsx | 11 ++- .../useDrawerHeaderNavigation.ts | 13 ++- .../renderDrawerHeaderTitle.tsx | 17 ++-- .../DrawerHeaderTitle/useDrawerHeaderTitle.ts | 19 ++-- .../DrawerInline/renderDrawerInline.tsx | 11 ++- .../DrawerInline/useDrawerInline.ts | 13 ++- .../DrawerOverlay/renderDrawerOverlay.tsx | 12 ++- .../DrawerOverlay/useDrawerOverlay.ts | 13 ++- .../src/components/Field/renderField.tsx | 23 ++--- .../src/components/Field/useField.tsx | 49 +++------- .../src/components/Image/renderImage.tsx | 6 +- .../src/components/Image/useImage.ts | 15 +-- .../InfoButton/renderInfoButton.tsx | 13 ++- .../components/InfoButton/useInfoButton.tsx | 27 +++--- .../components/InfoLabel/renderInfoLabel.tsx | 12 +-- .../src/components/InfoLabel/useInfoLabel.ts | 15 ++- .../src/components/Input/renderInput.tsx | 14 +-- .../src/components/Input/useInput.ts | 17 ++-- .../src/components/Label/renderLabel.tsx | 10 +- .../src/components/Label/useLabel.tsx | 8 +- .../src/components/Link/renderLink.tsx | 6 +- .../react-link/src/components/Link/useLink.ts | 17 ++-- .../MenuDivider/renderMenuDivider.tsx | 6 +- .../components/MenuDivider/useMenuDivider.ts | 17 ++-- .../components/MenuGroup/renderMenuGroup.tsx | 6 +- .../src/components/MenuGroup/useMenuGroup.ts | 17 ++-- .../MenuGroupHeader/renderMenuGroupHeader.tsx | 6 +- .../MenuGroupHeader/useMenuGroupHeader.ts | 15 +-- .../components/MenuItem/renderMenuItem.tsx | 18 ++-- .../src/components/MenuItem/useMenuItem.tsx | 79 ++++++++------- .../renderMenuItemCheckbox.tsx | 16 +-- .../MenuItemCheckbox/useMenuItemCheckbox.tsx | 5 +- .../MenuItemRadio/renderMenuItemRadio.tsx | 16 +-- .../MenuItemRadio/useMenuItemRadio.tsx | 5 +- .../components/MenuList/renderMenuList.tsx | 6 +- .../src/components/MenuList/useMenuList.ts | 18 ++-- .../MenuPopover/renderMenuPopover.tsx | 8 +- .../components/MenuPopover/useMenuPopover.ts | 35 +++---- .../MenuSplitGroup/renderMenuSplitGroup.tsx | 6 +- .../MenuSplitGroup/useMenuSplitGroup.ts | 17 ++-- .../src/components/ItemLayout/ItemLayout.tsx | 43 ++++---- .../src/components/Persona/renderPersona.tsx | 20 ++-- .../src/components/Persona/usePersona.ts | 37 +++---- .../PopoverSurface/renderPopoverSurface.tsx | 10 +- .../PopoverSurface/usePopoverSurface.ts | 19 ++-- .../ProgressBar/renderProgressBar.tsx | 6 +- .../components/ProgressBar/useProgressBar.tsx | 35 +++---- .../FluentProvider/renderFluentProvider.tsx | 11 ++- .../FluentProvider/useFluentProvider.ts | 15 +-- .../src/components/Radio/renderRadio.tsx | 14 +-- .../src/components/Radio/useRadio.tsx | 36 +++---- .../RadioGroup/renderRadioGroup.tsx | 6 +- .../components/RadioGroup/useRadioGroup.ts | 7 +- .../components/SearchBox/renderSearchBox.tsx | 6 +- .../src/components/SearchBox/useSearchBox.ts | 13 ++- .../src/components/Select/renderSelect.tsx | 12 +-- .../src/components/Select/useSelect.tsx | 11 ++- .../components/Skeleton/renderSkeleton.tsx | 6 +- .../src/components/Skeleton/useSkeleton.ts | 29 +++--- .../SkeletonItem/renderSkeletonItem.tsx | 6 +- .../SkeletonItem/useSkeletonItem.tsx | 25 ++--- .../src/components/Slider/renderSlider.tsx | 14 +-- .../src/components/Slider/useSlider.ts | 12 ++- .../SpinButton/renderSpinButton.tsx | 14 +-- .../components/SpinButton/useSpinButton.tsx | 14 ++- .../src/components/Spinner/renderSpinner.tsx | 19 ++-- .../src/components/Spinner/useSpinner.tsx | 36 ++----- .../src/components/Switch/renderSwitch.tsx | 16 +-- .../src/components/Switch/useSwitch.tsx | 51 +++------- .../DataGridBody/renderDataGridBody.tsx | 8 +- .../DataGridRow/renderDataGridRow.tsx | 10 +- .../components/DataGridRow/useDataGridRow.tsx | 4 +- .../src/components/Table/renderTable.tsx | 6 +- .../src/components/Table/useTable.ts | 15 +-- .../components/TableBody/renderTableBody.tsx | 6 +- .../src/components/TableBody/useTableBody.ts | 15 +-- .../components/TableCell/renderTableCell.tsx | 6 +- .../src/components/TableCell/useTableCell.ts | 15 +-- .../renderTableCellActions.tsx | 6 +- .../TableCellActions/useTableCellActions.ts | 13 ++- .../TableCellLayout/renderTableCellLayout.tsx | 23 ++--- .../TableCellLayout/useTableCellLayout.ts | 12 +-- .../TableHeader/renderTableHeader.tsx | 6 +- .../components/TableHeader/useTableHeader.ts | 15 +-- .../TableHeaderCell/renderTableHeaderCell.tsx | 18 ++-- .../TableHeaderCell/useTableHeaderCell.tsx | 22 +++-- .../renderTableResizeHandle.tsx | 6 +- .../TableResizeHandle/useTableResizeHandle.ts | 15 +-- .../components/TableRow/renderTableRow.tsx | 6 +- .../src/components/TableRow/useTableRow.ts | 15 +-- .../renderTableSelectionCell.tsx | 15 ++- .../useTableSelectionCell.tsx | 8 +- .../react-tabs/etc/react-tabs.api.md | 3 +- .../src/components/Tab/Tab.types.ts | 15 +-- .../src/components/Tab/renderTab.tsx | 16 ++- .../react-tabs/src/components/Tab/useTab.ts | 46 +++++---- .../src/components/Tab/useTabStyles.styles.ts | 4 +- .../src/components/TabList/renderTabList.tsx | 8 +- .../src/components/TabList/useTabList.ts | 18 ++-- .../src/components/Tag/renderTag.tsx | 21 ++-- .../react-tags/src/components/Tag/useTag.tsx | 31 +++--- .../components/TagButton/renderTagButton.tsx | 31 +++--- .../src/components/TagButton/useTagButton.tsx | 44 +++++---- .../components/TagGroup/renderTagGroup.tsx | 6 +- .../src/components/TagGroup/useTagGroup.ts | 15 +-- .../src/components/Text/renderText.tsx | 6 +- .../react-text/src/components/Text/useText.ts | 15 +-- .../components/Textarea/renderTextarea.tsx | 10 +- .../src/components/Textarea/useTextarea.ts | 13 +-- .../components/AriaLive/renderAriaLive.tsx | 8 +- .../src/components/AriaLive/useAriaLive.ts | 8 +- .../ToastAlert/renderToastAlert.tsx | 14 +-- .../components/ToastAlert/useToastAlert.tsx | 25 +++-- .../src/components/Toolbar/renderToolbar.tsx | 6 +- .../src/components/Toolbar/useToolbar.ts | 19 ++-- .../ToolbarGroup/renderToolbarGroup.tsx | 6 +- .../ToolbarGroup/useToolbarGroup.ts | 15 +-- .../src/components/Tooltip/renderTooltip.tsx | 8 +- .../src/components/Tooltip/useTooltip.tsx | 5 +- .../src/components/Tree/renderTree.tsx | 6 +- .../src/components/Tree/useRootTree.ts | 15 +-- .../src/components/Tree/useSubtree.ts | 15 +-- .../components/TreeItem/renderTreeItem.tsx | 10 +- .../src/components/TreeItem/useTreeItem.ts | 36 +++---- .../TreeItemAside/renderTreeItemAside.tsx | 10 +- .../TreeItemAside/useTreeItemAside.ts | 13 ++- .../TreeItemLayout/renderTreeItemLayout.tsx | 16 +-- .../TreeItemLayout/useTreeItemLayout.tsx | 14 ++- .../renderTreeItemPersonaLayout.tsx | 20 ++-- .../useTreeItemPersonaLayout.tsx | 10 +- .../D_flatTree/Virtualization.stories.tsx | 14 ++- .../Virtualizer/renderVirtualizer.tsx | 16 +-- .../components/Virtualizer/useVirtualizer.ts | 14 ++- .../renderVirtualizerScrollView.tsx | 6 +- .../useVirtualizerScrollView.ts | 5 +- .../renderVirtualizerScrollViewDynamic.tsx | 6 +- .../useVirtualizerScrollViewDynamic.ts | 5 +- 222 files changed, 1793 insertions(+), 1696 deletions(-) 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..5d053485573c10 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.5", "@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 975a30043db570..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 }>({ + 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..0928cb00492db7 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,12 @@ 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); +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-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..543f398f9b9522 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'; @@ -25,19 +25,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/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 { - 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 : (