diff --git a/.github/workflows/gui-checks.yml b/.github/workflows/gui-checks.yml index 1a114b3f4f7b..accd40d7648d 100644 --- a/.github/workflows/gui-checks.yml +++ b/.github/workflows/gui-checks.yml @@ -15,6 +15,11 @@ permissions: statuses: write # Write access to commit statuses checks: write +env: + # Workaround for https://github.com/nodejs/corepack/issues/612 + # See: https://github.com/nodejs/corepack/blob/main/README.md#environment-variables + COREPACK_DEFAULT_TO_LATEST: 0 + jobs: lint: name: 👮 Lint GUI diff --git a/.github/workflows/ide-packaging-optional.yml b/.github/workflows/ide-packaging-optional.yml index 485bc4c0995d..8a8f1a1834b9 100644 --- a/.github/workflows/ide-packaging-optional.yml +++ b/.github/workflows/ide-packaging-optional.yml @@ -51,6 +51,15 @@ jobs: - run: ./run backend get env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Archive project-manager + run: tar -cvf project-manager.tar -C dist/backend . + - name: Upload project-manager + uses: actions/upload-artifact@v4 + with: + name: project-manager-macos + path: project-manager.tar + - name: Cleanup + run: rm project-manager.tar - if: "(always()) && (contains(github.event.pull_request.labels.*.name, 'CI: Clean build required') || inputs.clean_build_required)" name: Clean after run: ./run git-clean @@ -145,7 +154,15 @@ jobs: run: ./run git-clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: ./run ide build --backend-source current-ci-run --gui-upload-artifact false + - name: Download project-manager + uses: actions/download-artifact@v4 + with: + name: project-manager-macos + path: dist/backend + - run: |- + tar -xvf dist/backend/project-manager.tar -C dist/backend + rm dist/backend/project-manager.tar + - run: ./run ide build --backend-source local --gui-upload-artifact false env: APPLEID: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} APPLEIDPASS: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} @@ -181,6 +198,11 @@ jobs: compression-level: 0 name: test-traces-macos-amd64 path: app/ide-desktop/client/test-traces + - name: Upload ide + uses: actions/upload-artifact@v4 + with: + name: ide-macos + path: dist/ide/enso-*.dmg - run: rm $HOME/.enso/credentials env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ide-packaging.yml b/.github/workflows/ide-packaging.yml index 2f1b00dae956..4088c148e93a 100644 --- a/.github/workflows/ide-packaging.yml +++ b/.github/workflows/ide-packaging.yml @@ -51,6 +51,25 @@ jobs: - run: ./run backend get env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload Edition File + uses: actions/upload-artifact@v4 + with: + name: Edition File + path: distribution/editions/*.yaml + - name: Upload fbs-schema + uses: actions/upload-artifact@v4 + with: + name: fbs-schema + path: engine/language-server/src/main/schema/ + - name: Archive project-manager + run: tar -cvf project-manager.tar -C dist/backend . + - name: Upload project-manager + uses: actions/upload-artifact@v4 + with: + name: project-manager-linux + path: project-manager.tar + - name: Cleanup + run: rm project-manager.tar - if: "(always()) && (contains(github.event.pull_request.labels.*.name, 'CI: Clean build required') || inputs.clean_build_required)" name: Clean after run: ./run git-clean @@ -92,6 +111,15 @@ jobs: - run: ./run backend get env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Archive project-manager + run: tar -cvf project-manager.tar -C dist/backend . + - name: Upload project-manager + uses: actions/upload-artifact@v4 + with: + name: project-manager-windows + path: project-manager.tar + - name: Cleanup + run: rm project-manager.tar - if: "(always()) && (contains(github.event.pull_request.labels.*.name, 'CI: Clean build required') || inputs.clean_build_required)" name: Clean after run: ./run git-clean @@ -146,6 +174,11 @@ jobs: ENSO_IDE_SENTRY_DSN: ${{ vars.ENSO_CLOUD_SENTRY_DSN }} ENSO_IDE_STRIPE_KEY: ${{ vars.ENSO_CLOUD_STRIPE_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload gui + uses: actions/upload-artifact@v4 + with: + name: gui + path: dist/gui/ - if: "(always()) && (contains(github.event.pull_request.labels.*.name, 'CI: Clean build required') || inputs.clean_build_required)" name: Clean after run: ./run git-clean @@ -240,7 +273,15 @@ jobs: run: ./run git-clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: ./run ide build --backend-source current-ci-run --gui-upload-artifact false + - name: Download project-manager + uses: actions/download-artifact@v4 + with: + name: project-manager-linux + path: dist/backend + - run: |- + tar -xvf dist/backend/project-manager.tar -C dist/backend + rm dist/backend/project-manager.tar + - run: ./run ide build --backend-source local --gui-upload-artifact false env: ENSO_IDE_AG_GRID_LICENSE_KEY: ${{ vars.ENSO_AG_GRID_LICENSE_KEY }} ENSO_IDE_API_URL: ${{ vars.ENSO_CLOUD_API_URL }} @@ -270,6 +311,11 @@ jobs: compression-level: 0 name: test-traces-linux-amd64 path: app/ide-desktop/client/test-traces + - name: Upload ide + uses: actions/upload-artifact@v4 + with: + name: ide-linux + path: dist/ide/enso-*.AppImage - run: rm $HOME/.enso/credentials env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -313,7 +359,15 @@ jobs: run: ./run git-clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: ./run ide build --backend-source current-ci-run --gui-upload-artifact false + - name: Download project-manager + uses: actions/download-artifact@v4 + with: + name: project-manager-windows + path: dist/backend + - run: |- + tar -xvf dist/backend/project-manager.tar -C dist/backend + rm dist/backend/project-manager.tar + - run: ./run ide build --backend-source local --gui-upload-artifact false env: ENSO_IDE_AG_GRID_LICENSE_KEY: ${{ vars.ENSO_AG_GRID_LICENSE_KEY }} ENSO_IDE_API_URL: ${{ vars.ENSO_CLOUD_API_URL }} @@ -344,6 +398,11 @@ jobs: compression-level: 0 name: test-traces-windows-amd64 path: app/ide-desktop/client/test-traces + - name: Upload ide + uses: actions/upload-artifact@v4 + with: + name: ide-windows + path: dist/ide/enso-*.exe - run: rm $HOME/.enso/credentials env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index 1a7dec273de7..bb9bfa61ba51 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -16,6 +16,9 @@ permissions: statuses: write # Write access to commit statuses env: + # Workaround for https://github.com/nodejs/corepack/issues/612 + # See: https://github.com/nodejs/corepack/blob/main/README.md#environment-variables + COREPACK_DEFAULT_TO_LATEST: 0 ENSO_BUILD_SKIP_VERSION_CHECK: "true" PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 diff --git a/.github/workflows/wasm-changed-files.yml b/.github/workflows/wasm-changed-files.yml index fdbf1aa2b345..6998435d8222 100644 --- a/.github/workflows/wasm-changed-files.yml +++ b/.github/workflows/wasm-changed-files.yml @@ -30,7 +30,7 @@ jobs: files: | .cargo/** app/rust-ffi/** - build/** + build_tools/** lib/rust/** tools/language-server/logstat/** tools/language-server/wstest/** diff --git a/CHANGELOG.md b/CHANGELOG.md index ff571c06f58e..9dba2e7c5e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [Quick Fix Import Button][12051]. - [Fixed nodes being selected after deleting other nodes or connections.][11902] - [Redo stack is no longer lost when interacting with text literals][11908]. +- [Copy button on error message is fixed][12133]. - [Tooltips are hidden when clicking on a button][12067]. - [Fixed bug when clicking header in Table Editor Widget didn't start editing it][12064] @@ -21,6 +22,7 @@ [12051]: https://github.com/enso-org/enso/pull/12051 [11902]: https://github.com/enso-org/enso/pull/11902 [11908]: https://github.com/enso-org/enso/pull/11908 +[12133]: https://github.com/enso-org/enso/pull/12133 [12067]: https://github.com/enso-org/enso/pull/12067 [12064]: https://github.com/enso-org/enso/pull/12064 [12129]: https://github.com/enso-org/enso/pull/12129 diff --git a/app/gui/env.d.ts b/app/gui/env.d.ts index 9dfeb5ffa61f..0a84bf999156 100644 --- a/app/gui/env.d.ts +++ b/app/gui/env.d.ts @@ -199,3 +199,14 @@ declare global { (message: string, projectId?: string | null, metadata?: object | null): void } } + +// Add additional types for svg imports from `#/assets/*.svg` +declare module 'vite/client' { + declare module '#/assets/*.svg' { + /** + * @deprecated Prefer defined keys over importing from `#/assets/*.svg + */ + const src: string + export default src + } +} diff --git a/app/gui/integration-test/project-view/actions.ts b/app/gui/integration-test/project-view/actions.ts index 00522b0410aa..500fe10cc6bd 100644 --- a/app/gui/integration-test/project-view/actions.ts +++ b/app/gui/integration-test/project-view/actions.ts @@ -36,7 +36,7 @@ export async function expectNodePositionsInitialized(page: Page, yPos: number) { // Wait until edges are initialized and displayed correctly. await expect(page.getByTestId('broken-edge')).toBeHidden() // Wait until node sizes are initialized. - await expect(locate.graphNode(page).first().locator('.bgFill')).toBeVisible() + await expect(locate.graphNode(page).first().locator('.nodeBackground')).toBeVisible() // TODO: The yPos should not need to be a variable. Instead, first automatically positioned nodes // should always have constant known position. This is a bug caused by incorrect layout after // entering a function. To be fixed with #9255 diff --git a/app/gui/integration-test/project-view/componentBrowser.spec.ts b/app/gui/integration-test/project-view/componentBrowser.spec.ts index 95607c23504e..81f06b6c86f6 100644 --- a/app/gui/integration-test/project-view/componentBrowser.spec.ts +++ b/app/gui/integration-test/project-view/componentBrowser.spec.ts @@ -59,7 +59,10 @@ test('Different ways of opening Component Browser', async ({ page }) => { await locate.graphEditor(page).press('Enter') await expectAndCancelBrowser(page, '', 'selected') // Dragging out an edge - let outputPort = await locate.outputPortCoordinates(locate.graphNodeByBinding(page, 'selected')) + let outputPort = await locate.outputPortCoordinates( + page, + locate.graphNodeByBinding(page, 'selected'), + ) await page.mouse.click(outputPort.x, outputPort.y) await locate.graphEditor(page).click({ position: { x: 100, y: 500 } }) await expectAndCancelBrowser(page, '', 'selected') @@ -67,7 +70,7 @@ test('Different ways of opening Component Browser', async ({ page }) => { // TODO[ao] Without timeout, even the first click would be treated as double due to previous // event. Probably we need a better way to simulate double clicks. await page.waitForTimeout(600) - outputPort = await locate.outputPortCoordinates(locate.graphNodeByBinding(page, 'selected')) + outputPort = await locate.outputPortCoordinates(page, locate.graphNodeByBinding(page, 'selected')) await page.mouse.click(outputPort.x, outputPort.y) await page.mouse.click(outputPort.x, outputPort.y) await expectAndCancelBrowser(page, '', 'selected') @@ -79,15 +82,16 @@ test('Opening Component Browser from output port buttons', async ({ page }) => { // Small (+) button shown when node is hovered const node = locate.graphNodeByBinding(page, 'selected') await locate.graphNodeIcon(node).hover() - await expect(locate.createNodeFromPort(node)).toBeVisible() - await locate.createNodeFromPort(node).click({ force: true }) + const createNodeFromPortButton = await locate.createNodeFromPortButton(page, node) + await expect(createNodeFromPortButton).toBeVisible() + await createNodeFromPortButton.click({ force: true }) await expectAndCancelBrowser(page, '', 'selected') // Small (+) button shown when node is selected await page.keyboard.press('Escape') await node.click() - await expect(locate.createNodeFromPort(node)).toBeVisible() - await locate.createNodeFromPort(node).click({ force: true }) + await expect(createNodeFromPortButton).toBeVisible() + await createNodeFromPortButton.click({ force: true }) await expectAndCancelBrowser(page, '', 'selected') }) @@ -111,7 +115,10 @@ test('Graph Editor pans to Component Browser', async ({ page }) => { await page.mouse.move(100, 280) await page.mouse.up({ button: 'middle' }) await expect(locate.graphNodeByBinding(page, 'five')).toBeInViewport() - const outputPort = await locate.outputPortCoordinates(locate.graphNodeByBinding(page, 'final')) + const outputPort = await locate.outputPortCoordinates( + page, + locate.graphNodeByBinding(page, 'final'), + ) await page.mouse.click(outputPort.x, outputPort.y) await locate.graphEditor(page).click({ position: { x: 100, y: 1700 } }) await expect(locate.graphNodeByBinding(page, 'five')).not.toBeInViewport() diff --git a/app/gui/integration-test/project-view/edgeInteractions.spec.ts b/app/gui/integration-test/project-view/edgeInteractions.spec.ts index 574e68db9553..72e313795220 100644 --- a/app/gui/integration-test/project-view/edgeInteractions.spec.ts +++ b/app/gui/integration-test/project-view/edgeInteractions.spec.ts @@ -82,7 +82,7 @@ test('Conditional ports: Disabled', async ({ page }) => { // When a port is disabled, it doesn't react to hovering with a disconnected edge, // and any attempt to connect to it should open the CB. - const outputPort = await outputPortCoordinates(graphNodeByBinding(page, 'final')) + const outputPort = await outputPortCoordinates(page, graphNodeByBinding(page, 'final')) await page.mouse.click(outputPort.x, outputPort.y) await conditionalPort.hover() await expect(conditionalPort).not.toHaveClass(/isTarget/) @@ -101,7 +101,7 @@ test('Conditional ports: Enabled', async ({ page }) => { await page.keyboard.down(CONTROL_KEY) await expect(conditionalPort).toHaveClass(/enabled/) - const outputPort = await outputPortCoordinates(graphNodeByBinding(page, 'final')) + const outputPort = await outputPortCoordinates(page, graphNodeByBinding(page, 'final')) await page.mouse.click(outputPort.x, outputPort.y) await conditionalPort.hover() await expect(conditionalPort).toHaveClass(/isTarget/) diff --git a/app/gui/integration-test/project-view/graphNodeVisualization.spec.ts b/app/gui/integration-test/project-view/graphNodeVisualization.spec.ts index 6b8d9ddd0861..271641addc76 100644 --- a/app/gui/integration-test/project-view/graphNodeVisualization.spec.ts +++ b/app/gui/integration-test/project-view/graphNodeVisualization.spec.ts @@ -29,7 +29,7 @@ test('Node can open and load visualization', async ({ page }) => { test('Previewing visualization', async ({ page }) => { await actions.goToGraph(page) const node = locate.graphNode(page).last() - const port = await locate.outputPortCoordinates(node) + const port = await locate.outputPortCoordinates(page, node) await page.keyboard.down('Meta') await page.keyboard.down('Control') await expect(locate.anyVisualization(page)).toBeHidden() diff --git a/app/gui/integration-test/project-view/locate.ts b/app/gui/integration-test/project-view/locate.ts index e8a4a04b769f..d9c699ba898b 100644 --- a/app/gui/integration-test/project-view/locate.ts +++ b/app/gui/integration-test/project-view/locate.ts @@ -83,7 +83,6 @@ export const componentMenu = componentLocator('.ComponentMenu') export const addNewNodeButton = componentLocator('.PlusButton') export const componentBrowser = componentLocator('.ComponentBrowser') export const nodeOutputPort = componentLocator('.outputPortHoverArea') -export const createNodeFromPort = componentLocator('.CreateNodeFromPortButton .plusIcon') export const editorRoot = componentLocator('.CodeMirror') export const nodeComment = componentLocator('.GraphNodeComment') export const nodeCommentContent = componentLocator('.GraphNodeComment div[contentEditable]') @@ -185,11 +184,22 @@ export async function edgesToNode(page: Page, node: Locator) { * Returns a location that can be clicked to activate an output port. * Using a `Locator` would be better, but `position` option of `click` doesn't work. */ -export async function outputPortCoordinates(node: Locator) { - const outputPortArea = await node.locator('.outputPortHoverArea').boundingBox() +export async function outputPortCoordinates(page: Page, node: Locator) { + const nodeId = await node.getAttribute('data-node-id') + const outputPortArea = await page + .locator(`.GraphNodeOutputPorts[data-output-ports-node-id="${nodeId}"] .outputPortHoverArea`) + .boundingBox() expect(outputPortArea).not.toBeNull() assert(outputPortArea) const centerX = outputPortArea.x + outputPortArea.width / 2 const bottom = outputPortArea.y + outputPortArea.height return { x: centerX, y: bottom - 2.0 } } + +/** Returns a locator for the create node from port button. */ +export async function createNodeFromPortButton(page: Page, node: Locator) { + const nodeId = await node.getAttribute('data-node-id') + return page.locator( + `.GraphNodeOutputPorts[data-output-ports-node-id="${nodeId}"] .CreateNodeFromPortButton .plusIcon`, + ) +} diff --git a/app/gui/scripts/generateIconMetadata.mjs b/app/gui/scripts/generateIconMetadata.mjs index 242b2c76f8fd..e93936267f65 100644 --- a/app/gui/scripts/generateIconMetadata.mjs +++ b/app/gui/scripts/generateIconMetadata.mjs @@ -15,7 +15,7 @@ await fs.writeFile( // Please run \`bazel run //:write_all\` to regenerate this file whenever \`icons.svg\` is changed. /** All icon names present in icons.svg. */ -const iconNames = [ +export const iconNames = [ ${iconNames?.map((name) => ` '${name}',`).join('\n')} ] as const diff --git a/app/gui/src/App.vue b/app/gui/src/App.vue index c81288cfaf3e..8bbf7ffb29ea 100644 --- a/app/gui/src/App.vue +++ b/app/gui/src/App.vue @@ -5,7 +5,7 @@ import ProjectView from '@/ProjectView.vue' import { provideAppClassSet } from '@/providers/appClass' import { provideGuiConfig } from '@/providers/guiConfig' import { provideTooltipRegistry } from '@/providers/tooltipRegistry' -import { registerAutoBlurHandler } from '@/util/autoBlur' +import { registerAutoBlurHandler, registerGlobalBlurHandler } from '@/util/autoBlur' import { baseConfig, configValue, mergeConfig, type ApplicationConfigValue } from '@/util/config' import { urlParams } from '@/util/urlParams' import { useQueryClient } from '@tanstack/vue-query' @@ -38,6 +38,7 @@ const queryClient = useQueryClient() provideGuiConfig(appConfigValue) registerAutoBlurHandler() +registerGlobalBlurHandler() onMounted(() => { if (appConfigValue.value.window.vibrancy) { diff --git a/app/gui/src/dashboard/components/AriaComponents/Button/Button.stories.tsx b/app/gui/src/dashboard/components/AriaComponents/Button/Button.stories.tsx index 6eec46bd6c40..801eafa992be 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Button/Button.stories.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Button/Button.stories.tsx @@ -9,7 +9,7 @@ import { expect, userEvent, within } from '@storybook/test' import { Button, type BaseButtonProps } from '.' import { Badge } from '../../Badge' -type Story = StoryObj> +type Story = StoryObj> const variants = [ 'primary', @@ -40,7 +40,7 @@ export default { addonStart: { control: false }, addonEnd: { control: false }, }, -} as Meta> +} satisfies Meta> export const Variants: Story = { render: () => ( diff --git a/app/gui/src/dashboard/components/AriaComponents/Button/Button.tsx b/app/gui/src/dashboard/components/AriaComponents/Button/Button.tsx index 327ea8fd4791..cee8ed33c59b 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Button/Button.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Button/Button.tsx @@ -10,11 +10,10 @@ import { } from 'react' import * as aria from '#/components/aria' -import { StatelessSpinner } from '#/components/StatelessSpinner' -import SvgMask from '#/components/SvgMask' - import { useVisualTooltip } from '#/components/AriaComponents/Text' import { Tooltip, TooltipTrigger } from '#/components/AriaComponents/Tooltip' +import { Icon as IconComponent } from '#/components/Icon' +import { StatelessSpinner } from '#/components/StatelessSpinner' import { useEventCallback } from '#/hooks/eventCallbackHooks' import { forwardRef } from '#/utilities/react' import { ButtonGroup, ButtonGroupJoin } from './ButtonGroup' @@ -28,7 +27,10 @@ const ICON_LOADER_DELAY = 150 // Manually casting types to make TS infer the final type correctly (e.g. RenderProps in icon) // eslint-disable-next-line no-restricted-syntax export const Button = memo( - forwardRef(function Button(props: ButtonProps, ref: ForwardedRef) { + forwardRef(function Button( + props: ButtonProps, + ref: ForwardedRef, + ) { props = useMergedButtonStyles(props) const { className, @@ -252,7 +254,9 @@ export const Button = memo( ) }), -) as unknown as ((props: ButtonProps & { ref?: ForwardedRef }) => ReactNode) & { +) as unknown as (( + props: ButtonProps & { ref?: ForwardedRef }, +) => ReactNode) & { // eslint-disable-next-line @typescript-eslint/naming-convention Group: typeof ButtonGroup // eslint-disable-next-line @typescript-eslint/naming-convention @@ -382,7 +386,7 @@ const Icon = memo(function Icon(props: IconProps) { const actualIcon = (() => { return typeof icon === 'string' ? - + {icon} : {icon} })() diff --git a/app/gui/src/dashboard/components/AriaComponents/Button/CloseButton.tsx b/app/gui/src/dashboard/components/AriaComponents/Button/CloseButton.tsx index 120d13d2dbe5..265ae955eca5 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Button/CloseButton.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Button/CloseButton.tsx @@ -8,10 +8,15 @@ import { Button } from './Button' import type { ButtonProps } from './types' /** Props for a {@link CloseButton}. */ -export type CloseButtonProps = Omit +export type CloseButtonProps = Omit< + ButtonProps, + 'children' | 'rounding' | 'size' | 'variant' +> /** A styled button with a close icon that appears on hover. */ -export const CloseButton = memo(function CloseButton(props: CloseButtonProps) { +export const CloseButton = memo(function CloseButton( + props: CloseButtonProps, +) { const { getText } = useText() const { diff --git a/app/gui/src/dashboard/components/AriaComponents/Button/CopyButton.tsx b/app/gui/src/dashboard/components/AriaComponents/Button/CopyButton.tsx index e2567b6837a7..5a5f57801211 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Button/CopyButton.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Button/CopyButton.tsx @@ -17,7 +17,8 @@ import type { ButtonProps } from './types' // ================== /** Props for a {@link CopyButton}. */ -export interface CopyButtonProps extends Omit { +export interface CopyButtonProps + extends Omit, 'icon' | 'loading' | 'onPress'> { /** The text to copy to the clipboard. */ readonly copyText: string /** @@ -38,7 +39,7 @@ export interface CopyButtonProps extends Omit(props: CopyButtonProps) { const { variant = 'icon', copyIcon = CopyIcon, diff --git a/app/gui/src/dashboard/components/AriaComponents/Button/types.ts b/app/gui/src/dashboard/components/AriaComponents/Button/types.ts index 797a6f5957c0..72a1a7c5761b 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Button/types.ts +++ b/app/gui/src/dashboard/components/AriaComponents/Button/types.ts @@ -42,11 +42,11 @@ export interface LinkRenderProps extends aria.LinkRenderProps { } /** Props for a Button. */ -export type ButtonProps = - | (BaseButtonProps & +export type ButtonProps = + | (BaseButtonProps & Omit & PropsWithoutHref) - | (BaseButtonProps & + | (BaseButtonProps & Omit & PropsWithHref) @@ -61,7 +61,7 @@ interface PropsWithoutHref { } /** Base props for a button. */ -export interface BaseButtonProps +export interface BaseButtonProps extends Omit, TestIdProps { /** If `true`, the loader will not be shown. */ @@ -70,7 +70,7 @@ export interface BaseButtonProps readonly tooltip?: ReactElement | string | false | null readonly tooltipPlacement?: aria.Placement /** The icon to display in the button */ - readonly icon?: IconProp + readonly icon?: IconProp /** When `true`, icon will be shown only when hovered. */ readonly showIconOnHover?: boolean /** diff --git a/app/gui/src/dashboard/components/AriaComponents/Dialog/Close.tsx b/app/gui/src/dashboard/components/AriaComponents/Dialog/Close.tsx index f49ea796f9c8..61eaad4d3c3e 100644 --- a/app/gui/src/dashboard/components/AriaComponents/Dialog/Close.tsx +++ b/app/gui/src/dashboard/components/AriaComponents/Dialog/Close.tsx @@ -4,25 +4,23 @@ * Close button for a dialog. */ -import invariant from 'tiny-invariant' - import { useEventCallback } from '#/hooks/eventCallbackHooks' import { type ButtonProps, Button } from '../Button' import * as dialogProvider from './DialogProvider' /** Props for {@link Close} component. */ -export type CloseProps = ButtonProps +export type CloseProps = ButtonProps /** Close button for a dialog. */ -export function Close(props: CloseProps) { - const dialogContext = dialogProvider.useDialogContext() - - invariant(dialogContext, 'Close must be used inside a DialogProvider') +export function Close(props: CloseProps) { + const dialogContext = dialogProvider.useDialogStrictContext() - const onPressCallback = useEventCallback>((event) => { - dialogContext.close() - return props.onPress?.(event) - }) + const onPressCallback = useEventCallback['onPress']>>( + (event) => { + dialogContext.close() + return props.onPress?.(event) + }, + ) return