diff --git a/change/@fluentui-web-components-6860ddb3-4e1b-4679-9c94-1dfcf4bf40ca.json b/change/@fluentui-web-components-6860ddb3-4e1b-4679-9c94-1dfcf4bf40ca.json new file mode 100644 index 00000000000000..4510f8e4742102 --- /dev/null +++ b/change/@fluentui-web-components-6860ddb3-4e1b-4679-9c94-1dfcf4bf40ca.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Adds MessageBar component to Fluent Web Components", + "packageName": "@fluentui/web-components", + "email": "ryan@ryanmerrill.net", + "dependentChangeType": "patch" +} diff --git a/packages/web-components/package.json b/packages/web-components/package.json index b594145bc865e1..6d79de2e08c886 100644 --- a/packages/web-components/package.json +++ b/packages/web-components/package.json @@ -114,6 +114,10 @@ "types": "./dist/dts/menu-item/define.d.ts", "default": "./dist/esm/menu-item/define.js" }, + "./message-bar.js": { + "types": "./dist/dts/message-bar/define.d.ts", + "default": "./dist/esm/message-bar/define.js" + }, "./progress-bar.js": { "types": "./dist/dts/progress-bar/define.d.ts", "default": "./dist/esm/progress-bar/define.js" diff --git a/packages/web-components/src/index-rollup.ts b/packages/web-components/src/index-rollup.ts index 7470cdcf2c287f..bab50825f93c3e 100644 --- a/packages/web-components/src/index-rollup.ts +++ b/packages/web-components/src/index-rollup.ts @@ -19,6 +19,7 @@ import './menu-button/define.js'; import './menu-item/define.js'; import './menu-list/define.js'; import './menu/define.js'; +import './message-bar/define.js'; import './progress-bar/define.js'; import './radio-group/define.js'; import './radio/define.js'; diff --git a/packages/web-components/src/message-bar/README.md b/packages/web-components/src/message-bar/README.md index 66e08d3a922b8b..d831d0f61c8a8e 100644 --- a/packages/web-components/src/message-bar/README.md +++ b/packages/web-components/src/message-bar/README.md @@ -15,37 +15,31 @@ The Fabric WC3 MessageBar extends `FASTElement` ### Template ```html - + + ``` ### **Variables** -| Name | Type | Description | -| --------------------- | ---------------------------------- | ----------------------------------------- | -| `MessageBarLayout` | `multiline` `singleline` | How text flows within the MessageBar | -| `MessageBarShape` | `rounded` `square` | Shapes for the MessageBar | -| `MessageBarIntent` | `success` `warning` `error` `info` | Intents for the MessageBar | -| `MessageBarPolitness` | `assertive` `polite` | Sets the alert style for aria-live region | +| Name | Type | Description | +| ------------------ | ---------------------------------- | ------------------------------------ | +| `MessageBarLayout` | `multiline` `singleline` | How text flows within the MessageBar | +| `MessageBarShape` | `rounded` `square` | Shapes for the MessageBar | +| `MessageBarIntent` | `success` `warning` `error` `info` | Intents for the MessageBar | ### **Attributes** -| Name | Type | Default | Description | -| ------------ | ---------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------- | -| `layout` | `multiline` `singleline` | `singleline` | Determines if the MessageBar should opt out of automatic reflow for applications that have an existing responsive design mechanism | -| `shape` | `square` `rounded` | `rounded` | Determines the shape of the corners on the MessageBar | -| `intent` | `success` `warning` `error` `info` | `info` | Sets the intent type for the MessageBar | -| `politeness` | `assertive` `polite` | `polite` | Sets the alert style for aria-live region | +| Name | Type | Default | Description | +| -------- | ---------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------- | +| `layout` | `multiline` `singleline` | `singleline` | Determines if the MessageBar should opt out of automatic reflow for applications that have an existing responsive design mechanism | +| `shape` | `square` `rounded` | `rounded` | Determines the shape of the corners on the MessageBar | +| `intent` | `success` `warning` `error` `info` | `info` | Sets the intent type for the MessageBar | ### **Events** @@ -61,11 +55,12 @@ The Fabric WC3 MessageBar extends `FASTElement` ### **Slots** -| Name | Description | -| --------- | ------------------------------------- | -| | The default slot for the main content | -| `actions` | The slot for optional action buttons | -| `close` | The slot for a custom close icon | +| Name | Description | +| --------- | ----------------------------------------- | +| | The default slot for the main content | +| `icon` | The slot for the icon to represent intent | +| `actions` | The slot for optional action buttons | +| `dismiss` | The slot for a custom close icon | ## **Accessiblity** @@ -77,7 +72,7 @@ The Fabric WC3 MessageBar extends `FASTElement` - `aria-live` - - The `aria-live` attribute should be used to associate the MessageBar with the appropriate `aria-live` value to announce content changes to assistive technology devices. Corresponds to `politeness` attribute. + - The `aria-live` attribute should be used to associate the MessageBar with the appropriate `aria-live` value to announce content changes to assistive technology devices. - `aria-labelledby` diff --git a/packages/web-components/src/message-bar/define.ts b/packages/web-components/src/message-bar/define.ts new file mode 100644 index 00000000000000..ef07193254fd22 --- /dev/null +++ b/packages/web-components/src/message-bar/define.ts @@ -0,0 +1,4 @@ +import { FluentDesignSystem } from '../fluent-design-system.js'; +import { definition } from './message-bar.definition.js'; + +definition.define(FluentDesignSystem.registry); diff --git a/packages/web-components/src/message-bar/index.ts b/packages/web-components/src/message-bar/index.ts new file mode 100644 index 00000000000000..2b2d8e9ad60301 --- /dev/null +++ b/packages/web-components/src/message-bar/index.ts @@ -0,0 +1,5 @@ +export { definition as MessageBarDefinition } from './message-bar.definition.js'; +export { MessageBar } from './message-bar.js'; +export { MessageBarIntent, MessageBarLayout, MessageBarShape } from './message-bar.options.js'; +export { styles as MessageBarStyles } from './message-bar.styles.js'; +export { template as MessageBarTemplate } from './message-bar.template.js'; diff --git a/packages/web-components/src/message-bar/message-bar.bench.ts b/packages/web-components/src/message-bar/message-bar.bench.ts new file mode 100644 index 00000000000000..24528469295c41 --- /dev/null +++ b/packages/web-components/src/message-bar/message-bar.bench.ts @@ -0,0 +1,65 @@ +import { FluentDesignSystem } from '../fluent-design-system.js'; +import { definition as buttonDefinition } from '../button/button.definition.js'; +import { definition } from './message-bar.definition.js'; + +definition.define(FluentDesignSystem.registry); +buttonDefinition.define(FluentDesignSystem.registry); + +const dismissed20Regular = ` + +`; + +const infoIcon = ` + + + +`; + +const itemRenderer = () => { + const messageBar = document.createElement('fluent-message-bar'); + messageBar.appendChild(document.createTextNode('message-bar')); + + // Create and append icon slot + const icon = document.createElement('span'); + icon.setAttribute('slot', 'icon'); + icon.innerHTML = infoIcon; + messageBar.appendChild(icon); + + // Create and append content slot + const content = document.createElement('span'); + content.setAttribute('slot', 'content'); + content.appendChild(document.createTextNode('Accordion item')); + messageBar.appendChild(content); + + // Create and append dismiss slot + const dismiss = document.createElement('span'); + dismiss.setAttribute('slot', 'dismiss'); + dismiss.innerHTML = dismissed20Regular; // replace with your SVG content + messageBar.appendChild(dismiss); + + // Create and append actions slot + const actions = document.createElement('fluent-button'); + actions.setAttribute('slot', 'actions'); + actions.appendChild(document.createTextNode('Button')); + messageBar.appendChild(actions); + + return messageBar; +}; + +export default itemRenderer; +export { tests } from '../utils/benchmark-wrapper.js'; diff --git a/packages/web-components/src/message-bar/message-bar.definition.ts b/packages/web-components/src/message-bar/message-bar.definition.ts new file mode 100644 index 00000000000000..e58c7c39e231d8 --- /dev/null +++ b/packages/web-components/src/message-bar/message-bar.definition.ts @@ -0,0 +1,20 @@ +import { FluentDesignSystem } from '../fluent-design-system.js'; +import { MessageBar } from './message-bar.js'; +import { styles } from './message-bar.styles.js'; +import { template } from './message-bar.template.js'; + +/** + * The Fluent MessageBar Element definition. + * + * @public + * @remarks + * HTML Element: `` + */ +export const definition = MessageBar.compose({ + name: `${FluentDesignSystem.prefix}-message-bar`, + template, + styles, + shadowOptions: { + mode: FluentDesignSystem.shadowRootMode, + }, +}); diff --git a/packages/web-components/src/message-bar/message-bar.integration.spec.ts b/packages/web-components/src/message-bar/message-bar.integration.spec.ts new file mode 100644 index 00000000000000..34e1a747b5e952 --- /dev/null +++ b/packages/web-components/src/message-bar/message-bar.integration.spec.ts @@ -0,0 +1,103 @@ +import { expect, test } from '@playwright/test'; +import { fixtureURL } from '../helpers.tests.js'; +import type { MessageBar } from './message-bar.js'; + +test.describe('Message Bar', () => { + test.beforeEach(async ({ page }) => { + await page.goto(fixtureURL('components-messagebar--message')); + await page.waitForFunction(() => customElements.whenDefined('fluent-message-bar')); + }); + + test('should include a role of status', async ({ page }) => { + const element = page.locator('fluent-message-bar'); + + await expect(element).toHaveJSProperty('elementInternals.role', 'status'); + }); + + test('should set and retrieve the `intent` property correctly', async ({ page }) => { + const element = page.locator('fluent-message-bar'); + + await element.evaluate((node: MessageBar) => { + node.intent = 'success'; + }); + + await expect(element).toHaveJSProperty('intent', 'success'); + expect(await element.evaluate((node: MessageBar) => node.elementInternals.states.has('success'))).toBe(true); + + await element.evaluate((node: MessageBar) => { + node.intent = 'warning'; + }); + + await expect(element).toHaveJSProperty('intent', 'warning'); + expect(await element.evaluate((node: MessageBar) => node.elementInternals.states.has('success'))).toBe(false); + expect(await element.evaluate((node: MessageBar) => node.elementInternals.states.has('warning'))).toBe(true); + + await element.evaluate((node: MessageBar) => { + node.intent = 'error'; + }); + + await expect(element).toHaveJSProperty('intent', 'error'); + expect(await element.evaluate((node: MessageBar) => node.elementInternals.states.has('warning'))).toBe(false); + expect(await element.evaluate((node: MessageBar) => node.elementInternals.states.has('error'))).toBe(true); + + await element.evaluate((node: MessageBar) => { + node.intent = 'info'; + }); + + await expect(element).toHaveJSProperty('intent', 'info'); + expect(await element.evaluate((node: MessageBar) => node.elementInternals.states.has('error'))).toBe(false); + expect(await element.evaluate((node: MessageBar) => node.elementInternals.states.has('info'))).toBe(true); + }); + + test('should set and retrieve the `shape` property correctly', async ({ page }) => { + const element = page.locator('fluent-message-bar'); + + await element.evaluate((node: MessageBar) => { + node.shape = 'square'; + }); + + await expect(element).toHaveJSProperty('shape', 'square'); + expect(await element.evaluate((node: MessageBar) => node.elementInternals.states.has('square'))).toBe(true); + + await element.evaluate((node: MessageBar) => { + node.shape = 'rounded'; + }); + + await expect(element).toHaveJSProperty('shape', 'rounded'); + expect(await element.evaluate((node: MessageBar) => node.elementInternals.states.has('square'))).toBe(false); + expect(await element.evaluate((node: MessageBar) => node.elementInternals.states.has('rounded'))).toBe(true); + }); + + test('should set and retrieve the `layout` property correctly', async ({ page }) => { + const element = page.locator('fluent-message-bar'); + + await element.evaluate((node: MessageBar) => { + node.layout = 'multiline'; + }); + + await expect(element).toHaveJSProperty('layout', 'multiline'); + expect(await element.evaluate((node: MessageBar) => node.getAttribute('layout'))).toBe('multiline'); + + await element.evaluate((node: MessageBar) => { + node.layout = 'singleline'; + }); + + await expect(element).toHaveJSProperty('layout', 'singleline'); + expect(await element.evaluate((node: MessageBar) => node.getAttribute('layout'))).toBe('singleline'); + }); + + test('should emit dismiss event when dismissMessageBar is called', async ({ page }) => { + const element = page.locator('fluent-message-bar'); + await element.evaluate((node: MessageBar) => { + node.addEventListener('dismiss', () => { + node.setAttribute('dismissed', 'true'); + }); + }); + + await element.evaluate((node: MessageBar) => { + node.dismissMessageBar(); + }); + + await expect(element).toHaveAttribute('dismissed', 'true'); + }); +}); diff --git a/packages/web-components/src/message-bar/message-bar.options.ts b/packages/web-components/src/message-bar/message-bar.options.ts new file mode 100644 index 00000000000000..121cd3ad7f2804 --- /dev/null +++ b/packages/web-components/src/message-bar/message-bar.options.ts @@ -0,0 +1,36 @@ +import type { ValuesOf } from '../utils/typings.js'; + +/** + * @public + * The `layout` variations for the MessageBar component. + */ +export const MessageBarLayout = { + multiline: 'multiline', + singleline: 'singleline', +} as const; + +export type MessageBarLayout = ValuesOf; + +/** + * @public + * The `shape` variations for the MessageBar component. + */ +export const MessageBarShape = { + rounded: 'rounded', + square: 'square', +} as const; + +export type MessageBarShape = ValuesOf; + +/** + * @public + * The `intent` variations for the MessageBar component. + */ +export const MessageBarIntent = { + success: 'success', + warning: 'warning', + error: 'error', + info: 'info', +} as const; + +export type MessageBarIntent = ValuesOf; diff --git a/packages/web-components/src/message-bar/message-bar.stories.ts b/packages/web-components/src/message-bar/message-bar.stories.ts new file mode 100644 index 00000000000000..688e4216a55801 --- /dev/null +++ b/packages/web-components/src/message-bar/message-bar.stories.ts @@ -0,0 +1,208 @@ +import { html } from '@microsoft/fast-element'; +import type { Args, Meta } from '@storybook/html'; +import { renderComponent } from '../helpers.stories.js'; +import { MessageBar as FluentMessageBar } from './message-bar.js'; +import { MessageBarIntent, MessageBarLayout, MessageBarShape } from './message-bar.options.js'; +import './define'; + +type MessageBarStoryArgs = Args & FluentMessageBar; +type MessageBarStoryMeta = Meta; + +const dismissed20Regular = html` + +`; + +const infoIcon = html` + + + +`; + +const warningIcon = html` + + + +`; + +const successIcon = html` + + + +`; + +const errorIcon = html` + + + +`; + +const storyTemplate = html` + + ${infoIcon} + ${x => x.content} + Action + + ${dismissed20Regular} + + +`; + +export default { + title: 'Components/MessageBar', + args: { + content: 'This is a message bar that provides information to the user.', + shape: MessageBarShape.rounded, + layout: MessageBarLayout.singleline, + intent: MessageBarIntent.info, + }, + argTypes: { + content: { + description: 'MessageBar content', + control: { type: 'text' }, + }, + shape: { + description: 'MessageBar shape', + control: { type: 'select' }, + options: Object.values(MessageBarShape), + }, + layout: { + description: 'MessageBar layout', + control: { type: 'select' }, + options: Object.values(MessageBarLayout), + }, + intent: { + description: 'MessageBar intent', + control: { type: 'select' }, + options: Object.values(MessageBarIntent), + }, + }, +} as MessageBarStoryMeta; + +export const MessageBar = renderComponent(storyTemplate).bind({}) as any; + +export const Shape = renderComponent(html` + + ${infoIcon} + rounded + Action + Action 2 + + ${dismissed20Regular} + + +
+ + ${infoIcon} + square + Action + + ${dismissed20Regular} + + +`); + +export const Layout = renderComponent(html` + + ${infoIcon} + singleline + Action + + ${dismissed20Regular} + + +
+ + ${infoIcon} + This is a message bar that provides information to the user. This is a message bar that provides information to the + user. + Action + + ${dismissed20Regular} + + +`); + +export const Intent = renderComponent(html` + + ${infoIcon} + info + Action + + ${dismissed20Regular} + + +
+ + ${warningIcon} + warning + Action + + ${dismissed20Regular} + + +
+ + ${successIcon} + success + Action + + ${dismissed20Regular} + + +
+ + ${errorIcon} + error + Action + + ${dismissed20Regular} + + +`); + +export const Assertive = renderComponent(html` + + ${infoIcon} + info + Action + + ${dismissed20Regular} + + +`); + +export const LabelledBy = renderComponent(html` + This is a message bar that provides information to the user. + + ${infoIcon} + info + Action + + ${dismissed20Regular} + + +`); diff --git a/packages/web-components/src/message-bar/message-bar.styles.ts b/packages/web-components/src/message-bar/message-bar.styles.ts new file mode 100644 index 00000000000000..b86de8cf7d3cb2 --- /dev/null +++ b/packages/web-components/src/message-bar/message-bar.styles.ts @@ -0,0 +1,128 @@ +import type { ElementStyles } from '@microsoft/fast-element'; +import { css } from '@microsoft/fast-element'; +import { + borderRadiusMedium, + colorNeutralBackground3, + colorNeutralForeground3, + colorNeutralStroke1, + colorPaletteDarkOrangeBackground1, + colorPaletteDarkOrangeBorder1, + colorPaletteGreenBackground1, + colorPaletteGreenBorder1, + colorPaletteRedBackground1, + colorPaletteRedBorder1, + fontFamilyBase, + fontSizeBase200, + lineHeightBase200, + spacingHorizontalM, + spacingHorizontalS, + spacingVerticalMNudge, + spacingVerticalS, +} from '../theme/design-tokens.js'; + +import { errorState, multiLineState, squareState, successState, warningState } from '../styles/states/index.js'; + +/** + * Styles for the MessageBar component. + * + * @public + */ +export const styles: ElementStyles = css` + :host { + display: grid; + box-sizing: border-box; + font-family: ${fontFamilyBase}; + font-size: ${fontSizeBase200}; + line-height: ${lineHeightBase200}; + width: 100%; + background: ${colorNeutralBackground3}; + border: 1px solid ${colorNeutralStroke1}; + padding-inline: ${spacingHorizontalM}; + border-radius: ${borderRadiusMedium}; + min-height: 36px; + align-items: center; + grid-template: 'icon body actions dismiss' / auto 1fr auto auto; + contain: layout style paint; + } + + :host(${squareState}) { + border-radius: 0; + } + + :host(${successState}) { + background-color: ${colorPaletteGreenBackground1}; + border-color: ${colorPaletteGreenBorder1}; + } + + :host(${warningState}) { + background-color: ${colorPaletteDarkOrangeBackground1}; + border-color: ${colorPaletteDarkOrangeBorder1}; + } + + :host(${errorState}) { + background-color: ${colorPaletteRedBackground1}; + border-color: ${colorPaletteRedBorder1}; + } + + :host(${multiLineState}) { + grid-template-areas: + 'icon body dismiss' + 'actions actions actions'; + grid-template-columns: auto 1fr auto; + grid-template-rows: auto auto 1fr; + padding-block: ${spacingVerticalMNudge}; + padding-inline: ${spacingHorizontalM}; + } + + .content { + grid-area: body; + max-width: 520px; + padding-block: ${spacingVerticalMNudge}; + padding-inline: 0; + } + + :host(${multiLineState}) .content { + padding: 0; + } + + ::slotted([slot='icon']) { + display: flex; + grid-area: icon; + flex-direction: column; + align-items: center; + color: ${colorNeutralForeground3}; + margin-inline-end: ${spacingHorizontalS}; + } + + :host(${multiLineState}) ::slotted([slot='icon']) { + align-items: start; + height: 100%; + } + + ::slotted([slot='dismiss']) { + grid-area: dismiss; + } + + .actions { + grid-area: actions; + display: flex; + justify-self: end; + margin-inline-end: ${spacingHorizontalS}; + gap: ${spacingHorizontalS}; + } + + :host(${multiLineState}) .actions { + margin-block-start: ${spacingVerticalMNudge}; + margin-inline-end: 0; + } + + :host(${multiLineState}) ::slotted([slot='dismiss']) { + align-items: start; + height: 100%; + padding-block-start: ${spacingVerticalS}; + } + + ::slotted(*) { + font-size: inherit; + } +`; diff --git a/packages/web-components/src/message-bar/message-bar.template.ts b/packages/web-components/src/message-bar/message-bar.template.ts new file mode 100644 index 00000000000000..db049657c4882e --- /dev/null +++ b/packages/web-components/src/message-bar/message-bar.template.ts @@ -0,0 +1,27 @@ +import { ElementViewTemplate, html } from '@microsoft/fast-element'; +import type { MessageBar } from './message-bar.js'; + +/** + * Generates a template for the MessageBar component. + * @public + * @param {MessageBar} T - The type of the MessageBar. + * @returns {ElementViewTemplate} - The template for the MessageBar component. + */ +export function messageBarTemplate(): ElementViewTemplate { + return html` + +
+ +
+
+ +
+ + `; +} + +/** + * The template for the MessageBar component. + * @type {ElementViewTemplate} + */ +export const template: ElementViewTemplate = messageBarTemplate(); diff --git a/packages/web-components/src/message-bar/message-bar.ts b/packages/web-components/src/message-bar/message-bar.ts new file mode 100644 index 00000000000000..df2c04b22dc8a5 --- /dev/null +++ b/packages/web-components/src/message-bar/message-bar.ts @@ -0,0 +1,105 @@ +import { attr, FASTElement } from '@microsoft/fast-element'; +import { toggleState } from '../utils/element-internals.js'; +import { MessageBarIntent, MessageBarLayout, MessageBarShape } from './message-bar.options.js'; + +/** + * A Message Bar Custom HTML Element. + * + * @slot actions - Content that can be provided for the actions + * @slot dismiss - Content that can be provided for the dismiss button + * @slot - The default slot for the content + * @public + */ +export class MessageBar extends FASTElement { + /** + * The internal {@link https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals | `ElementInternals`} instance for the component. + * + * @internal + */ + public elementInternals: ElementInternals = this.attachInternals(); + + constructor() { + super(); + this.elementInternals.role = 'status'; + } + + /** + * Sets the shape of the Message Bar. + * + * @public + * @remarks + * HTML Attribute: `shape` + */ + @attr + public shape?: MessageBarShape; + + /** + * Handles changes to shape attribute custom states + * @param prev - the previous state + * @param next - the next state + */ + public shapeChanged(prev: MessageBarShape | undefined, next: MessageBarShape | undefined) { + if (prev) { + toggleState(this.elementInternals, prev, false); + } + if (next) { + toggleState(this.elementInternals, next, true); + } + } + + /** + * Sets the layout of the control. + * + * @public + * @remarks + * HTML Attribute: `layout` + */ + @attr + public layout?: MessageBarLayout; + + /** + * Handles changes to the layout attribute custom states + * @param prev - the previous state + * @param next - the next state + */ + public layoutChanged(prev: MessageBarLayout | undefined, next: MessageBarLayout | undefined) { + if (prev) { + toggleState(this.elementInternals, prev, false); + } + if (next) { + toggleState(this.elementInternals, next, true); + } + } + + /** + * Sets the intent of the control. + * + * @public + * @remarks + * HTML Attribute: `intent` + */ + @attr + public intent?: MessageBarIntent; + + /** + * Handles changes to the intent attribute custom states + * @param prev - the previous state + * @param next - the next state + */ + public intentChanged(prev: MessageBarIntent | undefined, next: MessageBarIntent | undefined) { + if (prev) { + toggleState(this.elementInternals, prev, false); + } + if (next) { + toggleState(this.elementInternals, next, true); + } + } + + /** + * @public + * Method to emit a `dismiss` event when the message bar is dismissed + */ + public dismissMessageBar = () => { + this.$emit('dismiss', {}); + }; +} diff --git a/packages/web-components/src/styles/states/index.ts b/packages/web-components/src/styles/states/index.ts index 758c1458c83138..911040e896c983 100644 --- a/packages/web-components/src/styles/states/index.ts +++ b/packages/web-components/src/styles/states/index.ts @@ -221,3 +221,15 @@ export const verticalState = css.partial`:is([state--vertical], :state(vertical) * @public */ export const horizontalState = css.partial`:is([state--horizontal], :state(horizontal))`; + +/** + * Selector for the `singleline` state. + * @public + */ +export const singleLineState = css.partial`:is([state--singleline], :state(singleline))`; + +/** + * Selector for the `multiline` state. + * @public + */ +export const multiLineState = css.partial`:is([state--multiline], :state(multiline))`;