diff --git a/change/@fluentui-react-headless-components-preview-69fc269f-3430-4627-a4bf-8c12ae5f7f06.json b/change/@fluentui-react-headless-components-preview-69fc269f-3430-4627-a4bf-8c12ae5f7f06.json new file mode 100644 index 00000000000000..5cba052b80edfc --- /dev/null +++ b/change/@fluentui-react-headless-components-preview-69fc269f-3430-4627-a4bf-8c12ae5f7f06.json @@ -0,0 +1,6 @@ +{ + "type": "patch", + "comment": "feat: expose ToastTitle intent state via data attribute", + "packageName": "@fluentui/react-headless-components-preview", + "email": "dmytrokirpa@microsoft.com" +} diff --git a/packages/react-components/react-headless-components-preview/library/etc/toast.api.md b/packages/react-components/react-headless-components-preview/library/etc/toast.api.md index 212a33c4b9a33a..9d248b90ee920b 100644 --- a/packages/react-components/react-headless-components-preview/library/etc/toast.api.md +++ b/packages/react-components/react-headless-components-preview/library/etc/toast.api.md @@ -34,9 +34,9 @@ import { ToastPosition } from '@fluentui/react-toast'; import { ToastBaseProps as ToastProps } from '@fluentui/react-toast'; import { ToastSlots } from '@fluentui/react-toast'; import { ToastStatus } from '@fluentui/react-toast'; +import type { ToastTitleBaseState } from '@fluentui/react-toast'; import { ToastTitleBaseProps as ToastTitleProps } from '@fluentui/react-toast'; import { ToastTitleSlots } from '@fluentui/react-toast'; -import { ToastTitleBaseState as ToastTitleState } from '@fluentui/react-toast'; import { useToastBodyBase_unstable as useToastBody } from '@fluentui/react-toast'; import { useToastContainerContext } from '@fluentui/react-toast'; import { useToastController } from '@fluentui/react-toast'; @@ -152,7 +152,12 @@ export { ToastTitleProps } export { ToastTitleSlots } -export { ToastTitleState } +// @public (undocumented) +export type ToastTitleState = ToastTitleBaseState & { + media?: ToastTitleBaseState['media'] & { + 'data-intent'?: ToastTitleBaseState['intent']; + }; +}; // @public export const useToast: (props: ToastProps, ref: React_2.Ref) => ToastState; diff --git a/packages/react-components/react-headless-components-preview/library/src/components/Toast/Toast.cy.tsx b/packages/react-components/react-headless-components-preview/library/src/components/Toast/Toast.cy.tsx index 87ee5d55f1edb1..3dcafa3b1068e1 100644 --- a/packages/react-components/react-headless-components-preview/library/src/components/Toast/Toast.cy.tsx +++ b/packages/react-components/react-headless-components-preview/library/src/components/Toast/Toast.cy.tsx @@ -8,10 +8,9 @@ import { Provider } from '../Provider'; /** * Selectors used by the headless tests. Unlike the styled v9 layer, the headless * Toast does not emit Griffel class names — we target structural roles and the - * `data-intent` attribute the headless `Toast` adds to its root. + * structural roles on the headless `ToastContainer` root. */ const TOAST_CONTAINER = '[role="listitem"]'; -const TOAST = '[data-intent]'; const mount = (element: JSXElement) => mountBase( @@ -43,7 +42,7 @@ describe('Toast (headless)', () => { }; mount(); - cy.get('button').click().get(TOAST).should('exist'); + cy.get('button').click().get(TOAST_CONTAINER).should('exist'); }); it('should dismiss toast', () => { @@ -73,8 +72,8 @@ describe('Toast (headless)', () => { }; mount(); - cy.get('#make').click().get(TOAST).should('exist'); - cy.get('#dismiss').click().get(TOAST).should('not.exist'); + cy.get('#make').click().get(TOAST_CONTAINER).should('exist'); + cy.get('#dismiss').click().get(TOAST_CONTAINER).should('not.exist'); }); it('should dismiss all toasts', () => { @@ -105,8 +104,8 @@ describe('Toast (headless)', () => { }; mount(); - cy.get('#make').click().get(TOAST).should('have.length', 5); - cy.get('#dismiss').click().get(TOAST).should('not.exist'); + cy.get('#make').click().get(TOAST_CONTAINER).should('have.length', 5); + cy.get('#dismiss').click().get(TOAST_CONTAINER).should('not.exist'); }); it('should play and pause toast', () => { @@ -138,9 +137,9 @@ describe('Toast (headless)', () => { }; mount(); - cy.get('#make').click().get(TOAST).should('exist'); - cy.get('#pause').click().wait(1000).get(TOAST).should('exist'); - cy.get('#play').click().get(TOAST).should('not.exist'); + cy.get('#make').click().get(TOAST_CONTAINER).should('exist'); + cy.get('#pause').click().wait(1000).get(TOAST_CONTAINER).should('exist'); + cy.get('#play').click().get(TOAST_CONTAINER).should('not.exist'); }); it('should update toast content', () => { @@ -178,7 +177,7 @@ describe('Toast (headless)', () => { }; mount(); - cy.get('#make').click().get(TOAST).should('exist'); + cy.get('#make').click().get(TOAST_CONTAINER).should('exist'); cy.get('#update').click().get('body').contains('Foo'); }); @@ -204,7 +203,7 @@ describe('Toast (headless)', () => { }; mount(); - cy.get('#make').click().get(TOAST).trigger('mouseenter').wait(700).get(TOAST).should('exist'); + cy.get('#make').click().get(TOAST_CONTAINER).trigger('mouseenter').wait(700).get(TOAST_CONTAINER).should('exist'); }); it('should follow lifecycle', () => { diff --git a/packages/react-components/react-headless-components-preview/library/src/components/Toast/ToastTitle/ToastTitle.tsx b/packages/react-components/react-headless-components-preview/library/src/components/Toast/ToastTitle/ToastTitle.tsx index 2858ce58e896b3..20abfa7368ef50 100644 --- a/packages/react-components/react-headless-components-preview/library/src/components/Toast/ToastTitle/ToastTitle.tsx +++ b/packages/react-components/react-headless-components-preview/library/src/components/Toast/ToastTitle/ToastTitle.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import type { ForwardRefComponent } from '@fluentui/react-utilities'; -import type { ToastTitleProps } from './ToastTitle.types'; +import type { ToastTitleProps, ToastTitleState } from './ToastTitle.types'; import { useToastTitle } from './useToastTitle'; import { renderToastTitle } from './renderToastTitle'; @@ -10,7 +10,13 @@ import { renderToastTitle } from './renderToastTitle'; * Represents the title of a toast, which is a brief summary or headline for the toast message. */ export const ToastTitle: ForwardRefComponent = React.forwardRef((props, ref) => { - const state = useToastTitle(props, ref); + const state: ToastTitleState = useToastTitle(props, ref); + + if (state.media) { + // eslint-disable-next-line react-hooks/immutability + state.media['data-intent'] = state.intent; + } + return renderToastTitle(state); }); diff --git a/packages/react-components/react-headless-components-preview/library/src/components/Toast/ToastTitle/ToastTitle.types.ts b/packages/react-components/react-headless-components-preview/library/src/components/Toast/ToastTitle/ToastTitle.types.ts index 61d0ac9d9f39f1..61cecd45842ff4 100644 --- a/packages/react-components/react-headless-components-preview/library/src/components/Toast/ToastTitle/ToastTitle.types.ts +++ b/packages/react-components/react-headless-components-preview/library/src/components/Toast/ToastTitle/ToastTitle.types.ts @@ -1,5 +1,11 @@ -export type { - ToastTitleBaseProps as ToastTitleProps, - ToastTitleBaseState as ToastTitleState, - ToastTitleSlots, -} from '@fluentui/react-toast'; +import type { ToastTitleBaseState } from '@fluentui/react-toast'; +export type { ToastTitleBaseProps as ToastTitleProps, ToastTitleSlots } from '@fluentui/react-toast'; + +export type ToastTitleState = ToastTitleBaseState & { + media?: ToastTitleBaseState['media'] & { + /** + * The intent of the toast, used for styling purposes. + */ + 'data-intent'?: ToastTitleBaseState['intent']; + }; +};