From f5a7be14952c21f0f59a759e04ed20956184c394 Mon Sep 17 00:00:00 2001 From: Shaun Andrews Date: Fri, 7 Aug 2026 15:11:04 -0400 Subject: [PATCH 1/2] Add beta menu to the agentic sidebar --- .../app-message-cards/index.test.tsx | 66 +++++++++++++++++++ .../components/app-message-cards/index.tsx | 51 +++++++++++++- .../app-message-cards/style.module.css | 54 +++++++++++++++ .../components/sidebar-layout/index.test.tsx | 1 + .../src/components/sidebar-layout/index.tsx | 9 ++- .../sidebar-layout/style.module.css | 9 +++ apps/ui/src/data/core/types.ts | 3 + 7 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 apps/ui/src/components/app-message-cards/index.test.tsx diff --git a/apps/ui/src/components/app-message-cards/index.test.tsx b/apps/ui/src/components/app-message-cards/index.test.tsx new file mode 100644 index 0000000000..7173454d52 --- /dev/null +++ b/apps/ui/src/components/app-message-cards/index.test.tsx @@ -0,0 +1,66 @@ +import '@testing-library/jest-dom/vitest'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { useConnector } from '@/data/core'; +import { useAppGlobals } from '@/data/queries/use-app-globals'; +import { REPORT_ISSUE_URL } from '@/lib/docs-links'; +import { StudioBetaCard } from './index'; + +vi.mock( '@/data/core', () => ( { + useConnector: vi.fn(), +} ) ); + +vi.mock( '@/data/queries/use-app-globals', () => ( { + useAppGlobals: vi.fn(), +} ) ); + +const useConnectorMock = vi.mocked( useConnector, { partial: true } ); +const useAppGlobalsMock = vi.mocked( useAppGlobals, { partial: true } ); + +describe( 'StudioBetaCard', () => { + const disableAgenticUi = vi.fn().mockResolvedValue( undefined ); + const openExternalUrl = vi.fn().mockResolvedValue( undefined ); + + beforeEach( () => { + vi.clearAllMocks(); + useConnectorMock.mockReturnValue( { + capabilities: { switchToClassicUi: true }, + disableAgenticUi, + openExternalUrl, + } as never ); + useAppGlobalsMock.mockReturnValue( { + data: { platform: 'darwin', isWindowsStore: false, appVersion: '1.18.0-beta1' }, + } as never ); + } ); + + it( 'offers feedback and classic Studio actions from its menu', async () => { + render( ); + + expect( screen.queryByRole( 'menuitem', { name: 'Send feedback' } ) ).not.toBeInTheDocument(); + fireEvent.click( screen.getByRole( 'button', { name: 'Studio Beta options' } ) ); + expect( await screen.findByText( 'Studio 1.18.0-beta1' ) ).toBeVisible(); + + fireEvent.click( await screen.findByRole( 'menuitem', { name: 'Switch to Studio Classic' } ) ); + expect( disableAgenticUi ).toHaveBeenCalledTimes( 1 ); + + fireEvent.click( screen.getByRole( 'button', { name: 'Studio Beta options' } ) ); + fireEvent.click( await screen.findByRole( 'menuitem', { name: 'Send feedback' } ) ); + expect( openExternalUrl ).toHaveBeenCalledWith( REPORT_ISSUE_URL ); + } ); + + it( 'hides the classic action when the host cannot switch experiences', async () => { + useConnectorMock.mockReturnValue( { + capabilities: { switchToClassicUi: false }, + disableAgenticUi, + openExternalUrl, + } as never ); + + render( ); + fireEvent.click( screen.getByRole( 'button', { name: 'Studio Beta options' } ) ); + + expect( await screen.findByRole( 'menuitem', { name: 'Send feedback' } ) ).toBeVisible(); + expect( + screen.queryByRole( 'menuitem', { name: 'Switch to Studio Classic' } ) + ).not.toBeInTheDocument(); + } ); +} ); diff --git a/apps/ui/src/components/app-message-cards/index.tsx b/apps/ui/src/components/app-message-cards/index.tsx index 48ad895b8d..8b2739cbb2 100644 --- a/apps/ui/src/components/app-message-cards/index.tsx +++ b/apps/ui/src/components/app-message-cards/index.tsx @@ -1,10 +1,59 @@ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { Button, Notice } from '@wordpress/ui'; import { clsx } from 'clsx'; import toastStyles from '@/components/app-toasts/style.module.css'; +import * as Menu from '@/components/menu'; +import { useConnector } from '@/data/core'; +import { useAppGlobals } from '@/data/queries/use-app-globals'; import { useActivePersistentMessages } from '@/data/queries/use-app-messages'; +import { REPORT_ISSUE_URL } from '@/lib/docs-links'; import styles from './style.module.css'; +export function StudioBetaCard( { className }: { className?: string } ) { + const connector = useConnector(); + const { data: appGlobals } = useAppGlobals(); + + return ( + +
+
+ + } + > + { __( 'Beta' ) } + +
+
+ + { connector.capabilities.switchToClassicUi ? ( + void connector.disableAgenticUi() }> + { __( 'Switch to Studio Classic' ) } + + ) : null } + void connector.openExternalUrl( REPORT_ISSUE_URL ) }> + { __( 'Send feedback' ) } + + { appGlobals?.appVersion ? ( + <> + + + + { sprintf( __( 'Studio %s' ), appGlobals.appVersion ) } + + + + ) : null } + +
+ ); +} + export function AppMessageCards( { className }: { className?: string } ) { const { messages, dismiss } = useActivePersistentMessages(); diff --git a/apps/ui/src/components/app-message-cards/style.module.css b/apps/ui/src/components/app-message-cards/style.module.css index 3907fe248b..a445abcb1f 100644 --- a/apps/ui/src/components/app-message-cards/style.module.css +++ b/apps/ui/src/components/app-message-cards/style.module.css @@ -44,6 +44,60 @@ animation: card-enter 160ms ease-out; } +.betaCardButton { + --beta-bubble-bg: var(--wpds-color-bg-interactive-brand-strong); + --beta-bubble-border: color-mix( + in srgb, + var(--wpds-color-fg-content-neutral) 35%, + transparent + ); + --wp-ui-button-background-color: var(--beta-bubble-bg); + --wp-ui-button-border-color: var(--beta-bubble-border); + + display: flex; + align-items: center; + width: max-content; + max-width: 100%; + box-sizing: border-box; + padding: var(--wpds-dimension-padding-sm) var(--wpds-dimension-padding-md); + appearance: none; + background-color: var(--beta-bubble-bg); + border: var(--wpds-border-width-xs) solid var(--beta-bubble-border); + border-radius: 14px 14px 14px 4px; + box-shadow: var(--wpds-elevation-md); + color: var(--wpds-color-fg-content-neutral); + font: inherit; + font-size: 11px; + line-height: var(--wpds-typography-line-height-xs); + cursor: var(--wpds-cursor-control); + outline: none; + animation: card-enter 160ms ease-out; +} + +.betaCardButton:hover { + --beta-bubble-bg: color-mix( + in srgb, + var(--wpds-color-bg-interactive-brand-strong) 88%, + var(--wpds-color-fg-content-neutral) 12% + ); +} + +.betaCardButton:active, +.betaCardButton[data-popup-open] { + background-color: var(--wpds-color-bg-interactive-brand-strong); + border-color: var(--beta-bubble-border); +} + +.betaCardButton:focus-visible { + outline: var(--wpds-border-width-xs) solid var(--wpds-color-fg-content-neutral); + outline-offset: 2px; +} + +.betaLabel { + text-transform: uppercase; + letter-spacing: 0.04em; +} + @keyframes card-enter { from { opacity: 0; diff --git a/apps/ui/src/components/sidebar-layout/index.test.tsx b/apps/ui/src/components/sidebar-layout/index.test.tsx index 92fef395d0..1eb2f4622b 100644 --- a/apps/ui/src/components/sidebar-layout/index.test.tsx +++ b/apps/ui/src/components/sidebar-layout/index.test.tsx @@ -11,6 +11,7 @@ vi.mock( '@/components/sidebar-header', () => ( { vi.mock( '@/components/app-message-cards', () => ( { AppMessageCards: () => null, AppMessageCardsDot: () => null, + StudioBetaCard: () => null, } ) ); vi.mock( '@/components/site-list', () => ( { diff --git a/apps/ui/src/components/sidebar-layout/index.tsx b/apps/ui/src/components/sidebar-layout/index.tsx index 2ef2b0c2e6..82ad057ec5 100644 --- a/apps/ui/src/components/sidebar-layout/index.tsx +++ b/apps/ui/src/components/sidebar-layout/index.tsx @@ -3,7 +3,11 @@ import { privateApis } from '@wordpress/theme'; import { IconButton } from '@wordpress/ui'; import { clsx } from 'clsx'; import { useCallback, useEffect, useState } from 'react'; -import { AppMessageCards, AppMessageCardsDot } from '@/components/app-message-cards'; +import { + AppMessageCards, + AppMessageCardsDot, + StudioBetaCard, +} from '@/components/app-message-cards'; import { AppToasts } from '@/components/app-toasts'; import { ResizeHandle, ResizeOverlay } from '@/components/resize-handle'; import { SidebarHeader } from '@/components/sidebar-header'; @@ -98,6 +102,9 @@ export function SidebarLayout( { { ! effectiveCollapsed ? ( ) : null } + { ! effectiveCollapsed ? ( + + ) : null } diff --git a/apps/ui/src/components/sidebar-layout/style.module.css b/apps/ui/src/components/sidebar-layout/style.module.css index 15fdc196f3..118c1fc028 100644 --- a/apps/ui/src/components/sidebar-layout/style.module.css +++ b/apps/ui/src/components/sidebar-layout/style.module.css @@ -82,6 +82,15 @@ padding: var(--wpds-dimension-padding-sm) calc(var(--wpds-dimension-padding-lg) - var(--wpds-dimension-padding-sm)) 0; } +/* The beta affordance stays in normal footer flow, below transient/persistent + messages and above Settings, so it never overlays a toast or checklist. */ +.sidebarBeta { + width: fit-content; + max-width: calc(100% - 2 * var(--wpds-dimension-padding-lg)); + padding: var(--wpds-dimension-padding-sm) var(--wpds-dimension-padding-lg) + var(--wpds-dimension-padding-xs); +} + /* Sidebar-specific indicator treatment: match the content card's vertical extent instead of running through the frame gaps above/below it (keep the 12px in sync with --panel-frame-gap in preview-split-frame). The handle is diff --git a/apps/ui/src/data/core/types.ts b/apps/ui/src/data/core/types.ts index edcdfc6fa3..b61bcd32c1 100644 --- a/apps/ui/src/data/core/types.ts +++ b/apps/ui/src/data/core/types.ts @@ -525,6 +525,9 @@ export interface UserPreferences { export interface AppGlobals { platform: string; isWindowsStore: boolean; + // Supplied by the desktop host; browser targets do not have an installed + // Studio app version to report. + appVersion?: string; } // Subset of UserPreferences that callers can actually mutate. `locale` is From 5395a3a6b240edb8d15b07d7e384a64c4af85df2 Mon Sep 17 00:00:00 2001 From: bcotrim Date: Mon, 10 Aug 2026 11:49:40 +0100 Subject: [PATCH 2/2] Move the beta menu to its own component and fix its hover contrast --- .../components/app-message-cards/index.tsx | 51 +------------- .../app-message-cards/style.module.css | 54 --------------- .../components/sidebar-layout/index.test.tsx | 5 +- .../src/components/sidebar-layout/index.tsx | 9 +-- .../sidebar-layout/style.module.css | 8 +-- .../index.test.tsx | 32 ++++++--- .../src/components/studio-beta-menu/index.tsx | 51 ++++++++++++++ .../studio-beta-menu/style.module.css | 69 +++++++++++++++++++ 8 files changed, 155 insertions(+), 124 deletions(-) rename apps/ui/src/components/{app-message-cards => studio-beta-menu}/index.test.tsx (66%) create mode 100644 apps/ui/src/components/studio-beta-menu/index.tsx create mode 100644 apps/ui/src/components/studio-beta-menu/style.module.css diff --git a/apps/ui/src/components/app-message-cards/index.tsx b/apps/ui/src/components/app-message-cards/index.tsx index 8b2739cbb2..48ad895b8d 100644 --- a/apps/ui/src/components/app-message-cards/index.tsx +++ b/apps/ui/src/components/app-message-cards/index.tsx @@ -1,59 +1,10 @@ -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { Button, Notice } from '@wordpress/ui'; import { clsx } from 'clsx'; import toastStyles from '@/components/app-toasts/style.module.css'; -import * as Menu from '@/components/menu'; -import { useConnector } from '@/data/core'; -import { useAppGlobals } from '@/data/queries/use-app-globals'; import { useActivePersistentMessages } from '@/data/queries/use-app-messages'; -import { REPORT_ISSUE_URL } from '@/lib/docs-links'; import styles from './style.module.css'; -export function StudioBetaCard( { className }: { className?: string } ) { - const connector = useConnector(); - const { data: appGlobals } = useAppGlobals(); - - return ( - -
-
- - } - > - { __( 'Beta' ) } - -
-
- - { connector.capabilities.switchToClassicUi ? ( - void connector.disableAgenticUi() }> - { __( 'Switch to Studio Classic' ) } - - ) : null } - void connector.openExternalUrl( REPORT_ISSUE_URL ) }> - { __( 'Send feedback' ) } - - { appGlobals?.appVersion ? ( - <> - - - - { sprintf( __( 'Studio %s' ), appGlobals.appVersion ) } - - - - ) : null } - -
- ); -} - export function AppMessageCards( { className }: { className?: string } ) { const { messages, dismiss } = useActivePersistentMessages(); diff --git a/apps/ui/src/components/app-message-cards/style.module.css b/apps/ui/src/components/app-message-cards/style.module.css index a445abcb1f..3907fe248b 100644 --- a/apps/ui/src/components/app-message-cards/style.module.css +++ b/apps/ui/src/components/app-message-cards/style.module.css @@ -44,60 +44,6 @@ animation: card-enter 160ms ease-out; } -.betaCardButton { - --beta-bubble-bg: var(--wpds-color-bg-interactive-brand-strong); - --beta-bubble-border: color-mix( - in srgb, - var(--wpds-color-fg-content-neutral) 35%, - transparent - ); - --wp-ui-button-background-color: var(--beta-bubble-bg); - --wp-ui-button-border-color: var(--beta-bubble-border); - - display: flex; - align-items: center; - width: max-content; - max-width: 100%; - box-sizing: border-box; - padding: var(--wpds-dimension-padding-sm) var(--wpds-dimension-padding-md); - appearance: none; - background-color: var(--beta-bubble-bg); - border: var(--wpds-border-width-xs) solid var(--beta-bubble-border); - border-radius: 14px 14px 14px 4px; - box-shadow: var(--wpds-elevation-md); - color: var(--wpds-color-fg-content-neutral); - font: inherit; - font-size: 11px; - line-height: var(--wpds-typography-line-height-xs); - cursor: var(--wpds-cursor-control); - outline: none; - animation: card-enter 160ms ease-out; -} - -.betaCardButton:hover { - --beta-bubble-bg: color-mix( - in srgb, - var(--wpds-color-bg-interactive-brand-strong) 88%, - var(--wpds-color-fg-content-neutral) 12% - ); -} - -.betaCardButton:active, -.betaCardButton[data-popup-open] { - background-color: var(--wpds-color-bg-interactive-brand-strong); - border-color: var(--beta-bubble-border); -} - -.betaCardButton:focus-visible { - outline: var(--wpds-border-width-xs) solid var(--wpds-color-fg-content-neutral); - outline-offset: 2px; -} - -.betaLabel { - text-transform: uppercase; - letter-spacing: 0.04em; -} - @keyframes card-enter { from { opacity: 0; diff --git a/apps/ui/src/components/sidebar-layout/index.test.tsx b/apps/ui/src/components/sidebar-layout/index.test.tsx index 1eb2f4622b..af8e1e2edc 100644 --- a/apps/ui/src/components/sidebar-layout/index.test.tsx +++ b/apps/ui/src/components/sidebar-layout/index.test.tsx @@ -11,7 +11,10 @@ vi.mock( '@/components/sidebar-header', () => ( { vi.mock( '@/components/app-message-cards', () => ( { AppMessageCards: () => null, AppMessageCardsDot: () => null, - StudioBetaCard: () => null, +} ) ); + +vi.mock( '@/components/studio-beta-menu', () => ( { + StudioBetaMenu: () => null, } ) ); vi.mock( '@/components/site-list', () => ( { diff --git a/apps/ui/src/components/sidebar-layout/index.tsx b/apps/ui/src/components/sidebar-layout/index.tsx index 82ad057ec5..bd994902ad 100644 --- a/apps/ui/src/components/sidebar-layout/index.tsx +++ b/apps/ui/src/components/sidebar-layout/index.tsx @@ -3,15 +3,12 @@ import { privateApis } from '@wordpress/theme'; import { IconButton } from '@wordpress/ui'; import { clsx } from 'clsx'; import { useCallback, useEffect, useState } from 'react'; -import { - AppMessageCards, - AppMessageCardsDot, - StudioBetaCard, -} from '@/components/app-message-cards'; +import { AppMessageCards, AppMessageCardsDot } from '@/components/app-message-cards'; import { AppToasts } from '@/components/app-toasts'; import { ResizeHandle, ResizeOverlay } from '@/components/resize-handle'; import { SidebarHeader } from '@/components/sidebar-header'; import { SiteList } from '@/components/site-list'; +import { StudioBetaMenu } from '@/components/studio-beta-menu'; import { UserMenu } from '@/components/user-menu'; import { useConnector } from '@/data/core'; import { useColorScheme } from '@/hooks/use-color-scheme'; @@ -103,7 +100,7 @@ export function SidebarLayout( { ) : null } { ! effectiveCollapsed ? ( - + ) : null } diff --git a/apps/ui/src/components/sidebar-layout/style.module.css b/apps/ui/src/components/sidebar-layout/style.module.css index 118c1fc028..c9f403ad26 100644 --- a/apps/ui/src/components/sidebar-layout/style.module.css +++ b/apps/ui/src/components/sidebar-layout/style.module.css @@ -83,11 +83,11 @@ } /* The beta affordance stays in normal footer flow, below transient/persistent - messages and above Settings, so it never overlays a toast or checklist. */ + messages and above Settings, so it never overlays a toast or checklist. + Margin, not padding: this class lands on the bubble itself, which owns its + own padding. */ .sidebarBeta { - width: fit-content; - max-width: calc(100% - 2 * var(--wpds-dimension-padding-lg)); - padding: var(--wpds-dimension-padding-sm) var(--wpds-dimension-padding-lg) + margin: var(--wpds-dimension-padding-sm) var(--wpds-dimension-padding-lg) var(--wpds-dimension-padding-xs); } diff --git a/apps/ui/src/components/app-message-cards/index.test.tsx b/apps/ui/src/components/studio-beta-menu/index.test.tsx similarity index 66% rename from apps/ui/src/components/app-message-cards/index.test.tsx rename to apps/ui/src/components/studio-beta-menu/index.test.tsx index 7173454d52..e8d0ffd1d3 100644 --- a/apps/ui/src/components/app-message-cards/index.test.tsx +++ b/apps/ui/src/components/studio-beta-menu/index.test.tsx @@ -4,7 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { useConnector } from '@/data/core'; import { useAppGlobals } from '@/data/queries/use-app-globals'; import { REPORT_ISSUE_URL } from '@/lib/docs-links'; -import { StudioBetaCard } from './index'; +import { StudioBetaMenu } from './index'; vi.mock( '@/data/core', () => ( { useConnector: vi.fn(), @@ -17,7 +17,7 @@ vi.mock( '@/data/queries/use-app-globals', () => ( { const useConnectorMock = vi.mocked( useConnector, { partial: true } ); const useAppGlobalsMock = vi.mocked( useAppGlobals, { partial: true } ); -describe( 'StudioBetaCard', () => { +describe( 'StudioBetaMenu', () => { const disableAgenticUi = vi.fn().mockResolvedValue( undefined ); const openExternalUrl = vi.fn().mockResolvedValue( undefined ); @@ -34,17 +34,17 @@ describe( 'StudioBetaCard', () => { } ); it( 'offers feedback and classic Studio actions from its menu', async () => { - render( ); + render( ); - expect( screen.queryByRole( 'menuitem', { name: 'Send feedback' } ) ).not.toBeInTheDocument(); + expect( screen.queryByRole( 'menuitem', { name: 'Report an issue' } ) ).not.toBeInTheDocument(); fireEvent.click( screen.getByRole( 'button', { name: 'Studio Beta options' } ) ); expect( await screen.findByText( 'Studio 1.18.0-beta1' ) ).toBeVisible(); - fireEvent.click( await screen.findByRole( 'menuitem', { name: 'Switch to Studio Classic' } ) ); + fireEvent.click( await screen.findByRole( 'menuitem', { name: 'Switch to classic' } ) ); expect( disableAgenticUi ).toHaveBeenCalledTimes( 1 ); fireEvent.click( screen.getByRole( 'button', { name: 'Studio Beta options' } ) ); - fireEvent.click( await screen.findByRole( 'menuitem', { name: 'Send feedback' } ) ); + fireEvent.click( await screen.findByRole( 'menuitem', { name: 'Report an issue' } ) ); expect( openExternalUrl ).toHaveBeenCalledWith( REPORT_ISSUE_URL ); } ); @@ -55,12 +55,26 @@ describe( 'StudioBetaCard', () => { openExternalUrl, } as never ); - render( ); + render( ); fireEvent.click( screen.getByRole( 'button', { name: 'Studio Beta options' } ) ); - expect( await screen.findByRole( 'menuitem', { name: 'Send feedback' } ) ).toBeVisible(); + expect( await screen.findByRole( 'menuitem', { name: 'Report an issue' } ) ).toBeVisible(); expect( - screen.queryByRole( 'menuitem', { name: 'Switch to Studio Classic' } ) + screen.queryByRole( 'menuitem', { name: 'Switch to classic' } ) ).not.toBeInTheDocument(); } ); + + // Browser targets report no installed app version; the menu drops the row + // rather than rendering a bare "Studio". + it( 'omits the version row when the host reports no app version', async () => { + useAppGlobalsMock.mockReturnValue( { + data: { platform: 'browser', isWindowsStore: false }, + } as never ); + + render( ); + fireEvent.click( screen.getByRole( 'button', { name: 'Studio Beta options' } ) ); + + expect( await screen.findByRole( 'menuitem', { name: 'Report an issue' } ) ).toBeVisible(); + expect( screen.queryByText( /^Studio / ) ).not.toBeInTheDocument(); + } ); } ); diff --git a/apps/ui/src/components/studio-beta-menu/index.tsx b/apps/ui/src/components/studio-beta-menu/index.tsx new file mode 100644 index 0000000000..ecf10f4425 --- /dev/null +++ b/apps/ui/src/components/studio-beta-menu/index.tsx @@ -0,0 +1,51 @@ +import { __, sprintf } from '@wordpress/i18n'; +import { clsx } from 'clsx'; +import * as Menu from '@/components/menu'; +import { useConnector } from '@/data/core'; +import { useAppGlobals } from '@/data/queries/use-app-globals'; +import { REPORT_ISSUE_URL } from '@/lib/docs-links'; +import styles from './style.module.css'; + +export function StudioBetaMenu( { className }: { className?: string } ) { + const connector = useConnector(); + const { data: appGlobals } = useAppGlobals(); + + return ( + + + } + > + { __( 'Beta' ) } + + + { connector.capabilities.switchToClassicUi ? ( + void connector.disableAgenticUi() }> + { __( 'Switch to classic' ) } + + ) : null } + void connector.openExternalUrl( REPORT_ISSUE_URL ) }> + { __( 'Report an issue' ) } + + { appGlobals?.appVersion ? ( + <> + + + + { + // translators: %s: the running Studio version, e.g. "1.18.0-beta1". + sprintf( __( 'Studio %s' ), appGlobals.appVersion ) + } + + + + ) : null } + + + ); +} diff --git a/apps/ui/src/components/studio-beta-menu/style.module.css b/apps/ui/src/components/studio-beta-menu/style.module.css new file mode 100644 index 0000000000..00bf8b0059 --- /dev/null +++ b/apps/ui/src/components/studio-beta-menu/style.module.css @@ -0,0 +1,69 @@ +/* Speech bubble anchored to the sidebar footer: brand fill, notch on the + bottom-left corner pointing at Settings. */ +.trigger { + --beta-bubble-bg: var(--wpds-color-bg-interactive-brand-strong); + --beta-bubble-border: color-mix(in srgb, var(--wpds-color-fg-content-neutral) 35%, transparent); + /* Read by the global `button:focus:not(:focus-visible)` reset in index.css, + which would otherwise repaint the bubble with WP UI's Button colors. */ + --wp-ui-button-background-color: var(--beta-bubble-bg); + --wp-ui-button-border-color: var(--beta-bubble-border); + + display: flex; + align-items: center; + width: max-content; + max-width: 100%; + box-sizing: border-box; + padding: var(--wpds-dimension-padding-sm) var(--wpds-dimension-padding-md); + appearance: none; + background-color: var(--beta-bubble-bg); + border: var(--wpds-border-width-xs) solid var(--beta-bubble-border); + border-radius: 14px 14px 14px 4px; + box-shadow: var(--wpds-elevation-md); + color: var(--wpds-color-fg-content-neutral); + font: inherit; + font-size: 11px; + line-height: var(--wpds-typography-line-height-xs); + cursor: var(--wpds-cursor-control); + outline: none; + animation: beta-enter 160ms ease-out; +} + +/* Darken toward the window chrome rather than toward the label. The sidebar + renders in a fixed dark theme scope, so `fg-content-neutral` is near-white + there and mixing it in would drop the label under the 4.5:1 contrast floor. */ +.trigger:hover { + --beta-bubble-bg: color-mix( + in srgb, + var(--wpds-color-bg-interactive-brand-strong) 88%, + var(--app-chrome-bg) 12% + ); +} + +/* Hold the resting fill while pressed or while the menu is open, including + under the global focus reset above, which paints `--beta-bubble-bg`. */ +.trigger:active, +.trigger[data-popup-open] { + --beta-bubble-bg: var(--wpds-color-bg-interactive-brand-strong); +} + +.trigger:focus-visible { + outline: var(--wpds-border-width-xs) solid var(--wpds-color-fg-content-neutral); + outline-offset: 2px; +} + +.label { + text-transform: uppercase; + letter-spacing: 0.04em; +} + +@keyframes beta-enter { + from { + opacity: 0; + transform: translateY(6px); + } + + to { + opacity: 1; + transform: translateY(0); + } +}