@xenide-io/the-old-ui-theme 0.6.14 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -170,7 +170,9 @@ var IconPlayCircle = createIconBase({
170
170
  )
171
171
  });
172
172
  var IconGridView = createIconBase({
173
- paths: fill("M3 3v8h8V3H3zm6 6H5V5h4v4zm-6 4v8h8v-8H3zm6 6H5v-4h4v4zm4-16v8h8V3h-8zm6 6h-4V5h4v4zm-6 4v8h8v-8h-8zm6 6h-4v-4h4v4z")
173
+ paths: fill(
174
+ "M3,3v8h8V3H3z M9,9H5V5h4V9z M3,13v8h8v-8H3z M9,19H5v-4h4V19z M13,3v8h8V3H13z M19,9h-4V5h4V9z M13,13v8h8v-8H13z M19,19h-4v-4h4V19z"
175
+ )
174
176
  });
175
177
  var IconListView = createIconBase({
176
178
  paths: fill(
@@ -17,7 +17,7 @@ import {
17
17
  IconSearch,
18
18
  IconTuning,
19
19
  cn
20
- } from "./chunk-E5ZBQYEX.mjs";
20
+ } from "./chunk-KDR6HI6F.mjs";
21
21
  import {
22
22
  __objRest,
23
23
  __spreadProps,
package/dist/index.js CHANGED
@@ -480,7 +480,9 @@ var IconPlayCircle = createIconBase({
480
480
  )
481
481
  });
482
482
  var IconGridView = createIconBase({
483
- paths: fill("M3 3v8h8V3H3zm6 6H5V5h4v4zm-6 4v8h8v-8H3zm6 6H5v-4h4v4zm4-16v8h8V3h-8zm6 6h-4V5h4v4zm-6 4v8h8v-8h-8zm6 6h-4v-4h4v4z")
483
+ paths: fill(
484
+ "M3,3v8h8V3H3z M9,9H5V5h4V9z M3,13v8h8v-8H3z M9,19H5v-4h4V19z M13,3v8h8V3H13z M19,9h-4V5h4V9z M13,13v8h8v-8H13z M19,19h-4v-4h4V19z"
485
+ )
484
486
  });
485
487
  var IconListView = createIconBase({
486
488
  paths: fill(
package/dist/index.mjs CHANGED
@@ -63,7 +63,7 @@ import {
63
63
  isThemeId,
64
64
  persistTheme,
65
65
  readStoredTheme
66
- } from "./chunk-VOCHIR3W.mjs";
66
+ } from "./chunk-VXISSXTB.mjs";
67
67
  import {
68
68
  ButtonChrome,
69
69
  CANONICAL_ICONS,
@@ -157,7 +157,7 @@ import {
157
157
  TooltipProvider,
158
158
  createIconBase,
159
159
  createIconBasePath
160
- } from "./chunk-E5ZBQYEX.mjs";
160
+ } from "./chunk-KDR6HI6F.mjs";
161
161
  import "./chunk-FWCSY2DS.mjs";
162
162
  export {
163
163
  Accordion,
package/dist/suite.d.mts CHANGED
@@ -170,7 +170,6 @@ type SuiteSpinnerComponent = ComponentType<{
170
170
 
171
171
  declare function appSwitcherTriggerClass(open: boolean, collapsed?: boolean): string;
172
172
  declare function appSwitcherMarkClass(): string;
173
- /** No sticky Radix highlight while waiting for cross-app redirect. */
174
173
  declare function appSwitcherMenuItemClass(): string;
175
174
  declare function AppSwitcherMark({ children, collapsed, }: {
176
175
  children: ReactNode;
@@ -186,6 +185,10 @@ interface SuiteAppEntry {
186
185
  description: string;
187
186
  icon: string;
188
187
  }
188
+ interface SuiteAppSelectOptions {
189
+ /** Open the handoff in a separate browser tab when requested by a caller. */
190
+ newTab?: boolean;
191
+ }
189
192
  /**
190
193
  * Suite app switcher dropdown. Apps inject their brand mark/title, the
191
194
  * visible app list, the cross-app navigation handler, and the theme
@@ -194,7 +197,7 @@ interface SuiteAppEntry {
194
197
  declare function AppSwitcher({ apps, currentApp, onSelect, mark, title, collapsed, dropdownMenu: DropdownMenu, dropdownItem: DropdownItem, triggerId, triggerDataTest, }: {
195
198
  apps: SuiteAppEntry[];
196
199
  currentApp: string;
197
- onSelect: (app: SuiteAppEntry) => void;
200
+ onSelect: (app: SuiteAppEntry, options?: SuiteAppSelectOptions) => void;
198
201
  mark: ReactNode;
199
202
  title: ReactNode;
200
203
  collapsed?: boolean;
@@ -984,4 +987,4 @@ interface SuiteAppLayoutProps {
984
987
  */
985
988
  declare function SuiteAppLayout({ sidebar, children, mobileHeader, bottomNav, sidebarWidth, collapsed, lockMainScroll, onStartResize, onResizeBy, onResetWidth, className, }: SuiteAppLayoutProps): react.JSX.Element;
986
989
 
987
- export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SIDEBAR_COLLAPSE_THRESHOLD, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_EXPANDED_WIDTH, SIDEBAR_RAIL_WIDTH, SUITE_APPS, SUITE_APP_MAP, SUITE_ASK_AI_OPEN_KEY, SUITE_ASK_AI_OPEN_QUERY, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_MUTATED_EVENT, SUITE_OPEN_ASK_AI_EVENT, SUITE_SIDEBAR_WIDTH_COOKIE, SUITE_SIDEBAR_WIDTH_KEY, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, type SuiteAiAction, type SuiteAiActionStatus, type SuiteAiChatMessage, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSlug, SuiteAppStrip, type SuiteAskAiOpenDetail, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, type SuiteHrefTarget, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsLayout, type SuiteSettingsLayoutProps, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, type SuiteWorkspaceEntry, type SuiteWorkspaceGroup, SuiteWorkspaceSwitcher, TURTLETIME_TIMER_MUTATED_EVENT, TodayAskAiCard, TodayCalibrating, TodayEmptyAction, TodayEmptyState, TodayHero, TodayLayout, TodayPageFrame, TodayPanel, type TodayPrimaryAccent, TodayPrimaryAction, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, classifySuiteHref, cn, consumePendingAskAiPrompt, consumeSuiteAskAiOpenFromLocation, getTodayGreeting, openSuiteAskAi, pathWithSuiteAskAiOpen, persistSidebarWidth, persistSuiteAskAiOpen, readSidebarWidth, readSuiteAskAiOpen, resetSuiteSidebarWidth, resolveSuiteNotificationHref, sidebarColumnWidth, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSidebarWidth, useSuiteTheme };
990
+ export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SIDEBAR_COLLAPSE_THRESHOLD, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_EXPANDED_WIDTH, SIDEBAR_RAIL_WIDTH, SUITE_APPS, SUITE_APP_MAP, SUITE_ASK_AI_OPEN_KEY, SUITE_ASK_AI_OPEN_QUERY, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_MUTATED_EVENT, SUITE_OPEN_ASK_AI_EVENT, SUITE_SIDEBAR_WIDTH_COOKIE, SUITE_SIDEBAR_WIDTH_KEY, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, type SuiteAiAction, type SuiteAiActionStatus, type SuiteAiChatMessage, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSelectOptions, type SuiteAppSlug, SuiteAppStrip, type SuiteAskAiOpenDetail, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, type SuiteHrefTarget, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsLayout, type SuiteSettingsLayoutProps, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, type SuiteWorkspaceEntry, type SuiteWorkspaceGroup, SuiteWorkspaceSwitcher, TURTLETIME_TIMER_MUTATED_EVENT, TodayAskAiCard, TodayCalibrating, TodayEmptyAction, TodayEmptyState, TodayHero, TodayLayout, TodayPageFrame, TodayPanel, type TodayPrimaryAccent, TodayPrimaryAction, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, classifySuiteHref, cn, consumePendingAskAiPrompt, consumeSuiteAskAiOpenFromLocation, getTodayGreeting, openSuiteAskAi, pathWithSuiteAskAiOpen, persistSidebarWidth, persistSuiteAskAiOpen, readSidebarWidth, readSuiteAskAiOpen, resetSuiteSidebarWidth, resolveSuiteNotificationHref, sidebarColumnWidth, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSidebarWidth, useSuiteTheme };
package/dist/suite.d.ts CHANGED
@@ -170,7 +170,6 @@ type SuiteSpinnerComponent = ComponentType<{
170
170
 
171
171
  declare function appSwitcherTriggerClass(open: boolean, collapsed?: boolean): string;
172
172
  declare function appSwitcherMarkClass(): string;
173
- /** No sticky Radix highlight while waiting for cross-app redirect. */
174
173
  declare function appSwitcherMenuItemClass(): string;
175
174
  declare function AppSwitcherMark({ children, collapsed, }: {
176
175
  children: ReactNode;
@@ -186,6 +185,10 @@ interface SuiteAppEntry {
186
185
  description: string;
187
186
  icon: string;
188
187
  }
188
+ interface SuiteAppSelectOptions {
189
+ /** Open the handoff in a separate browser tab when requested by a caller. */
190
+ newTab?: boolean;
191
+ }
189
192
  /**
190
193
  * Suite app switcher dropdown. Apps inject their brand mark/title, the
191
194
  * visible app list, the cross-app navigation handler, and the theme
@@ -194,7 +197,7 @@ interface SuiteAppEntry {
194
197
  declare function AppSwitcher({ apps, currentApp, onSelect, mark, title, collapsed, dropdownMenu: DropdownMenu, dropdownItem: DropdownItem, triggerId, triggerDataTest, }: {
195
198
  apps: SuiteAppEntry[];
196
199
  currentApp: string;
197
- onSelect: (app: SuiteAppEntry) => void;
200
+ onSelect: (app: SuiteAppEntry, options?: SuiteAppSelectOptions) => void;
198
201
  mark: ReactNode;
199
202
  title: ReactNode;
200
203
  collapsed?: boolean;
@@ -984,4 +987,4 @@ interface SuiteAppLayoutProps {
984
987
  */
985
988
  declare function SuiteAppLayout({ sidebar, children, mobileHeader, bottomNav, sidebarWidth, collapsed, lockMainScroll, onStartResize, onResizeBy, onResetWidth, className, }: SuiteAppLayoutProps): react.JSX.Element;
986
989
 
987
- export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SIDEBAR_COLLAPSE_THRESHOLD, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_EXPANDED_WIDTH, SIDEBAR_RAIL_WIDTH, SUITE_APPS, SUITE_APP_MAP, SUITE_ASK_AI_OPEN_KEY, SUITE_ASK_AI_OPEN_QUERY, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_MUTATED_EVENT, SUITE_OPEN_ASK_AI_EVENT, SUITE_SIDEBAR_WIDTH_COOKIE, SUITE_SIDEBAR_WIDTH_KEY, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, type SuiteAiAction, type SuiteAiActionStatus, type SuiteAiChatMessage, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSlug, SuiteAppStrip, type SuiteAskAiOpenDetail, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, type SuiteHrefTarget, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsLayout, type SuiteSettingsLayoutProps, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, type SuiteWorkspaceEntry, type SuiteWorkspaceGroup, SuiteWorkspaceSwitcher, TURTLETIME_TIMER_MUTATED_EVENT, TodayAskAiCard, TodayCalibrating, TodayEmptyAction, TodayEmptyState, TodayHero, TodayLayout, TodayPageFrame, TodayPanel, type TodayPrimaryAccent, TodayPrimaryAction, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, classifySuiteHref, cn, consumePendingAskAiPrompt, consumeSuiteAskAiOpenFromLocation, getTodayGreeting, openSuiteAskAi, pathWithSuiteAskAiOpen, persistSidebarWidth, persistSuiteAskAiOpen, readSidebarWidth, readSuiteAskAiOpen, resetSuiteSidebarWidth, resolveSuiteNotificationHref, sidebarColumnWidth, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSidebarWidth, useSuiteTheme };
990
+ export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SIDEBAR_COLLAPSE_THRESHOLD, SIDEBAR_DEFAULT_WIDTH, SIDEBAR_MAX_WIDTH, SIDEBAR_MIN_EXPANDED_WIDTH, SIDEBAR_RAIL_WIDTH, SUITE_APPS, SUITE_APP_MAP, SUITE_ASK_AI_OPEN_KEY, SUITE_ASK_AI_OPEN_QUERY, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_MUTATED_EVENT, SUITE_OPEN_ASK_AI_EVENT, SUITE_SIDEBAR_WIDTH_COOKIE, SUITE_SIDEBAR_WIDTH_KEY, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, type SuiteAiAction, type SuiteAiActionStatus, type SuiteAiChatMessage, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSelectOptions, type SuiteAppSlug, SuiteAppStrip, type SuiteAskAiOpenDetail, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, type SuiteHrefTarget, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsLayout, type SuiteSettingsLayoutProps, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, type SuiteWorkspaceEntry, type SuiteWorkspaceGroup, SuiteWorkspaceSwitcher, TURTLETIME_TIMER_MUTATED_EVENT, TodayAskAiCard, TodayCalibrating, TodayEmptyAction, TodayEmptyState, TodayHero, TodayLayout, TodayPageFrame, TodayPanel, type TodayPrimaryAccent, TodayPrimaryAction, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, classifySuiteHref, cn, consumePendingAskAiPrompt, consumeSuiteAskAiOpenFromLocation, getTodayGreeting, openSuiteAskAi, pathWithSuiteAskAiOpen, persistSidebarWidth, persistSuiteAskAiOpen, readSidebarWidth, readSuiteAskAiOpen, resetSuiteSidebarWidth, resolveSuiteNotificationHref, sidebarColumnWidth, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSidebarWidth, useSuiteTheme };
package/dist/suite.js CHANGED
@@ -1370,43 +1370,53 @@ function AppSwitcher({
1370
1370
  ),
1371
1371
  children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { id: "app-switcher-menu", className: "p-1", "data-test": "app-switcher-menu", children: [
1372
1372
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "px-3 py-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-ph-mutedtext", children: "Switch application" }),
1373
- apps.map((app) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1374
- DropdownItem2,
1375
- {
1376
- id: `switch-app-${app.slug}`,
1377
- "data-test": `switch-app-${app.slug}`,
1378
- onMouseDown: (e) => e.preventDefault(),
1379
- onClick: () => {
1380
- setOpen(false);
1381
- onSelect(app);
1382
- },
1383
- className: appSwitcherMenuItemClass(),
1384
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "flex min-w-0 items-center gap-3", children: [
1385
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1386
- import_image.default,
1387
- {
1388
- src: app.icon,
1389
- alt: "",
1390
- width: 28,
1391
- height: 28,
1392
- className: "h-7 w-7 shrink-0 rounded-md object-contain"
1393
- }
1394
- ),
1395
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "min-w-0 flex-1", children: [
1396
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "block truncate text-sm font-medium text-ph-ink", children: app.name }),
1397
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "block truncate text-xs text-ph-mutedtext", children: app.description })
1398
- ] }),
1399
- app.slug === currentApp ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1400
- import_iconoir_react2.Check,
1401
- {
1402
- className: "h-4 w-4 shrink-0 text-ph-brand",
1403
- "aria-hidden": "true"
1404
- }
1405
- ) : null
1406
- ] })
1407
- },
1408
- app.slug
1409
- ))
1373
+ apps.filter((app) => app.slug !== currentApp).map((app) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "relative", children: [
1374
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1375
+ DropdownItem2,
1376
+ {
1377
+ id: `switch-app-${app.slug}`,
1378
+ "data-test": `switch-app-${app.slug}`,
1379
+ onMouseDown: (e) => e.preventDefault(),
1380
+ onClick: () => {
1381
+ setOpen(false);
1382
+ onSelect(app);
1383
+ },
1384
+ className: cn(appSwitcherMenuItemClass(), "pr-11"),
1385
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "flex min-w-0 items-center gap-3", children: [
1386
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1387
+ import_image.default,
1388
+ {
1389
+ src: app.icon,
1390
+ alt: "",
1391
+ width: 28,
1392
+ height: 28,
1393
+ className: "h-7 w-7 shrink-0 rounded-md object-contain"
1394
+ }
1395
+ ),
1396
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "min-w-0 flex-1", children: [
1397
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "block truncate text-sm font-medium text-ph-ink", children: app.name }),
1398
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "block truncate text-xs text-ph-mutedtext", children: app.description })
1399
+ ] })
1400
+ ] })
1401
+ }
1402
+ ),
1403
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1404
+ "button",
1405
+ {
1406
+ type: "button",
1407
+ id: `switch-app-${app.slug}-new-tab`,
1408
+ "data-test": `switch-app-${app.slug}-new-tab`,
1409
+ "aria-label": `Open ${app.name} in a new tab`,
1410
+ onMouseDown: (e) => e.preventDefault(),
1411
+ onClick: () => {
1412
+ setOpen(false);
1413
+ onSelect(app, { newTab: true });
1414
+ },
1415
+ className: "absolute right-1.5 top-1/2 z-[1] flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-md text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ph-brand/35",
1416
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_iconoir_react2.OpenNewWindow, { className: "h-4 w-4", "aria-hidden": "true" })
1417
+ }
1418
+ )
1419
+ ] }, app.slug))
1410
1420
  ] })
