From bb96f015d17deea87cf92a9550ee224e2e3dbd9c Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Thu, 16 Jul 2026 01:12:52 +0300 Subject: [PATCH] feat(ui): add Radix overlays themed to VS Code Tooltip, ContextMenu, and DropdownMenu wrap the Radix primitives with flat compound exports (Tooltip takes a content prop) and are styled to match the native menu and hover widgets via --ui-* tokens, including high contrast, forced-colors, and reduced-motion handling. Long menus and tooltips cap to the space Radix reports and scroll like native. Stories snapshot the four theme families through Chromatic and assert focus handling in play functions. --- .storybook/global.css | 10 +- eslint.config.mjs | 19 + packages/ui/README.md | 27 +- packages/ui/package.json | 11 + .../ContextMenu/ContextMenu.stories.tsx | 119 +++ .../components/ContextMenu/ContextMenu.tsx | 100 +++ .../DropdownMenu/DropdownMenu.stories.tsx | 137 ++++ .../components/DropdownMenu/DropdownMenu.tsx | 106 +++ .../ui/src/components/Tooltip/Tooltip.css | 36 + .../components/Tooltip/Tooltip.stories.tsx | 117 +++ .../ui/src/components/Tooltip/Tooltip.tsx | 44 ++ packages/ui/src/components/menu.css | 108 +++ packages/ui/src/cx.ts | 4 + packages/ui/src/foundations.stories.tsx | 8 +- packages/ui/src/index.ts | 23 + packages/ui/src/story-helpers.css | 19 + packages/ui/src/storybook.ts | 11 + packages/ui/src/tokens.css | 31 + pnpm-lock.yaml | 748 ++++++++++++++++++ pnpm-workspace.yaml | 3 + 20 files changed, 1669 insertions(+), 12 deletions(-) create mode 100644 packages/ui/src/components/ContextMenu/ContextMenu.stories.tsx create mode 100644 packages/ui/src/components/ContextMenu/ContextMenu.tsx create mode 100644 packages/ui/src/components/DropdownMenu/DropdownMenu.stories.tsx create mode 100644 packages/ui/src/components/DropdownMenu/DropdownMenu.tsx create mode 100644 packages/ui/src/components/Tooltip/Tooltip.css create mode 100644 packages/ui/src/components/Tooltip/Tooltip.stories.tsx create mode 100644 packages/ui/src/components/Tooltip/Tooltip.tsx create mode 100644 packages/ui/src/components/menu.css create mode 100644 packages/ui/src/cx.ts create mode 100644 packages/ui/src/story-helpers.css create mode 100644 packages/ui/src/storybook.ts diff --git a/.storybook/global.css b/.storybook/global.css index 0ca6719f3b..953125c408 100644 --- a/.storybook/global.css +++ b/.storybook/global.css @@ -2,7 +2,13 @@ html { background: var(--vscode-sideBar-background); } +/* Typography lives on body like real webviews, so portalled content + (menus, tooltips) inherits it too. */ body { + color: var(--vscode-editor-foreground); + font-family: var(--vscode-font-family); + font-weight: var(--vscode-font-weight); + font-size: var(--vscode-font-size); margin: 0; padding: 0; overflow: hidden; @@ -15,10 +21,6 @@ body { #root { overscroll-behavior-x: none; background-color: var(--vscode-sideBar-background); - color: var(--vscode-editor-foreground); - font-family: var(--vscode-font-family); - font-weight: var(--vscode-font-weight); - font-size: var(--vscode-font-size); margin: 0; max-width: 100%; /* arbitrary size choice for the rough VSCode sidebar size */ diff --git a/eslint.config.mjs b/eslint.config.mjs index 8198aae6b5..13081916f9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -208,6 +208,25 @@ export default defineConfig( }, }, + // Keep the UI package independent from other workspace packages. + { + files: ["packages/ui/**/*.{ts,tsx}"], + rules: { + "import-x/no-relative-packages": "error", + "no-restricted-imports": [ + "error", + { + patterns: [ + { + group: ["@repo/*"], + message: "packages/ui must not import other workspace packages.", + }, + ], + }, + ], + }, + }, + // React rules with type-checked analysis (covers hooks, JSX, DOM) { files: ["packages/**/*.{ts,tsx}"], diff --git a/packages/ui/README.md b/packages/ui/README.md index 04b76f5bfb..4e4d4028aa 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -23,14 +23,37 @@ import "@repo/ui/tokens.css"; `codicon.css` re-exports the codicon font and classes. +## Overlays + +`Tooltip`, `ContextMenu`, and `DropdownMenu` wrap the Radix primitives and +are styled to match the native VS Code menu and hover widgets. The menus +expose Radix's compound parts as flat named exports +(`DropdownMenuTrigger`, `DropdownMenuItem`, …); `Tooltip` is a single +component taking a `content` prop, with a 500ms show delay matching VS +Code's `workbench.hover.delay` default. Each `Tooltip` mounts its own +Radix provider, so the cross-trigger skip-delay window is not shared +between tooltips; if that matters, expose a shared provider. Checkbox/ +radio items, labels, and keybinding hints are not wrapped yet. + +Overlay content is portalled to `body` and inherits webview typography +from there; surface colors come from the `--ui-menu-*` and +`--ui-tooltip-*` tokens. High contrast follows the VS Code contrast +variables, and the styles handle `forced-colors` and +`prefers-reduced-motion`. + ## Rules The package is shaped for a future standalone NPM split. Keep it that way: - No `workspace:*` runtime dependencies (no `@repo/shared`, - `@repo/webview-shared`). -- `react` stays a peer dependency. + `@repo/webview-shared`); ESLint rejects `@repo/*` imports and relative + cross-package imports in this package. +- `react` stays a peer dependency; the Radix overlay primitives are the + only other runtime dependencies. - New entry points go in the `exports` map; consumers never deep-import `src/`. +- Shared internals are reached through `package.json` subpath imports + (`#cx`, `#storybook`). These resolve only inside this package and ship + with it, so they survive a standalone NPM split. - New components define their VS Code mappings in `tokens.css`, not inline `--vscode-*` references. diff --git a/packages/ui/package.json b/packages/ui/package.json index 9e5fb4fed4..6a046c1913 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -4,6 +4,10 @@ "description": "Shared component library for VS Code webviews", "private": true, "type": "module", + "sideEffects": [ + "**/*.css", + "./storybook.preview.ts" + ], "exports": { ".": { "types": "./src/index.ts", @@ -12,10 +16,17 @@ "./codicon.css": "./src/codicon.css", "./tokens.css": "./src/tokens.css" }, + "imports": { + "#cx": "./src/cx.ts", + "#storybook": "./src/storybook.ts" + }, "scripts": { "typecheck": "tsc --noEmit" }, "dependencies": { + "@radix-ui/react-context-menu": "catalog:", + "@radix-ui/react-dropdown-menu": "catalog:", + "@radix-ui/react-tooltip": "catalog:", "@vscode/codicons": "catalog:" }, "peerDependencies": { diff --git a/packages/ui/src/components/ContextMenu/ContextMenu.stories.tsx b/packages/ui/src/components/ContextMenu/ContextMenu.stories.tsx new file mode 100644 index 0000000000..d22a6d0ee8 --- /dev/null +++ b/packages/ui/src/components/ContextMenu/ContextMenu.stories.tsx @@ -0,0 +1,119 @@ +import { expect, screen, userEvent, waitFor, within } from "storybook/test"; + +import { FOUR_THEME_MODES } from "#storybook"; + +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuTrigger, +} from "./ContextMenu"; + +import type { Meta, StoryObj } from "@storybook/react-vite"; + +const TARGET_STYLE: React.CSSProperties = { + display: "grid", + placeItems: "center", + width: 240, + height: 120, + border: "1px dashed var(--ui-description-foreground)", +}; + +const MenuExample = (): React.JSX.Element => ( + + +
Right-click here
+
+ + + + + + + + + + More actions + + Open logs + Edit settings + + + +
+); + +const meta: Meta = { + title: "UI/ContextMenu", + component: MenuExample, +}; + +export default meta; +type Story = StoryObj; + +/* Right-click at the target's center; without coords the contextmenu + event fires at (0,0) and the menu opens detached from the target. */ +async function rightClickCenter(target: Element): Promise { + const rect = target.getBoundingClientRect(); + await userEvent.pointer({ + keys: "[MouseRight]", + target, + coords: { + clientX: rect.left + rect.width / 2, + clientY: rect.top + rect.height / 2, + }, + }); +} + +export const Closed: Story = {}; + +/* Chromatic crops to in-flow content, so snapshot stories reserve space + for the portalled menu. */ +const OVERLAY_SPACE: React.CSSProperties = { width: 620, height: 400 }; + +/* Opens the menu and its submenu so Chromatic snapshots the open state. */ +export const Open: Story = { + decorators: [ + (Story) => ( +
+ +
+ ), + ], + parameters: { chromatic: { modes: FOUR_THEME_MODES } }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await rightClickCenter(canvas.getByText("Right-click here")); + const menu = await screen.findByRole("menu"); + // Radix moves focus into the menu on open + await waitFor(() => + expect(menu.contains(document.activeElement)).toBe(true), + ); + // Keyboard avoids the submenu hover-open delay + await userEvent.keyboard("{End}{ArrowRight}"); + await screen.findByRole("menuitem", { name: "Open logs" }); + }, +}; + +export const EscapeClosesMenu: Story = { + parameters: { chromatic: { disableSnapshot: true } }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await rightClickCenter(canvas.getByText("Right-click here")); + await screen.findByRole("menu"); + await userEvent.keyboard("{Escape}"); + await waitFor(() => + expect(screen.queryByRole("menu")).not.toBeInTheDocument(), + ); + }, +}; diff --git a/packages/ui/src/components/ContextMenu/ContextMenu.tsx b/packages/ui/src/components/ContextMenu/ContextMenu.tsx new file mode 100644 index 0000000000..7885d68e36 --- /dev/null +++ b/packages/ui/src/components/ContextMenu/ContextMenu.tsx @@ -0,0 +1,100 @@ +import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"; + +import { cx } from "#cx"; + +import "../menu.css"; + +import type { ComponentPropsWithRef } from "react"; + +export const ContextMenu = ContextMenuPrimitive.Root; +export const ContextMenuTrigger = ContextMenuPrimitive.Trigger; +export const ContextMenuGroup = ContextMenuPrimitive.Group; +export const ContextMenuSub = ContextMenuPrimitive.Sub; + +export function ContextMenuContent({ + className, + // Native menus wrap focus when arrowing past the last item + loop = true, + ...props +}: ComponentPropsWithRef< + typeof ContextMenuPrimitive.Content +>): React.JSX.Element { + return ( + + + + ); +} + +export function ContextMenuSubContent({ + className, + sideOffset = 2, + loop = true, + ...props +}: ComponentPropsWithRef< + typeof ContextMenuPrimitive.SubContent +>): React.JSX.Element { + return ( + + + + ); +} + +export function ContextMenuItem({ + className, + ...props +}: ComponentPropsWithRef): React.JSX.Element { + return ( + + ); +} + +export function ContextMenuSubTrigger({ + className, + children, + ...props +}: ComponentPropsWithRef< + typeof ContextMenuPrimitive.SubTrigger +>): React.JSX.Element { + return ( + + {children} + + ); +} + +export function ContextMenuSeparator({ + className, + ...props +}: ComponentPropsWithRef< + typeof ContextMenuPrimitive.Separator +>): React.JSX.Element { + return ( + + ); +} diff --git a/packages/ui/src/components/DropdownMenu/DropdownMenu.stories.tsx b/packages/ui/src/components/DropdownMenu/DropdownMenu.stories.tsx new file mode 100644 index 0000000000..b5de2f917e --- /dev/null +++ b/packages/ui/src/components/DropdownMenu/DropdownMenu.stories.tsx @@ -0,0 +1,137 @@ +import { expect, screen, userEvent, waitFor, within } from "storybook/test"; + +import { FOUR_THEME_MODES, STORY_TRIGGER_CLASS } from "#storybook"; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from "./DropdownMenu"; + +import type { Meta, StoryObj } from "@storybook/react-vite"; + +const MenuExample = (): React.JSX.Element => ( + + + + + + + + + + + + + + More actions + + Open logs + Edit settings + + + + +); + +const meta: Meta = { + title: "UI/DropdownMenu", + component: MenuExample, +}; + +export default meta; +type Story = StoryObj; + +export const Closed: Story = {}; + +/* Chromatic crops to in-flow content, so snapshot stories reserve space + for the portalled menu. */ +const OVERLAY_SPACE: React.CSSProperties = { width: 540, height: 320 }; + +/* Opens the menu and its submenu so Chromatic snapshots the open state. */ +export const Open: Story = { + decorators: [ + (Story) => ( +
+ +
+ ), + ], + parameters: { chromatic: { modes: FOUR_THEME_MODES } }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click( + canvas.getByRole("button", { name: "Workspace actions" }), + ); + const menu = await screen.findByRole("menu"); + // Radix moves focus into the menu on open + await waitFor(() => + expect(menu.contains(document.activeElement)).toBe(true), + ); + // Keyboard avoids the submenu hover-open delay + await userEvent.keyboard("{End}{ArrowRight}"); + await screen.findByRole("menuitem", { name: "Open logs" }); + }, +}; + +/* Long menus scroll. The default cap is the viewport space Radix + reports; the story lowers it via style. */ +export const ManyItems: Story = { + decorators: [ + (Story) => ( +
+ +
+ ), + ], + render: () => ( + + + + + + {Array.from({ length: 40 }, (_, i) => ( + Workspace {i + 1} + ))} + + + ), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click( + canvas.getByRole("button", { name: "Workspace actions" }), + ); + const menu = await screen.findByRole("menu"); + await waitFor(() => + expect(menu.scrollHeight).toBeGreaterThan(menu.clientHeight), + ); + }, +}; + +export const FocusReturnsToTrigger: Story = { + parameters: { chromatic: { disableSnapshot: true } }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByRole("button", { name: "Workspace actions" }); + await userEvent.click(trigger); + await screen.findByRole("menu"); + await userEvent.keyboard("{Escape}"); + await waitFor(() => expect(trigger).toHaveFocus()); + await expect(screen.queryByRole("menu")).not.toBeInTheDocument(); + }, +}; diff --git a/packages/ui/src/components/DropdownMenu/DropdownMenu.tsx b/packages/ui/src/components/DropdownMenu/DropdownMenu.tsx new file mode 100644 index 0000000000..4ebdfd449f --- /dev/null +++ b/packages/ui/src/components/DropdownMenu/DropdownMenu.tsx @@ -0,0 +1,106 @@ +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; + +import { cx } from "#cx"; + +import "../menu.css"; + +import type { ComponentPropsWithRef } from "react"; + +export const DropdownMenu = DropdownMenuPrimitive.Root; +export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; +export const DropdownMenuGroup = DropdownMenuPrimitive.Group; +export const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +export function DropdownMenuContent({ + className, + sideOffset = 2, + align = "start", + // Native menus wrap focus when arrowing past the last item + loop = true, + ...props +}: ComponentPropsWithRef< + typeof DropdownMenuPrimitive.Content +>): React.JSX.Element { + return ( + + + + ); +} + +export function DropdownMenuSubContent({ + className, + sideOffset = 2, + loop = true, + ...props +}: ComponentPropsWithRef< + typeof DropdownMenuPrimitive.SubContent +>): React.JSX.Element { + return ( + + + + ); +} + +export function DropdownMenuItem({ + className, + ...props +}: ComponentPropsWithRef< + typeof DropdownMenuPrimitive.Item +>): React.JSX.Element { + return ( + + ); +} + +export function DropdownMenuSubTrigger({ + className, + children, + ...props +}: ComponentPropsWithRef< + typeof DropdownMenuPrimitive.SubTrigger +>): React.JSX.Element { + return ( + + {children} + + ); +} + +export function DropdownMenuSeparator({ + className, + ...props +}: ComponentPropsWithRef< + typeof DropdownMenuPrimitive.Separator +>): React.JSX.Element { + return ( + + ); +} diff --git a/packages/ui/src/components/Tooltip/Tooltip.css b/packages/ui/src/components/Tooltip/Tooltip.css new file mode 100644 index 0000000000..c360f20423 --- /dev/null +++ b/packages/ui/src/components/Tooltip/Tooltip.css @@ -0,0 +1,36 @@ +/* Matches the native workbench hover widget; typography inherits from + the webview body. */ +.ui-tooltip { + box-sizing: border-box; + max-width: min(700px, calc(100vw - 16px)); + /* Cap to the space Radix reports; oversized content scrolls */ + max-height: var(--radix-tooltip-content-available-height); + overflow-y: auto; + padding: 4px 8px; + color: var(--ui-tooltip-foreground); + background: var(--ui-tooltip-background); + border: 1px solid var(--ui-tooltip-border); + border-radius: 5px; + box-shadow: var(--ui-widget-shadow); + overflow-wrap: break-word; + z-index: 40; + animation: ui-tooltip-fade-in 100ms linear; +} + +@keyframes ui-tooltip-fade-in { + from { + opacity: 0; + } +} + +@media (prefers-reduced-motion: reduce) { + .ui-tooltip { + animation: none; + } +} + +@media (forced-colors: active) { + .ui-tooltip { + border-color: CanvasText; + } +} diff --git a/packages/ui/src/components/Tooltip/Tooltip.stories.tsx b/packages/ui/src/components/Tooltip/Tooltip.stories.tsx new file mode 100644 index 0000000000..4606da88d2 --- /dev/null +++ b/packages/ui/src/components/Tooltip/Tooltip.stories.tsx @@ -0,0 +1,117 @@ +import { expect, screen, userEvent, waitFor } from "storybook/test"; + +import { FOUR_THEME_MODES, STORY_TRIGGER_CLASS } from "#storybook"; + +import { Tooltip } from "./Tooltip"; + +import type { Meta, StoryObj } from "@storybook/react-vite"; + +const LONG_TEXT = + "This workspace has been running for 14 days without a rebuild. " + + "Stopping it frees compute resources, and any unsaved changes in " + + "the home volume are preserved until the next start."; + +const meta: Meta = { + title: "UI/Tooltip", + component: Tooltip, + args: { + content: "Stops the workspace", + // Instant in stories; the component default is 500ms + delayDuration: 0, + children: ( + + ), + }, +}; + +export default meta; +type Story = StoryObj; + +/* findByRole("tooltip") matches Radix's visually hidden a11y copy, so + open assertions target the visible .ui-tooltip bubble instead. */ +async function openTooltipWithKeyboard(): Promise { + await userEvent.tab(); + await screen.findByRole("tooltip"); + const tooltip = document.querySelector(".ui-tooltip"); + if (!tooltip) { + throw new Error("visible tooltip content not found"); + } + await waitFor(() => expect(tooltip).toBeVisible()); + return tooltip; +} + +/* Chromatic crops to in-flow content; the tooltip opens upward, so the + wrapper reserves space above a bottom-anchored trigger. */ +function tooltipSpace( + width: number, + height: number | string, +): (Story: React.ComponentType) => React.JSX.Element { + const space: React.CSSProperties = { + width, + height, + // Keeps the trigger's focus outline inside the cropped bounds + paddingBottom: 8, + display: "flex", + alignItems: "flex-end", + justifyContent: "center", + }; + return function TooltipSpace(Story) { + return ( +
+ +
+ ); + }; +} + +export const Closed: Story = {}; + +/* Keyboard focus opens instantly and proves keyboard users get the tooltip. */ +export const Open: Story = { + decorators: [tooltipSpace(280, 100)], + parameters: { chromatic: { modes: FOUR_THEME_MODES } }, + play: async () => { + await openTooltipWithKeyboard(); + }, +}; + +/* Long content wraps inside the native hover widget's max-width. */ +export const LongContent: Story = { + decorators: [tooltipSpace(720, 160)], + args: { + content: LONG_TEXT, + }, + play: async () => { + await openTooltipWithKeyboard(); + }, +}; + +/* Overflowing content scrolls inside the tooltip. The default cap is the + viewport space Radix reports; the story lowers it via style. */ +export const OverflowScrolls: Story = { + decorators: [tooltipSpace(720, 240)], + args: { + content: Array.from({ length: 8 }, () => LONG_TEXT).join(" "), + style: { maxHeight: 160 }, + }, + play: async () => { + const tooltip = await openTooltipWithKeyboard(); + await waitFor(() => + expect(tooltip.scrollHeight).toBeGreaterThan(tooltip.clientHeight), + ); + }, +}; + +export const ClosesOnEscape: Story = { + parameters: { chromatic: { disableSnapshot: true } }, + play: async () => { + await userEvent.tab(); + await screen.findByRole("tooltip"); + await userEvent.keyboard("{Escape}"); + await waitFor(() => + expect(screen.queryByRole("tooltip")).not.toBeInTheDocument(), + ); + }, +}; diff --git a/packages/ui/src/components/Tooltip/Tooltip.tsx b/packages/ui/src/components/Tooltip/Tooltip.tsx new file mode 100644 index 0000000000..180b8c9e5d --- /dev/null +++ b/packages/ui/src/components/Tooltip/Tooltip.tsx @@ -0,0 +1,44 @@ +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; + +import { cx } from "#cx"; + +import "./Tooltip.css"; + +import type { ComponentPropsWithRef, ReactNode } from "react"; + +export interface TooltipProps extends Omit< + ComponentPropsWithRef, + "content" +> { + content: ReactNode; + /** The trigger element; must accept a forwarded ref (asChild). */ + children: ReactNode; + /** Show delay in ms. Defaults to VS Code's workbench.hover.delay. */ + delayDuration?: number; +} + +export function Tooltip({ + content, + children, + className, + sideOffset = 4, + delayDuration = 500, + ...props +}: TooltipProps): React.JSX.Element { + return ( + + + {children} + + + {content} + + + + + ); +} diff --git a/packages/ui/src/components/menu.css b/packages/ui/src/components/menu.css new file mode 100644 index 0000000000..d5b2b3c8ae --- /dev/null +++ b/packages/ui/src/components/menu.css @@ -0,0 +1,108 @@ +/* Shared ContextMenu/DropdownMenu surface matching the native menu + widget; typography inherits from the webview body. */ +.ui-menu { + --ui-menu-gutter: 2em; + --ui-menu-highlight-foreground: var(--ui-menu-selection-foreground); + --ui-menu-highlight-background: var(--ui-menu-selection-background); + --ui-menu-disabled-foreground: var(--ui-disabled-foreground); + + box-sizing: border-box; + min-width: 160px; + /* Cap to the space Radix reports (named per primitive) so long menus scroll */ + max-height: var( + --radix-dropdown-menu-content-available-height, + var(--radix-context-menu-content-available-height) + ); + overflow-y: auto; + padding: 4px; + color: var(--ui-menu-foreground); + background: var(--ui-menu-background); + border: 1px solid var(--ui-menu-border); + border-radius: var(--ui-menu-radius); + box-shadow: var(--ui-widget-shadow); + outline: 0; + z-index: 40; + animation: ui-menu-fade-in 83ms linear; +} + +.ui-menu__item { + position: relative; + display: flex; + align-items: center; + height: 24px; + /* Native label inset; the gutters double as the check/icon columns. */ + padding: 0 var(--ui-menu-gutter); + line-height: 1; + white-space: nowrap; + border-radius: var(--ui-menu-item-radius); + cursor: default; + user-select: none; + outline: 1px solid transparent; + outline-offset: -1px; +} + +/* Selection border only resolves in high contrast; an open submenu + keeps its parent highlighted, like native. */ +.ui-menu__item[data-highlighted], +.ui-menu__item[data-state="open"] { + forced-color-adjust: none; + color: var(--ui-menu-highlight-foreground); + background: var(--ui-menu-highlight-background); + outline-color: var(--ui-menu-selection-border); +} + +.ui-menu__item[data-disabled] { + color: var(--ui-menu-disabled-foreground); +} + +/* Leading icons sit in the left gutter so labels stay aligned */ +.ui-menu__item > .codicon:not(.ui-menu__submenu-indicator) { + position: absolute; + left: 0; + width: var(--ui-menu-gutter); + text-align: center; +} + +/* Chevron in the right gutter, like the native indicator */ +.ui-menu__submenu-indicator { + position: absolute; + right: 0; + width: var(--ui-menu-gutter); + text-align: center; +} + +.ui-menu__item[data-disabled] .ui-menu__submenu-indicator { + opacity: 0.4; +} + +.ui-menu__separator { + height: 1px; + margin: 5px 0; + background: var(--ui-menu-separator); +} + +@keyframes ui-menu-fade-in { + from { + opacity: 0; + } +} + +@media (prefers-reduced-motion: reduce) { + .ui-menu { + animation: none; + } +} + +@media (forced-colors: active) { + .ui-menu { + --ui-menu-highlight-foreground: HighlightText; + --ui-menu-highlight-background: Highlight; + --ui-menu-disabled-foreground: GrayText; + + border-color: CanvasText; + } + + .ui-menu__separator { + background: CanvasText; + } +} diff --git a/packages/ui/src/cx.ts b/packages/ui/src/cx.ts new file mode 100644 index 0000000000..e8e639d30f --- /dev/null +++ b/packages/ui/src/cx.ts @@ -0,0 +1,4 @@ +/** Joins the truthy class names, dropping conditional falsy entries. */ +export function cx(...classes: Array): string { + return classes.filter(Boolean).join(" "); +} diff --git a/packages/ui/src/foundations.stories.tsx b/packages/ui/src/foundations.stories.tsx index cbd5132fb1..830edc1f49 100644 --- a/packages/ui/src/foundations.stories.tsx +++ b/packages/ui/src/foundations.stories.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; +import { FOUR_THEME_MODES } from "./storybook"; import { useVscodeTheme } from "./useVscodeTheme"; import type { Meta, StoryObj } from "@storybook/react-vite"; @@ -132,12 +133,7 @@ const meta: Meta = { // Snapshot every theme; tokens are the single theming surface, so this // is where theme regressions show up. chromatic: { - modes: { - light: { theme: "light" }, - dark: { theme: "dark" }, - "high-contrast": { theme: "high-contrast" }, - "high-contrast-light": { theme: "high-contrast-light" }, - }, + modes: FOUR_THEME_MODES, }, }, }; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index fb38844dba..c1db9bf1be 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1 +1,24 @@ +export { + ContextMenu, + ContextMenuContent, + ContextMenuGroup, + ContextMenuItem, + ContextMenuSeparator, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuTrigger, +} from "./components/ContextMenu/ContextMenu"; +export { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from "./components/DropdownMenu/DropdownMenu"; +export { Tooltip, type TooltipProps } from "./components/Tooltip/Tooltip"; export { useVscodeTheme, type VscodeThemeKind } from "./useVscodeTheme"; diff --git a/packages/ui/src/story-helpers.css b/packages/ui/src/story-helpers.css new file mode 100644 index 0000000000..a9f6edeb48 --- /dev/null +++ b/packages/ui/src/story-helpers.css @@ -0,0 +1,19 @@ +/* Story stand-in: a VS Code secondary button like consuming webviews render. */ +.story-trigger { + padding: 0.4em 1em; + border: 1px solid var(--vscode-button-border, transparent); + border-radius: 2px; + background: var(--vscode-button-secondaryBackground); + color: var(--vscode-button-secondaryForeground); + font: inherit; + cursor: pointer; +} + +.story-trigger:hover { + background: var(--vscode-button-secondaryHoverBackground); +} + +.story-trigger:focus-visible { + outline: 1px solid var(--vscode-focusBorder); + outline-offset: 2px; +} diff --git a/packages/ui/src/storybook.ts b/packages/ui/src/storybook.ts new file mode 100644 index 0000000000..2b2a386f03 --- /dev/null +++ b/packages/ui/src/storybook.ts @@ -0,0 +1,11 @@ +import "./story-helpers.css"; + +export const FOUR_THEME_MODES = { + light: { theme: "light" }, + dark: { theme: "dark" }, + "high-contrast": { theme: "high-contrast" }, + "high-contrast-light": { theme: "high-contrast-light" }, +} as const; + +/* Story stand-in for a webview-styled button. */ +export const STORY_TRIGGER_CLASS = "story-trigger"; diff --git a/packages/ui/src/tokens.css b/packages/ui/src/tokens.css index e0cae1cbb8..a1b333f75f 100644 --- a/packages/ui/src/tokens.css +++ b/packages/ui/src/tokens.css @@ -45,6 +45,37 @@ --ui-error-foreground: var(--vscode-errorForeground); --ui-warning-foreground: var(--vscode-editorWarning-foreground); + /* Disabled text */ + --ui-disabled-foreground: var(--vscode-disabledForeground); + + /* widget.shadow is null in high contrast, hiding the shadow like native */ + --ui-widget-shadow: 0 0 12px var(--vscode-widget-shadow, transparent); + + /* Menus; radius fallbacks predate the corner radius tokens */ + --ui-menu-background: var(--vscode-menu-background); + --ui-menu-foreground: var(--vscode-menu-foreground); + --ui-menu-border: var( + --vscode-menu-border, + var(--vscode-editorWidget-border, transparent) + ); + --ui-menu-selection-background: var( + --vscode-menu-selectionBackground, + transparent + ); + --ui-menu-selection-foreground: var( + --vscode-menu-selectionForeground, + var(--ui-menu-foreground) + ); + --ui-menu-selection-border: var(--vscode-menu-selectionBorder, transparent); + --ui-menu-separator: var(--vscode-menu-separatorBackground); + --ui-menu-radius: var(--vscode-cornerRadius-large, 5px); + --ui-menu-item-radius: var(--vscode-cornerRadius-medium, 4px); + + /* Tooltip (native workbench hover widget) */ + --ui-tooltip-background: var(--vscode-editorHoverWidget-background); + --ui-tooltip-foreground: var(--vscode-editorHoverWidget-foreground); + --ui-tooltip-border: var(--vscode-editorHoverWidget-border); + /* Only defined by high contrast themes; transparent elsewhere */ --ui-contrast-border: var(--vscode-contrastBorder, transparent); --ui-contrast-active-border: var(--vscode-contrastActiveBorder, transparent); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9666dc1ec2..76834986ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,15 @@ settings: catalogs: default: + '@radix-ui/react-context-menu': + specifier: ^2.3.3 + version: 2.3.3 + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.20 + version: 2.1.20 + '@radix-ui/react-tooltip': + specifier: ^1.2.12 + version: 1.2.12 '@rolldown/plugin-babel': specifier: ^0.2.3 version: 0.2.3 @@ -438,6 +447,15 @@ importers: packages/ui: dependencies: + '@radix-ui/react-context-menu': + specifier: 'catalog:' + version: 2.3.3(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-dropdown-menu': + specifier: 'catalog:' + version: 2.1.20(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-tooltip': + specifier: 'catalog:' + version: 1.2.12(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) '@vscode/codicons': specifier: 'catalog:' version: 0.0.45 @@ -1015,6 +1033,21 @@ packages: '@noble/hashes': optional: true + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/react-dom@2.1.9': + resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -1511,6 +1544,311 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@radix-ui/primitive@1.1.5': + resolution: {integrity: sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==} + + '@radix-ui/react-arrow@1.1.11': + resolution: {integrity: sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.12': + resolution: {integrity: sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.3': + resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context-menu@2.3.3': + resolution: {integrity: sha512-PS+gKE0z2prJ74Y0sM+brAGK4mYOHIR7TlcV5EJgUQ6E0xMvyswkK2X4yRqyganrzsRL+WCSKAPu0NQITICRWg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-context@1.2.0': + resolution: {integrity: sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-direction@1.1.2': + resolution: {integrity: sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.15': + resolution: {integrity: sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.20': + resolution: {integrity: sha512-slfm+rRaZRuQBvHq60lXvSVUPhid0IPtjSZzIuUlWZMUs01iYZNlGS3mJgRD3ChLQVBAYlKiL/tFyWGX+dz8Xw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.4': + resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.12': + resolution: {integrity: sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.2': + resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-menu@2.1.20': + resolution: {integrity: sha512-VsUrXxFe9d2ScbZF0fR/oPR1+qjyeLs5p0jzG8h90puMoA9bq4SirYlXbE+USRg9Q2qTeJSFNqjw2nts8jJe4w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.3.3': + resolution: {integrity: sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.13': + resolution: {integrity: sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.7': + resolution: {integrity: sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.7': + resolution: {integrity: sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.15': + resolution: {integrity: sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.3.0': + resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tooltip@1.2.12': + resolution: {integrity: sha512-U3HoftgWnmla78vzQbLvKKb7bUYJxoiiqYFzp1wu/TBMyDqMZSuCl3aRICsD6EfVEwcJD2mumGDGUXLFVqQHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.2': + resolution: {integrity: sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.3': + resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.3': + resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.1': + resolution: {integrity: sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.2': + resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.2': + resolution: {integrity: sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.2': + resolution: {integrity: sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.7': + resolution: {integrity: sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.2': + resolution: {integrity: sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==} + '@rolldown/binding-android-arm64@1.1.4': resolution: {integrity: sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2340,6 +2678,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -2748,6 +3090,9 @@ packages: resolution: {integrity: sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} @@ -3208,6 +3553,10 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -4298,6 +4647,36 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react@19.2.7: resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} engines: {node: '>=0.10.0'} @@ -4863,6 +5242,26 @@ packages: url-join@4.0.1: resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -5641,6 +6040,23 @@ snapshots: '@exodus/bytes@1.15.1': {} + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 + + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 + + '@floating-ui/react-dom@2.1.9(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@floating-ui/dom': 1.8.0 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + '@floating-ui/utils@0.2.12': {} + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -6106,6 +6522,288 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@radix-ui/primitive@1.1.5': {} + + '@radix-ui/react-arrow@1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-collection@1.1.12(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-context-menu@2.3.3(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-menu': 2.1.20(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-context@1.2.0(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-direction@1.1.2(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-dismissable-layer@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-dropdown-menu@2.1.20(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-menu': 2.1.20(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-focus-guards@1.1.4(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-focus-scope@1.1.12(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-id@1.1.2(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-menu@2.1.20(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.12(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-popper': 1.3.3(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-roving-focus': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + aria-hidden: 1.2.6 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-popper@1.3.3(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@floating-ui/react-dom': 2.1.9(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-arrow': 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-rect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/rect': 1.1.2 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-portal@1.1.13(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-presence@1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-primitive@2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-roving-focus@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-slot@1.3.0(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-tooltip@1.2.12(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-popper': 1.3.3(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-visually-hidden': 1.2.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-use-callback-ref@1.1.2(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-controllable-state@1.2.3(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-effect-event@0.0.3(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-is-hydrated@0.1.1(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.17)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-rect@1.1.2(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@radix-ui/rect': 1.1.2 + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-size@1.1.2(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-visually-hidden@1.2.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7)': + dependencies: + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3)(@types/react@19.2.17)(react-dom@19.2.7)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/rect@1.1.2': {} + '@rolldown/binding-android-arm64@1.1.4': optional: true @@ -6977,6 +7675,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -7375,6 +8077,8 @@ snapshots: detect-newline@4.0.1: {} + detect-node-es@1.1.0: {} + detect-node@2.1.0: optional: true @@ -7919,6 +8623,8 @@ snapshots: hasown: 2.0.4 math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -9292,6 +9998,33 @@ snapshots: react-is@17.0.2: {} + react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.7): + dependencies: + react: 19.2.7 + react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + react-remove-scroll@2.7.2(@types/react@19.2.17)(react@19.2.7): + dependencies: + react: 19.2.7 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.17)(react@19.2.7) + react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.17)(react@19.2.7) + use-sidecar: 1.1.3(@types/react@19.2.17)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + + react-style-singleton@2.2.3(@types/react@19.2.17)(react@19.2.7): + dependencies: + get-nonce: 1.0.1 + react: 19.2.7 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + react@19.2.7: {} read-pkg@9.0.1: @@ -9912,6 +10645,21 @@ snapshots: url-join@4.0.1: {} + use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.7): + dependencies: + react: 19.2.7 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + use-sidecar@1.1.3(@types/react@19.2.17)(react@19.2.7): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.7 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + use-sync-external-store@1.6.0(react@19.2.7): dependencies: react: 19.2.7 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fd1f735b9d..db80f00c40 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,6 +2,9 @@ packages: - packages/* catalog: + "@radix-ui/react-context-menu": ^2.3.3 + "@radix-ui/react-dropdown-menu": ^2.1.20 + "@radix-ui/react-tooltip": ^1.2.12 "@rolldown/plugin-babel": ^0.2.3 "@storybook/addon-a11y": ^10.4.6 "@storybook/addon-docs": ^10.4.6