From 2b3a1cc9c1ee17fe51a478a7e74d386df5382e6f Mon Sep 17 00:00:00 2001 From: Shaun Andrews Date: Thu, 6 Aug 2026 14:14:01 -0400 Subject: [PATCH 1/7] Add Open in actions to the site overview --- .../site-overview-view/index.test.tsx | 66 ++++++++++++++++++- .../components/site-overview-view/index.tsx | 66 ++++++++++++++++++- .../site-overview-view/style.module.css | 6 +- apps/ui/src/data/core/index.ts | 1 + 4 files changed, 135 insertions(+), 4 deletions(-) diff --git a/apps/ui/src/components/site-overview-view/index.test.tsx b/apps/ui/src/components/site-overview-view/index.test.tsx index 3837e8e6be..aaae92b8e0 100644 --- a/apps/ui/src/components/site-overview-view/index.test.tsx +++ b/apps/ui/src/components/site-overview-view/index.test.tsx @@ -16,11 +16,17 @@ import { useUpdateSite, useXdebugEnabledSite, } from '@/data/queries/use-sites'; +import { useUserPreferences } from '@/data/queries/use-user-preferences'; import { useWordPressVersions, useWpVersion } from '@/data/queries/use-wordpress-versions'; import { useOffline } from '@/hooks/use-offline'; import styles from './style.module.css'; import { SiteOverviewView } from './index'; -import type { SiteDetails } from '@/data/core'; +import type { + ConnectorCapabilities, + SiteDetails, + SupportedEditor, + UserPreferences, +} from '@/data/core'; const navigateMock = vi.fn(); const siteDropdownMock = vi.hoisted( () => vi.fn() ); @@ -88,6 +94,10 @@ vi.mock( '@/data/queries/use-sites', () => ( { useXdebugEnabledSite: vi.fn(), } ) ); +vi.mock( '@/data/queries/use-user-preferences', () => ( { + useUserPreferences: vi.fn(), +} ) ); + vi.mock( '@/data/queries/use-wordpress-versions', () => ( { useWordPressVersions: vi.fn(), useWpVersion: vi.fn(), @@ -118,18 +128,33 @@ const useSitesMock = vi.mocked( useSites, { partial: true } ); const useStartSiteMock = vi.mocked( useStartSite, { partial: true } ); const useUpdateSiteMock = vi.mocked( useUpdateSite, { partial: true } ); const useOfflineMock = vi.mocked( useOffline ); +const useUserPreferencesMock = vi.mocked( useUserPreferences, { partial: true } ); const useWordPressVersionsMock = vi.mocked( useWordPressVersions, { partial: true } ); const useWpVersionMock = vi.mocked( useWpVersion, { partial: true } ); const useXdebugEnabledSiteMock = vi.mocked( useXdebugEnabledSite, { partial: true } ); describe( 'SiteOverviewView', () => { const openSiteUrl = vi.fn().mockResolvedValue( undefined ); + const openSiteFolder = vi.fn().mockResolvedValue( undefined ); + const openSiteInEditor = vi.fn().mockResolvedValue( undefined ); + const openSiteInTerminal = vi.fn().mockResolvedValue( undefined ); const startSite = vi.fn().mockResolvedValue( undefined ); const copySite = vi.fn(); const exportFullSite = vi.fn(); const exportDatabase = vi.fn(); const onTabChange = vi.fn(); + const connectorStub = ( openInOS = true ) => ( { + openSiteUrl, + openSiteFolder, + openSiteInEditor, + openSiteInTerminal, + capabilities: { openInOS } as ConnectorCapabilities, + } ); + + const preferencesStub = ( editor: SupportedEditor | null ) => + ( { editor, terminal: 'terminal' } ) as UserPreferences; + beforeEach( () => { vi.clearAllMocks(); useSidebarCollapsedMock.mockReturnValue( false ); @@ -149,7 +174,8 @@ describe( 'SiteOverviewView', () => { } ) ), } ); - useConnectorMock.mockReturnValue( { openSiteUrl } ); + useConnectorMock.mockReturnValue( connectorStub() ); + useUserPreferencesMock.mockReturnValue( { data: preferencesStub( 'vscode' ) } ); useAgenticFeaturesMock.mockReturnValue( { enabled: true, chatEnabled: true, @@ -476,6 +502,42 @@ describe( 'SiteOverviewView', () => { expect( screen.queryByText( 'Site Editor' ) ).not.toBeInTheDocument(); } ); + it( 'offers the configured apps and phpMyAdmin under Open in…', () => { + renderView(); + + expect( screen.getByRole( 'heading', { name: 'Open in…' } ) ).toBeVisible(); + expect( screen.getByText( 'Finder' ) ).toBeVisible(); + expect( screen.getByText( 'Visual Studio Code' ) ).toBeVisible(); + expect( screen.getByText( 'Terminal' ) ).toBeVisible(); + expect( screen.queryByText( 'Browser' ) ).not.toBeInTheDocument(); + + fireEvent.click( screen.getByText( 'Finder' ).closest( 'button' )! ); + expect( openSiteFolder ).toHaveBeenCalledWith( 'site-1' ); + + fireEvent.click( screen.getByText( 'phpMyAdmin' ).closest( 'button' )! ); + expect( openSiteUrl ).toHaveBeenCalledWith( + 'site-1', + '/phpmyadmin/index.php?route=/database/structure&db=wordpress' + ); + } ); + + it( 'hides the editor shortcut until an editor is configured', () => { + useUserPreferencesMock.mockReturnValue( { data: preferencesStub( null ) } ); + + renderView(); + + expect( screen.queryByText( 'Visual Studio Code' ) ).not.toBeInTheDocument(); + expect( screen.getByText( 'Finder' ) ).toBeVisible(); + } ); + + it( 'drops the Open in… section on hosts that cannot open local apps', () => { + useConnectorMock.mockReturnValue( connectorStub( false ) ); + + renderView(); + + expect( screen.queryByRole( 'heading', { name: 'Open in…' } ) ).not.toBeInTheDocument(); + } ); + // Rendered without a SessionUIProvider, so the open-site-url hook takes // its browser fallback path; inside the app these open the preview panel. it( 'routes shortcuts through the connector', async () => { diff --git a/apps/ui/src/components/site-overview-view/index.tsx b/apps/ui/src/components/site-overview-view/index.tsx index 96c242a8fe..d024975d44 100644 --- a/apps/ui/src/components/site-overview-view/index.tsx +++ b/apps/ui/src/components/site-overview-view/index.tsx @@ -2,6 +2,7 @@ import { useNavigate } from '@tanstack/react-router'; import { __ } from '@wordpress/i18n'; import { desktop, + grid, Icon, layout, media, @@ -17,12 +18,16 @@ import { useState } from 'react'; import { AgenticSigninBanner } from '@/components/agentic-signin-banner'; import { DeleteSiteDialog } from '@/components/delete-site-dialog'; import { OfflineBanner } from '@/components/offline-banner'; +import { useOpenInDestinations } from '@/components/open-in-menu/use-open-in-destinations'; import { PreviewToggleButton } from '@/components/preview-toggle-button'; import { ProgressiveBlur } from '@/components/progressive-blur'; import { SiteDropdown } from '@/components/site-dropdown'; +import { DATABASE_HOME_PATH } from '@/components/site-preview/address-bar'; import { isSiteSettingsTab, SiteSettingsForm } from '@/components/site-settings-view'; import * as Tabs from '@/components/tabs'; +import { useConnector } from '@/data/core'; import { useIsSiteStarting, useIsSiteStopping, useSites } from '@/data/queries/use-sites'; +import { useUserPreferences } from '@/data/queries/use-user-preferences'; import { useOpenSiteUrl } from '@/hooks/use-open-site-url'; import { useSidebarCollapsed } from '@/hooks/use-sidebar-collapsed'; import { useSiteManagementActions } from '@/hooks/use-site-management-actions'; @@ -47,6 +52,7 @@ interface OverviewButtonProps { loading?: boolean; loadingAnnouncement?: string; className?: string; + brandIcon?: boolean; } function OverviewHeader( { @@ -86,6 +92,7 @@ function OverviewButton( { loading, loadingAnnouncement, className, + brandIcon, }: OverviewButtonProps ) { return ( ); } @@ -122,7 +124,7 @@ function OverviewButton( { function ButtonSection( { title, children }: { title: string; children: ReactNode } ) { return (
-

{ title }

+

{ title }

{ children }
); diff --git a/apps/ui/src/components/site-overview-view/style.module.css b/apps/ui/src/components/site-overview-view/style.module.css index 0c8e4c3030..22c513887e 100644 --- a/apps/ui/src/components/site-overview-view/style.module.css +++ b/apps/ui/src/components/site-overview-view/style.module.css @@ -118,58 +118,61 @@ calc(var(--wpds-dimension-padding-2xl) + 46px); } -.buttonSection h2 { - margin: 0 0 var(--wpds-dimension-padding-md); +.columnHeading { + margin: 0; font-size: var(--wpds-typography-font-size-sm); line-height: var(--wpds-typography-line-height-sm); font-weight: var(--wpds-typography-font-weight-medium); color: var(--wpds-color-fg-content-neutral); } -/* Each section is a full-width row (Customize, then Manage), stacked. */ +/* Each section is a full-width row (Customize, Open in…, Manage), stacked. */ .actionsColumn { display: flex; flex-direction: column; width: 100%; min-width: 0; - gap: var(--wpds-dimension-padding-xl); + gap: var(--wpds-dimension-padding-3xl); } .buttonSection { + display: flex; + flex-direction: column; + gap: var(--wpds-dimension-padding-md); width: 100%; } -/* Buttons flow across the row and wrap to the next line as the panel narrows. */ +/* Equal-width cells reflow as the panel narrows, keeping every section aligned. */ .buttonGrid { - display: flex; - flex-wrap: wrap; - gap: var(--wpds-dimension-padding-xs); + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: var(--wpds-dimension-padding-md); } .overviewButton { - --wp-ui-button-height: 32px; + --wp-ui-button-height: 36px; display: inline-flex; align-items: center; justify-content: flex-start; - /* Never squeeze a button until its label wraps — wrap the whole button to - the next row instead. */ - flex: 0 0 auto; - gap: var(--wpds-dimension-padding-xs); + width: 100%; + gap: var(--wpds-dimension-padding-sm); min-width: 0; - padding: 0 var(--wpds-dimension-padding-sm); - border: 0; - border-radius: 4px; + padding: 0 var(--wpds-dimension-padding-md); + border: var(--wpds-border-width-xs) solid var(--wpds-color-stroke-surface-neutral); + border-radius: var(--wpds-border-radius-sm); background: transparent; color: var(--wpds-color-fg-content-neutral); text-align: start; } .overviewButton:not([data-disabled]):is(:hover, :focus-visible) { - background: var( - --wpds-color-bg-interactive-neutral-weak-active, - var(--wpds-color-bg-surface-neutral-weak) - ); + border-color: var(--wpds-color-stroke-interactive-neutral); + background: var(--wpds-color-bg-interactive-neutral-weak-active); +} + +.overviewButton[data-disabled] { + border-color: var(--wpds-color-stroke-surface-neutral-weak); } .overviewButtonIcon { @@ -191,7 +194,9 @@ } .overviewButtonLabel { + overflow: hidden; white-space: nowrap; + text-overflow: ellipsis; font-size: var(--wpds-typography-font-size-sm); line-height: var(--wpds-typography-line-height-sm); color: var(--wpds-color-fg-content-neutral); @@ -199,7 +204,7 @@ .destructiveButton .overviewButtonIcon, .destructiveButton .overviewButtonLabel { - color: var(--wpds-color-fg-content-error, var(--wpds-color-fg-content-neutral)); + color: var(--wpds-color-fg-content-error); } @media (max-width: 760px) { From 6ed3e529f1c4af7a32aef13c9a33d95214220776 Mon Sep 17 00:00:00 2001 From: Shaun Andrews Date: Thu, 6 Aug 2026 15:39:08 -0400 Subject: [PATCH 4/7] Fix overview shortcut interaction states --- .../components/site-overview-view/style.module.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/ui/src/components/site-overview-view/style.module.css b/apps/ui/src/components/site-overview-view/style.module.css index 22c513887e..d75c84207c 100644 --- a/apps/ui/src/components/site-overview-view/style.module.css +++ b/apps/ui/src/components/site-overview-view/style.module.css @@ -151,6 +151,8 @@ .overviewButton { --wp-ui-button-height: 36px; + --wp-ui-button-border-color: var(--wpds-color-stroke-surface-neutral); + --wp-ui-button-border-color-active: var(--wpds-color-stroke-interactive-neutral); display: inline-flex; align-items: center; @@ -207,6 +209,17 @@ color: var(--wpds-color-fg-content-error); } +.destructiveButton { + --wp-ui-button-background-color-active: var(--wpds-color-bg-surface-error-weak); + --wp-ui-button-border-color-active: var(--wpds-color-stroke-surface-error); + --wp-ui-button-foreground-color-active: var(--wpds-color-fg-content-error); +} + +.destructiveButton:not([data-disabled]):is(:hover, :focus-visible, :active) { + border-color: var(--wpds-color-stroke-surface-error); + background: var(--wpds-color-bg-surface-error-weak); +} + @media (max-width: 760px) { .content { padding: var(--wpds-dimension-padding-xl) var(--wpds-dimension-padding-md) From e0e5fef898f6770d05908448388d954fccb4f031 Mon Sep 17 00:00:00 2001 From: bcotrim Date: Mon, 10 Aug 2026 15:11:52 +0100 Subject: [PATCH 5/7] Use app toasts for terminal failures and drop the Open in section comment --- apps/ui/src/components/open-in-menu/index.test.tsx | 8 ++++++-- .../components/open-in-menu/use-open-in-destinations.tsx | 3 ++- apps/ui/src/components/site-overview-view/index.tsx | 5 ----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/ui/src/components/open-in-menu/index.test.tsx b/apps/ui/src/components/open-in-menu/index.test.tsx index 454c7e0b6d..3d3a6c327c 100644 --- a/apps/ui/src/components/open-in-menu/index.test.tsx +++ b/apps/ui/src/components/open-in-menu/index.test.tsx @@ -2,6 +2,7 @@ import '@testing-library/jest-dom/vitest'; import { captureException } from '@studio/common/lib/error-reporting'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { toast } from '@/data/app-messages'; import { useConnector } from '@/data/core'; import { useStartSite } from '@/data/queries/use-sites'; import { useUserPreferences } from '@/data/queries/use-user-preferences'; @@ -85,6 +86,10 @@ vi.mock( '@studio/common/lib/error-reporting', () => ( { captureException: vi.fn(), } ) ); +vi.mock( '@/data/app-messages', () => ( { + toast: { error: vi.fn() }, +} ) ); + const useConnectorMock = vi.mocked( useConnector, { partial: true } ); const useStartSiteMock = vi.mocked( useStartSite, { partial: true } ); const useUserPreferencesMock = vi.mocked( useUserPreferences, { partial: true } ); @@ -156,7 +161,6 @@ describe( 'OpenInMenu', () => { it( 'reports terminal failures and tells the user', async () => { const error = new Error( 'Terminal unavailable' ); openSiteInTerminal.mockRejectedValueOnce( error ); - const alertMock = vi.spyOn( window, 'alert' ).mockImplementation( () => undefined ); const consoleErrorMock = vi.spyOn( console, 'error' ).mockImplementation( () => undefined ); renderMenu( { running: true } ); @@ -164,7 +168,7 @@ describe( 'OpenInMenu', () => { await waitFor( () => expect( captureException ).toHaveBeenCalledWith( error ) ); expect( consoleErrorMock ).toHaveBeenCalledWith( 'Failed to open site in terminal:', error ); - expect( alertMock ).toHaveBeenCalledWith( 'Could not open the terminal.' ); + expect( toast.error ).toHaveBeenCalledWith( 'Could not open the terminal.' ); } ); it( 'records Tracks events for browser and folder only (editor and terminal emit in Main)', () => { diff --git a/apps/ui/src/components/open-in-menu/use-open-in-destinations.tsx b/apps/ui/src/components/open-in-menu/use-open-in-destinations.tsx index b3362643f9..3bbeaaf9ce 100644 --- a/apps/ui/src/components/open-in-menu/use-open-in-destinations.tsx +++ b/apps/ui/src/components/open-in-menu/use-open-in-destinations.tsx @@ -6,6 +6,7 @@ import { useNavigate } from '@tanstack/react-router'; import { __ } from '@wordpress/i18n'; import { code, external } from '@wordpress/icons'; import { getPreviewRealm, getRealmOpenEvent } from '@/components/site-preview/address-bar'; +import { toast } from '@/data/app-messages'; import { useConnector } from '@/data/core'; import { useUserPreferences } from '@/data/queries/use-user-preferences'; import { editorLogos, finderLogo, folderLogo, terminalLogo, terminalLogos } from '@/lib/logos'; @@ -129,7 +130,7 @@ export function useOpenInDestinations( void connector.openSiteInTerminal( site.id ).catch( ( error ) => { console.error( 'Failed to open site in terminal:', error ); captureException( error ); - alert( __( 'Could not open the terminal.' ) ); + toast.error( __( 'Could not open the terminal.' ) ); } ); }, }, diff --git a/apps/ui/src/components/site-overview-view/index.tsx b/apps/ui/src/components/site-overview-view/index.tsx index c31beb37c9..e51a34dbe1 100644 --- a/apps/ui/src/components/site-overview-view/index.tsx +++ b/apps/ui/src/components/site-overview-view/index.tsx @@ -134,11 +134,6 @@ function ButtonSection( { title, children }: { title: string; children: ReactNod ); } -/** - * The apps a site can be handed off to. Mirrors the preview toolbar's - * "Open in…" menu, minus the browser (the preview itself is the browser here) - * and plus phpMyAdmin, which the classic overview also grouped with them. - */ function OpenInSection( { site, busy, From 3b6c4b0b9e6d08452944999eb1abc419aa58b190 Mon Sep 17 00:00:00 2001 From: bcotrim Date: Mon, 10 Aug 2026 16:56:49 +0100 Subject: [PATCH 6/7] Align export labels with Classic and cover the phpMyAdmin Tracks event --- apps/ui/src/components/site-overview-view/index.test.tsx | 7 +++++-- apps/ui/src/components/site-overview-view/index.tsx | 2 +- apps/ui/src/hooks/use-site-management-actions.ts | 8 ++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/ui/src/components/site-overview-view/index.test.tsx b/apps/ui/src/components/site-overview-view/index.test.tsx index 4e57c8f55e..2efbfc757f 100644 --- a/apps/ui/src/components/site-overview-view/index.test.tsx +++ b/apps/ui/src/components/site-overview-view/index.test.tsx @@ -239,8 +239,8 @@ describe( 'SiteOverviewView', () => { expect( screen.getByText( 'Media Library' ) ).toBeVisible(); expect( screen.queryByText( 'Customizer' ) ).not.toBeInTheDocument(); expect( screen.getByText( 'Duplicate' ) ).toBeVisible(); - expect( screen.getByText( 'Export' ) ).toBeVisible(); - expect( screen.getByText( 'Export DB' ) ).toBeVisible(); + expect( screen.getByText( 'Export entire site' ) ).toBeVisible(); + expect( screen.getByText( 'Export database' ) ).toBeVisible(); expect( screen.getByText( 'Delete' ) ).toBeVisible(); expect( screen.queryByDisplayValue( 'Demo Site' ) ).not.toBeInTheDocument(); } ); @@ -521,6 +521,9 @@ describe( 'SiteOverviewView', () => { 'site-1', '/phpmyadmin/index.php?route=/database/structure&db=wordpress' ); + expect( trackEvent ).toHaveBeenCalledWith( 'studio_site_open_phpmyadmin', { + browser: 'internal', + } ); } ); it( 'hides the editor shortcut until an editor is configured', () => { diff --git a/apps/ui/src/components/site-overview-view/index.tsx b/apps/ui/src/components/site-overview-view/index.tsx index e51a34dbe1..4fc4035540 100644 --- a/apps/ui/src/components/site-overview-view/index.tsx +++ b/apps/ui/src/components/site-overview-view/index.tsx @@ -225,7 +225,6 @@ function SiteOverviewBody( { onTabChange: ( tab: SiteSettingsTabId ) => void; } ) { const navigate = useNavigate(); - const connector = useConnector(); const isStarting = useIsSiteStarting( site.id ); const isStopping = useIsSiteStopping( site.id ); const [ deleteOpen, setDeleteOpen ] = useState( false ); @@ -241,6 +240,7 @@ function SiteOverviewBody( { // first when needed) rather than the external browser. const openSiteUrl = useOpenSiteUrl( site ); + const connector = useConnector(); const openCustomize = ( url: string, entryPoint: TracksCustomizeEntryPoint ) => { // The agentic UI opens customize screens in its in-app preview panel, not the OS browser. void connector.trackEvent( TRACKS_EVENTS.SITE_OPEN_CUSTOMIZE, { diff --git a/apps/ui/src/hooks/use-site-management-actions.ts b/apps/ui/src/hooks/use-site-management-actions.ts index 0f0e6c9c33..e70ee8e77a 100644 --- a/apps/ui/src/hooks/use-site-management-actions.ts +++ b/apps/ui/src/hooks/use-site-management-actions.ts @@ -24,8 +24,8 @@ export interface SiteManagementAction { } /** - * The canonical "manage this site" actions — Duplicate, Export, Export DB, - * Delete — shared by every surface that offers them, so labels, icons, + * The canonical "manage this site" actions — Duplicate, Export entire site, + * Export database, Delete — shared by every surface that offers them, so labels, icons, * order, and disabled logic don't drift apart between surfaces. * * Delete needs a confirmation dialog whose "deleted" navigation differs per @@ -58,7 +58,7 @@ export function useSiteManagementActions( { id: 'export', icon: download, - label: __( 'Export' ), + label: __( 'Export entire site' ), loading: exportFullSite.isPending, loadingAnnouncement: __( 'Exporting site' ), disabled: isExporting, @@ -68,7 +68,7 @@ export function useSiteManagementActions( { id: 'export-db', icon: grid, - label: __( 'Export DB' ), + label: __( 'Export database' ), loading: exportDatabase.isPending, loadingAnnouncement: __( 'Exporting database' ), disabled: isExporting, From 82ee888ba8b9db7f5d5ea6d526857cbb2a1d305a Mon Sep 17 00:00:00 2001 From: bcotrim Date: Mon, 10 Aug 2026 17:04:21 +0100 Subject: [PATCH 7/7] Give phpMyAdmin its own mark so it stops sharing the Export database icon --- .../components/site-overview-view/index.tsx | 5 +++-- apps/ui/src/lib/logos.tsx | 20 +++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/ui/src/components/site-overview-view/index.tsx b/apps/ui/src/components/site-overview-view/index.tsx index 4fc4035540..3634bfd3f0 100644 --- a/apps/ui/src/components/site-overview-view/index.tsx +++ b/apps/ui/src/components/site-overview-view/index.tsx @@ -6,7 +6,6 @@ import { useNavigate } from '@tanstack/react-router'; import { __ } from '@wordpress/i18n'; import { desktop, - grid, Icon, layout, media, @@ -36,6 +35,7 @@ import { useOpenSiteUrl } from '@/hooks/use-open-site-url'; import { useSidebarCollapsed } from '@/hooks/use-sidebar-collapsed'; import { useSiteManagementActions } from '@/hooks/use-site-management-actions'; import { useTrafficLightSpace } from '@/hooks/use-traffic-light-space'; +import { databaseLogo } from '@/lib/logos'; import styles from './style.module.css'; import type { SiteSettingsTabId } from '@/components/site-settings-view'; import type { SiteDetails } from '@/data/core'; @@ -166,7 +166,8 @@ function OpenInSection( { /> ) ) } } + brandIcon + icon={ } label={ __( 'phpMyAdmin' ) } disabled={ busy } onClick={ () => { diff --git a/apps/ui/src/lib/logos.tsx b/apps/ui/src/lib/logos.tsx index 5acf6145a4..3fac14f8c6 100644 --- a/apps/ui/src/lib/logos.tsx +++ b/apps/ui/src/lib/logos.tsx @@ -6,8 +6,8 @@ import type { ReactElement } from 'react'; `currentColor` so they follow the theme-aware foreground tokens in light and dark mode. Third-party path data comes from Simple Icons (CC0) except VS Code, which comes from Devicon (MIT) because Simple Icons no longer ships - it. Finder, Terminal, and the generic folder are drawn in-house in the same - stroke style as `./icons`. Each root svg declares its own `fill` so the + it. Finder, Terminal, the generic folder, and the database are drawn + in-house in the same stroke style as `./icons`. Each root svg declares its own `fill` so the stroke-drawn marks are not affected by a consumer's fill mapping for @wordpress/icons. */ @@ -124,6 +124,22 @@ export const terminalLogo = ( ); +export const databaseLogo = ( + + + + + +); + export const editorLogos: Partial< Record< SupportedEditor, ReactElement > > = { antigravity: antigravityLogo, vscode: vscodeLogo,