1411
1421
  }
1412
1422
  );
@@ -1643,10 +1653,10 @@ function SuiteNotificationBell({
1643
1653
  panelClassName: "w-80 overflow-hidden p-0",
1644
1654
  open,
1645
1655
  onOpenChange,
1646
- trigger: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "relative inline-flex h-11 w-11 items-center justify-center overflow-visible rounded-full text-ph-mutedtext transition hover:bg-ph-muted hover:text-ph-ink", children: [
1656
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "relative inline-flex h-11 w-11 items-center justify-center overflow-visible rounded-full text-ph-mutedtext transition hover:bg-ph-muted hover:text-ph-ink", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "relative inline-flex h-5 w-5", children: [
1647
1657
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_iconoir_react3.Bell, { className: "h-5 w-5", strokeWidth: 1.75, "aria-hidden": true }),
1648
- unread > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pointer-events-none absolute right-0 top-0 z-[1] flex h-4 min-w-4 translate-x-1/2 items-center justify-center rounded-full bg-ph-brand px-1 text-[9px] font-semibold leading-none text-white ring-2 ring-ph-surface", children: unread > 9 ? "9+" : unread }) : null
1649
- ] }),
1658
+ unread > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pointer-events-none absolute -right-2 -top-2 z-[1] flex h-4 min-w-4 items-center justify-center rounded-full bg-ph-brand px-1 text-[9px] font-semibold leading-none text-white ring-2 ring-ph-surface", children: unread > 9 ? "9+" : unread }) : null
1659
+ ] }) }),
1650
1660
  children: [
1651
1661
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between border-b border-ph-border px-3 py-2", children: [
1652
1662
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-sm font-semibold text-ph-ink", children: "Notifications" }),
package/dist/suite.mjs CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  DropdownItem,
3
3
  DropdownMenu,
4
4
  Tooltip
5
- } from "./chunk-E5ZBQYEX.mjs";
5
+ } from "./chunk-KDR6HI6F.mjs";
6
6
  import {
7
7
  SuiteThemeProvider,
8
8
  useSuiteTheme
@@ -896,7 +896,10 @@ function TodayEmptyState({
896
896
  // src/suite/components/app-switcher.tsx
897
897
  import Image from "next/image";
898
898
  import { useState } from "react";
899
- import { Check, NavArrowDown as ChevronDown } from "iconoir-react";
899
+ import {
900
+ NavArrowDown as ChevronDown,
901
+ OpenNewWindow
902
+ } from "iconoir-react";
900
903
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
901
904
  function appSwitcherTriggerClass(open, collapsed = false) {
902
905
  return cn(
@@ -1013,43 +1016,53 @@ function AppSwitcher({
1013
1016
  ),
1014
1017
  children: /* @__PURE__ */ jsxs6("div", { id: "app-switcher-menu", className: "p-1", "data-test": "app-switcher-menu", children: [
1015
1018
  /* @__PURE__ */ jsx6("p", { className: "px-3 py-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-ph-mutedtext", children: "Switch application" }),
1016
- apps.map((app) => /* @__PURE__ */ jsx6(
1017
- DropdownItem2,
1018
- {
1019
- id: `switch-app-${app.slug}`,
1020
- "data-test": `switch-app-${app.slug}`,
1021
- onMouseDown: (e) => e.preventDefault(),
1022
- onClick: () => {
1023
- setOpen(false);
1024
- onSelect(app);
1025
- },
1026
- className: appSwitcherMenuItemClass(),
1027
- children: /* @__PURE__ */ jsxs6("span", { className: "flex min-w-0 items-center gap-3", children: [
1028
- /* @__PURE__ */ jsx6(
1029
- Image,
1030
- {
1031
- src: app.icon,
1032
- alt: "",
1033
- width: 28,
1034
- height: 28,
1035
- className: "h-7 w-7 shrink-0 rounded-md object-contain"
1036
- }
1037
- ),
1038
- /* @__PURE__ */ jsxs6("span", { className: "min-w-0 flex-1", children: [
1039
- /* @__PURE__ */ jsx6("span", { className: "block truncate text-sm font-medium text-ph-ink", children: app.name }),
1040
- /* @__PURE__ */ jsx6("span", { className: "block truncate text-xs text-ph-mutedtext", children: app.description })
1041
- ] }),
1042
- app.slug === currentApp ? /* @__PURE__ */ jsx6(
1043
- Check,
1044
- {
1045
- className: "h-4 w-4 shrink-0 text-ph-brand",
1046
- "aria-hidden": "true"
1047
- }
1048
- ) : null
1049
- ] })
1050
- },
1051
- app.slug
1052
- ))
1019
+ apps.filter((app) => app.slug !== currentApp).map((app) => /* @__PURE__ */ jsxs6("div", { className: "relative", children: [
1020
+ /* @__PURE__ */ jsx6(
1021
+ DropdownItem2,
1022
+ {
1023
+ id: `switch-app-${app.slug}`,
1024
+ "data-test": `switch-app-${app.slug}`,
1025
+ onMouseDown: (e) => e.preventDefault(),
1026
+ onClick: () => {
1027
+ setOpen(false);
1028
+ onSelect(app);
1029
+ },
1030
+ className: cn(appSwitcherMenuItemClass(), "pr-11"),
1031
+ children: /* @__PURE__ */ jsxs6("span", { className: "flex min-w-0 items-center gap-3", children: [
1032
+ /* @__PURE__ */ jsx6(
1033
+ Image,
1034
+ {
1035
+ src: app.icon,
1036
+ alt: "",
1037
+ width: 28,
1038
+ height: 28,
1039
+ className: "h-7 w-7 shrink-0 rounded-md object-contain"
1040
+ }
1041
+ ),
1042
+ /* @__PURE__ */ jsxs6("span", { className: "min-w-0 flex-1", children: [
1043
+ /* @__PURE__ */ jsx6("span", { className: "block truncate text-sm font-medium text-ph-ink", children: app.name }),
1044
+ /* @__PURE__ */ jsx6("span", { className: "block truncate text-xs text-ph-mutedtext", children: app.description })
1045
+ ] })
1046
+ ] })
1047
+ }
1048
+ ),
1049
+ /* @__PURE__ */ jsx6(
1050
+ "button",
1051
+ {
1052
+ type: "button",
1053
+ id: `switch-app-${app.slug}-new-tab`,
1054
+ "data-test": `switch-app-${app.slug}-new-tab`,
1055
+ "aria-label": `Open ${app.name} in a new tab`,
1056
+ onMouseDown: (e) => e.preventDefault(),
1057
+ onClick: () => {
1058
+ setOpen(false);
1059
+ onSelect(app, { newTab: true });
1060
+ },
1061
+ className: "absolute right-1.5 top-1/2 z-[1] flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-md text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ph-brand/35",
1062
+ children: /* @__PURE__ */ jsx6(OpenNewWindow, { className: "h-4 w-4", "aria-hidden": "true" })
1063
+ }
1064
+ )
1065
+ ] }, app.slug))
1053
1066
  ] })
1054
1067
  }
1055
1068
  );
@@ -1286,10 +1299,10 @@ function SuiteNotificationBell({
1286
1299
  panelClassName: "w-80 overflow-hidden p-0",
1287
1300
  open,
1288
1301
  onOpenChange,
1289
- trigger: /* @__PURE__ */ jsxs7("span", { className: "relative inline-flex h-11 w-11 items-center justify-center overflow-visible rounded-full text-ph-mutedtext transition hover:bg-ph-muted hover:text-ph-ink", children: [
1302
+ trigger: /* @__PURE__ */ jsx7("span", { className: "relative inline-flex h-11 w-11 items-center justify-center overflow-visible rounded-full text-ph-mutedtext transition hover:bg-ph-muted hover:text-ph-ink", children: /* @__PURE__ */ jsxs7("span", { className: "relative inline-flex h-5 w-5", children: [
1290
1303
  /* @__PURE__ */ jsx7(Bell, { className: "h-5 w-5", strokeWidth: 1.75, "aria-hidden": true }),
1291
- unread > 0 ? /* @__PURE__ */ jsx7("span", { className: "pointer-events-none absolute right-0 top-0 z-[1] flex h-4 min-w-4 translate-x-1/2 items-center justify-center rounded-full bg-ph-brand px-1 text-[9px] font-semibold leading-none text-white ring-2 ring-ph-surface", children: unread > 9 ? "9+" : unread }) : null
1292
- ] }),
1304
+ unread > 0 ? /* @__PURE__ */ jsx7("span", { className: "pointer-events-none absolute -right-2 -top-2 z-[1] flex h-4 min-w-4 items-center justify-center rounded-full bg-ph-brand px-1 text-[9px] font-semibold leading-none text-white ring-2 ring-ph-surface", children: unread > 9 ? "9+" : unread }) : null
1305
+ ] }) }),
1293
1306
  children: [
1294
1307
  /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between border-b border-ph-border px-3 py-2", children: [
1295
1308
  /* @__PURE__ */ jsx7("span", { className: "text-sm font-semibold text-ph-ink", children: "Notifications" }),
@@ -2727,7 +2740,7 @@ function SuiteAiPanel({
2727
2740
 
2728
2741
  // src/suite/components/workspace-switcher.tsx
2729
2742
  import { useState as useState8 } from "react";
2730
- import { Building, Check as Check2, NavArrowDown } from "iconoir-react";
2743
+ import { Building, Check, NavArrowDown } from "iconoir-react";
2731
2744
  import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2732
2745
  function SuiteWorkspaceSwitcher({
2733
2746
  workspaces = [],
@@ -2814,7 +2827,7 @@ function SuiteWorkspaceSwitcher({
2814
2827
  workspace.description ? /* @__PURE__ */ jsx18("span", { className: "block truncate text-xs text-ph-mutedtext", children: workspace.description }) : null
2815
2828
  ] }),
2816
2829
  workspace.id === currentId ? /* @__PURE__ */ jsx18(
2817
- Check2,
2830
+ Check,
2818
2831
  {
2819
2832
  className: "h-4 w-4 shrink-0 text-ph-brand",
2820
2833
  "aria-hidden": "true"
package/dist/ui.js CHANGED
@@ -399,7 +399,9 @@ var IconPlayCircle = createIconBase({
399
399
  )
400
400
  });
401
401
  var IconGridView = createIconBase({
402
- paths: fill("M3 3v8h8V3H3zm6 6H5V5h4v4zm-6 4v8h8v-8H3zm6 6H5v-4h4v4zm4-16v8h8V3h-8zm6 6h-4V5h4v4zm-6 4v8h8v-8h-8zm6 6h-4v-4h4v4z")
402
+ paths: fill(
403
+ "M3,3v8h8V3H3z M9,9H5V5h4V9z M3,13v8h8v-8H3z M9,19H5v-4h4V19z M13,3v8h8V3H13z M19,9h-4V5h4V9z M13,13v8h8v-8H13z M19,19h-4v-4h4V19z"
404
+ )
403
405
  });
404
406
  var IconListView = createIconBase({
405
407
  paths: fill(
package/dist/ui.mjs CHANGED
@@ -65,7 +65,7 @@ import {
65
65
  ThemeDomSync,
66
66
  ThemeSwitcher,
67
67
  Toggle
68
- } from "./chunk-VOCHIR3W.mjs";
68
+ } from "./chunk-VXISSXTB.mjs";
69
69
  import {
70
70
  ButtonChrome,
71
71
  DropdownButton,
@@ -77,7 +77,7 @@ import {
77
77
  IconByName,
78
78
  Tooltip,
79
79
  TooltipProvider
80
- } from "./chunk-E5ZBQYEX.mjs";
80
+ } from "./chunk-KDR6HI6F.mjs";
81
81
  import "./chunk-FWCSY2DS.mjs";
82
82
  export {
83
83
  Accordion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenide-io/the-old-ui-theme",
3
- "version": "0.6.14",
3
+ "version": "0.7.0",
4
4
  "description": "Props-driven React components and theme tokens inspired by classic interface design \u2014 optimized for Next.js apps and internal tools.",
5
5
  "author": "Xenide",
6
6
  "license": "MIT",
@@ -18,7 +18,7 @@ const CACHE = resolve("scripts/.posthog-icons.tsx");
18
18
  function extractPhBlock(source, name) {
19
19
  const start = source.indexOf(`export function ${name}(`);
20
20
  if (start < 0) return null;
21
- const next = source.indexOf("\nexport function Icon", start + 10);
21
+ const next = source.indexOf("\nexport function ", start + 10);
22
22
  return source.slice(start, next > 0 ? next : start + 4000);
23
23
  }
24
24
 
@@ -34,9 +34,8 @@ function extractOurBlock(source, name) {
34
34
  function normPaths(block) {
35
35
  if (!block) return "";
36
36
  const fills = [...block.matchAll(/fill\(\s*\n?\s*"([^"]+)"/g)].map((m) => m[1]);
37
- const ds = [...block.matchAll(/<path d="([^"]+)"/g)].map((m) => m[1]);
38
- const attrs = [...block.matchAll(/\sd="([^"]+)"/g)].map((m) => m[1]);
39
- return [...fills, ...ds, ...attrs].join("|").replace(/\s/g, "");
37
+ const paths = [...block.matchAll(/\sd="([^"]+)"/g)].map((m) => m[1]);
38
+ return [...fills, ...paths].join("|").replace(/\s/g, "");
40
39
  }
41
40
 
42
41
  async function main() {
@@ -98,7 +98,9 @@ export const IconPlayCircle = createIconBase({
98
98
  });
99
99
 
100
100
  export const IconGridView = createIconBase({
101
- paths: fill("M3 3v8h8V3H3zm6 6H5V5h4v4zm-6 4v8h8v-8H3zm6 6H5v-4h4v4zm4-16v8h8V3h-8zm6 6h-4V5h4v4zm-6 4v8h8v-8h-8zm6 6h-4v-4h4v4z"),
101
+ paths: fill(
102
+ "M3,3v8h8V3H3z M9,9H5V5h4V9z M3,13v8h8v-8H3z M9,19H5v-4h4V19z M13,3v8h8V3H13z M19,9h-4V5h4V9z M13,13v8h8v-8H13z M19,19h-4v-4h4V19z",
103
+ ),
102
104
  });
103
105
 
104
106
  export const IconListView = createIconBase({
@@ -0,0 +1,96 @@
1
+ import type { ComponentPropsWithoutRef, ReactNode } from "react";
2
+ import { cleanup, render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+
6
+ vi.mock("next/image", () => ({
7
+ default: (props: ComponentPropsWithoutRef<"img">) => <img {...props} />,
8
+ }));
9
+
10
+ import { AppSwitcher, type SuiteAppEntry } from "./app-switcher";
11
+
12
+ afterEach(() => {
13
+ cleanup();
14
+ });
15
+
16
+ const APPS: SuiteAppEntry[] = [
17
+ { slug: "tides", name: "Tides", description: "Projects", icon: "/tides.svg" },
18
+ {
19
+ slug: "turtletime",
20
+ name: "TurtleTime",
21
+ description: "Time",
22
+ icon: "/time.svg",
23
+ },
24
+ {
25
+ slug: "kraken",
26
+ name: "Kraken",
27
+ description: "Documents",
28
+ icon: "/kraken.svg",
29
+ },
30
+ {
31
+ slug: "nakama",
32
+ name: "Nakama",
33
+ description: "AI teammates",
34
+ icon: "/nakama.svg",
35
+ },
36
+ ];
37
+
38
+ function TestDropdownMenu({
39
+ trigger,
40
+ children,
41
+ "aria-label": ariaLabel,
42
+ }: {
43
+ trigger: ReactNode;
44
+ children: ReactNode;
45
+ "aria-label": string;
46
+ }) {
47
+ return (
48
+ <div aria-label={ariaLabel}>
49
+ {trigger}
50
+ {children}
51
+ </div>
52
+ );
53
+ }
54
+
55
+ function TestDropdownItem({
56
+ children,
57
+ ...props
58
+ }: ComponentPropsWithoutRef<"button">) {
59
+ return <button {...props}>{children}</button>;
60
+ }
61
+
62
+ describe("AppSwitcher", () => {
63
+ it("keeps the current app out of the simple application list", async () => {
64
+ const user = userEvent.setup();
65
+ const onSelect = vi.fn();
66
+
67
+ render(
68
+ <AppSwitcher
69
+ apps={APPS}
70
+ currentApp="tides"
71
+ onSelect={onSelect}
72
+ mark={<span />}
73
+ title={<span>Tides</span>}
74
+ dropdownMenu={TestDropdownMenu}
75
+ dropdownItem={TestDropdownItem}
76
+ />,
77
+ );
78
+
79
+ const menu = document.querySelector("[data-test='app-switcher-menu']");
80
+ expect(menu).not.toHaveClass("grid-cols-2");
81
+ expect(menu).toHaveTextContent("Switch application");
82
+ expect(
83
+ document.querySelector("[data-test='switch-app-tides']"),
84
+ ).not.toBeInTheDocument();
85
+ const newTabButton = screen.getByRole("button", {
86
+ name: "Open Kraken in a new tab",
87
+ });
88
+ expect(newTabButton).toHaveClass("absolute", "h-8", "w-8");
89
+ expect(
90
+ screen.queryByRole("button", { name: "Open Tides in a new tab" }),
91
+ ).not.toBeInTheDocument();
92
+
93
+ await user.click(newTabButton);
94
+ expect(onSelect).toHaveBeenCalledWith(APPS[2], { newTab: true });
95
+ });
96
+ });
@@ -2,7 +2,10 @@
2
2
 
3
3
  import Image from "next/image";
4
4
  import { useState, type ReactNode } from "react";
5
- import { Check, NavArrowDown as ChevronDown } from "iconoir-react";
5
+ import {
6
+ NavArrowDown as ChevronDown,
7
+ OpenNewWindow,
8
+ } from "iconoir-react";
6
9
 
7
10
  import { cn } from "../lib/cn";
8
11
  import type {
@@ -24,7 +27,6 @@ export function appSwitcherMarkClass() {
24
27
  return "h-8 w-8 shrink-0 rounded-lg shadow-[0_1px_2px_rgba(0,0,0,0.1),0_4px_12px_rgba(0,0,0,0.08)] ring-1 ring-black/[0.06]";
25
28
  }
26
29
 
27
- /** No sticky Radix highlight while waiting for cross-app redirect. */
28
30
  export function appSwitcherMenuItemClass() {
29
31
  return "rounded-lg px-2 py-2 data-[highlighted]:bg-transparent hover:bg-ph-muted/80";
30
32
  }
@@ -101,6 +103,11 @@ export interface SuiteAppEntry {
101
103
  icon: string;
102
104
  }
103
105
 
106
+ export interface SuiteAppSelectOptions {
107
+ /** Open the handoff in a separate browser tab when requested by a caller. */
108
+ newTab?: boolean;
109
+ }
110
+
104
111
  /**
105
112
  * Suite app switcher dropdown. Apps inject their brand mark/title, the
106
113
  * visible app list, the cross-app navigation handler, and the theme
@@ -120,7 +127,7 @@ export function AppSwitcher({
120
127
  }: {
121
128
  apps: SuiteAppEntry[];
122
129
  currentApp: string;
123
- onSelect: (app: SuiteAppEntry) => void;
130
+ onSelect: (app: SuiteAppEntry, options?: SuiteAppSelectOptions) => void;
124
131
  mark: ReactNode;
125
132
  title: ReactNode;
126
133
  collapsed?: boolean;
@@ -160,43 +167,54 @@ export function AppSwitcher({
160
167
  <p className="px-3 py-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-ph-mutedtext">
161
168
  Switch application
162
169
  </p>
163
- {apps.map((app) => (
164
- <DropdownItem
165
- key={app.slug}
166
- id={`switch-app-${app.slug}`}
167
- data-test={`switch-app-${app.slug}`}
168
- onMouseDown={(e) => e.preventDefault()}
169
- onClick={() => {
170
- setOpen(false);
171
- onSelect(app);
172
- }}
173
- className={appSwitcherMenuItemClass()}
174
- >
175
- <span className="flex min-w-0 items-center gap-3">
176
- <Image
177
- src={app.icon}
178
- alt=""
179
- width={28}
180
- height={28}
181
- className="h-7 w-7 shrink-0 rounded-md object-contain"
182
- />
183
- <span className="min-w-0 flex-1">
184
- <span className="block truncate text-sm font-medium text-ph-ink">
185
- {app.name}
186
- </span>
187
- <span className="block truncate text-xs text-ph-mutedtext">
188
- {app.description}
170
+ {apps
171
+ .filter((app) => app.slug !== currentApp)
172
+ .map((app) => (
173
+ <div key={app.slug} className="relative">
174
+ <DropdownItem
175
+ id={`switch-app-${app.slug}`}
176
+ data-test={`switch-app-${app.slug}`}
177
+ onMouseDown={(e) => e.preventDefault()}
178
+ onClick={() => {
179
+ setOpen(false);
180
+ onSelect(app);
181
+ }}
182
+ className={cn(appSwitcherMenuItemClass(), "pr-11")}
183
+ >
184
+ <span className="flex min-w-0 items-center gap-3">
185
+ <Image
186
+ src={app.icon}
187
+ alt=""
188
+ width={28}
189
+ height={28}
190
+ className="h-7 w-7 shrink-0 rounded-md object-contain"
191
+ />
192
+ <span className="min-w-0 flex-1">
193
+ <span className="block truncate text-sm font-medium text-ph-ink">
194
+ {app.name}
195
+ </span>
196
+ <span className="block truncate text-xs text-ph-mutedtext">
197
+ {app.description}
198
+ </span>
199
+ </span>
189
200
  </span>
190
- </span>
191
- {app.slug === currentApp ? (
192
- <Check
193
- className="h-4 w-4 shrink-0 text-ph-brand"
194
- aria-hidden="true"
195
- />
196
- ) : null}
197
- </span>
198
- </DropdownItem>
199
- ))}
201
+ </DropdownItem>
202
+ <button
203
+ type="button"
204
+ id={`switch-app-${app.slug}-new-tab`}
205
+ data-test={`switch-app-${app.slug}-new-tab`}
206
+ aria-label={`Open ${app.name} in a new tab`}
207
+ onMouseDown={(e) => e.preventDefault()}
208
+ onClick={() => {
209
+ setOpen(false);
210
+ onSelect(app, { newTab: true });
211
+ }}
212
+ className="absolute right-1.5 top-1/2 z-[1] flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-md text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ph-brand/35"
213
+ >
214
+ <OpenNewWindow className="h-4 w-4" aria-hidden="true" />
215
+ </button>
216
+ </div>
217
+ ))}
200
218
  </div>
201
219
  </DropdownMenu>
202
220
  );
@@ -12,7 +12,7 @@ afterEach(() => {
12
12
  });
13
13
 
14
14
  describe("SuiteNotificationBell", () => {
15
- it("anchors the unread badge to the button corner, not the bell glyph", async () => {
15
+ it("anchors the unread badge close to the bell glyph", async () => {
16
16
  render(
17
17
  <SuiteNotificationBell
18
18
  fetchNotifications={async () => ({
@@ -38,9 +38,10 @@ describe("SuiteNotificationBell", () => {
38
38
  });
39
39
 
40
40
  expect(badge.textContent).toBe("9+");
41
- expect(badge.className).toContain("translate-x-1/2");
42
- expect(badge.className).not.toContain("-translate-y");
43
- expect(badge.parentElement?.className).toContain("h-11");
44
- expect(badge.parentElement?.className).toContain("w-11");
41
+ expect(badge.className).toContain("-right-2");
42
+ expect(badge.className).toContain("-top-2");
43
+ expect(badge.parentElement?.className).toContain("relative");
44
+ expect(badge.parentElement?.className).toContain("h-5");
45
+ expect(badge.parentElement?.className).toContain("w-5");
45
46
  });
46
47
  });
@@ -181,12 +181,14 @@ export function SuiteNotificationBell({
181
181
  onOpenChange={onOpenChange}
182
182
  trigger={
183
183
  <span className="relative inline-flex h-11 w-11 items-center justify-center overflow-visible rounded-full text-ph-mutedtext transition hover:bg-ph-muted hover:text-ph-ink">
184
- <Bell className="h-5 w-5" strokeWidth={1.75} aria-hidden />
185
- {unread > 0 ? (
186
- <span className="pointer-events-none absolute right-0 top-0 z-[1] flex h-4 min-w-4 translate-x-1/2 items-center justify-center rounded-full bg-ph-brand px-1 text-[9px] font-semibold leading-none text-white ring-2 ring-ph-surface">
187
- {unread > 9 ? "9+" : unread}
188
- </span>
189
- ) : null}
184
+ <span className="relative inline-flex h-5 w-5">
185
+ <Bell className="h-5 w-5" strokeWidth={1.75} aria-hidden />
186
+ {unread > 0 ? (
187
+ <span className="pointer-events-none absolute -right-2 -top-2 z-[1] flex h-4 min-w-4 items-center justify-center rounded-full bg-ph-brand px-1 text-[9px] font-semibold leading-none text-white ring-2 ring-ph-surface">
188
+ {unread > 9 ? "9+" : unread}
189
+ </span>
190
+ ) : null}
191
+ </span>
190
192
  </span>
191
193
  }
192
194
  >
@@ -8,6 +8,7 @@ export {
8
8
  appSwitcherMenuItemClass,
9
9
  appSwitcherTriggerClass,
10
10
  type SuiteAppEntry,
11
+ type SuiteAppSelectOptions,
11
12
  } from "./components/app-switcher";
12
13
  export {
13
14
  SuiteNotificationBell,