From 89c1e37f7b4a7cf1b67bde3a9f5b8c0449d58c38 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 15 Oct 2026 10:56:56 +0200 Subject: [PATCH 01/11] ref(scraps): Move isolated core components into the package Move isolated components, contexts, helpers, and their tests out of the app. Give Scraps the theme support and browser types needed to build without app code. --- oxlint-suppressions.json | 5 - oxlint.config.ts | 2 +- pnpm-lock.yaml | 66 +++++++ static/app/components/core/avatar/avatar.tsx | 4 +- .../core/avatar/imageAvatar/imageAvatar.tsx | 7 +- static/app/components/core/avatar/index.tsx | 2 +- .../app/components/core/avatar/useAvatar.ts | 3 +- .../core/avatarButton/useAvatarColors.ts | 3 +- static/app/components/core/badge/index.tsx | 2 +- .../core/breadcrumbList/breadcrumbList.tsx | 2 +- .../items/breadcrumbItemLink.tsx | 3 +- .../items/breadcrumbItemPageTitle.tsx | 3 +- .../items/breadcrumbItemPageTitleEditable.tsx | 3 +- .../items/breadcrumbItemSelect.tsx | 2 +- static/app/components/core/button/index.tsx | 2 +- static/app/components/core/chat/index.tsx | 8 +- .../core/chat/toolCallIndicator.tsx | 3 +- static/app/components/core/code/codeBlock.tsx | 3 +- static/app/components/core/code/index.tsx | 2 +- .../components/core/compactSelect/index.tsx | 2 +- .../app/components/core/composer/composer.tsx | 28 ++- static/app/components/core/composer/index.tsx | 2 +- static/app/components/core/composer/types.ts | 4 +- .../core/composer/useComposerSuggestions.tsx | 3 +- .../components/core/dropdownMenu/context.tsx | 3 +- .../components/core/dropdownMenu/index.tsx | 2 +- .../app/components/core/dropdownMenu/list.tsx | 2 +- .../core/entityHeader/entityHeader.tsx | 2 +- .../components/core/form/field/baseField.tsx | 2 +- .../app/components/core/form/field/meta.tsx | 3 +- .../components/core/form/field/radioField.tsx | 4 +- .../components/core/form/field/rangeField.tsx | 2 +- static/app/components/core/form/index.ts | 4 +- .../app/components/core/form/scrapsForm.tsx | 14 +- static/app/components/core/input/index.tsx | 2 +- .../core/markdown/defaultComponents.tsx | 3 +- static/app/components/core/markdown/index.tsx | 14 +- .../app/components/core/markdown/markdown.tsx | 9 +- static/app/components/core/markdown/token.tsx | 4 +- .../core/pictureInPicture/index.tsx | 5 +- static/app/components/core/scraps.spec.tsx | 14 ++ static/app/components/core/select/index.tsx | 4 +- static/app/components/core/select/option.tsx | 2 +- static/app/components/core/select/select.tsx | 24 +-- .../app/components/core/slot/knownContexts.ts | 4 - static/app/components/core/table/index.tsx | 2 +- static/app/components/core/table/table.tsx | 21 +- static/app/components/core/tabs/tab.tsx | 3 +- static/app/components/core/tabs/tabList.tsx | 2 +- static/app/components/core/tabs/tabPanels.tsx | 3 +- static/app/components/core/tabs/tabs.tsx | 3 +- static/app/components/core/toast/index.tsx | 6 +- static/app/components/core/toast/toast.tsx | 3 +- static/app/components/core/toast/toastApi.tsx | 3 +- .../app/types/documentPictureInPicture.d.ts | 33 ---- static/app/types/trustedTypes.d.ts | 4 - static/app/utils/theme/theme.tsx | 183 +----------------- static/packages/scraps/jest.config.mjs | 5 +- static/packages/scraps/package.json | 72 ++++++- static/packages/scraps/rslib.config.mjs | 2 +- static/packages/scraps/scripts/verify.mjs | 5 +- .../src}/avatar/avatarComponentStyles.tsx | 0 .../avatar/letterAvatar/letterAvatar.spec.tsx | 2 +- .../src}/avatar/letterAvatar/letterAvatar.tsx | 1 - .../avatarButton/avatarImageAnalysis.spec.ts | 0 .../src}/avatarButton/avatarImageAnalysis.ts | 0 .../scraps/src}/backdrop/backdrop.tsx | 0 .../scraps/src}/backdrop/index.tsx | 0 .../scraps/src}/badge/projectsBadge.tsx | 2 +- .../scraps/src}/boundaryContext.tsx | 0 .../breadcrumbList/breadcrumbDividerCombo.tsx | 4 +- .../items/breadcrumbLeadingSlot.tsx | 2 +- .../scraps/src}/button/buttonBar.tsx | 4 +- .../scraps/src}/chat/assistantMessage.tsx | 2 +- .../scraps/src}/chat/messageRow.tsx | 2 +- .../scraps/src}/chat/spinner.tsx | 0 .../scraps/src}/chat/userMessage.tsx | 4 +- .../scraps/src}/code/prism.tsx | 0 .../src}/compactSelect/highlightText.spec.tsx | 4 +- .../src}/compactSelect/highlightText.tsx | 2 +- .../scraps/src}/composer/dom.spec.ts | 0 .../scraps/src}/composer/dom.ts | 0 .../scraps/src}/composer/matching.spec.ts | 0 .../scraps/src}/composer/matching.ts | 0 .../scraps/src}/composer/model.spec.ts | 0 .../scraps/src}/composer/model.ts | 0 .../scraps/src}/datetime.spec.tsx | 4 +- .../core => packages/scraps/src}/datetime.tsx | 0 .../scraps/src}/dropdownMenu/footer.tsx | 0 .../src}/dropdownMenu/useSafetyTriangle.ts | 0 .../src}/emptyState/emptyState.spec.tsx | 4 +- .../scraps/src}/emptyState/emptyState.tsx | 4 +- .../scraps/src}/emptyState/index.tsx | 0 .../scraps/src}/entityHeader/constants.tsx | 0 .../scraps/src}/form/FormSearch.tsx | 0 .../scraps/src}/form/autoSaveContext.tsx | 0 .../scraps/src}/form/field/fieldIds.tsx | 2 +- .../scraps/src}/form/field/groupContext.tsx | 0 .../scraps/src}/form/formContext.ts | 0 static/packages/scraps/src/index.ts | 12 ++ .../scraps/src}/input/useAutosizeInput.tsx | 0 .../scraps/src}/markdown/extensions/index.ts | 2 +- .../src}/markdown/extensions/tag.spec.ts | 0 .../scraps/src}/markdown/extensions/tag.ts | 0 .../scraps/src}/markdown/marked.spec.tsx | 5 +- .../scraps/src}/markdown/marked.tsx | 10 +- .../src}/markdown/useStreamingAnimation.ts | 0 .../pictureInPicture.spec.tsx | 7 +- .../pictureInPicture/pictureInPicture.tsx | 30 +++ .../scraps/src}/renderToString.spec.tsx | 17 +- .../scraps/src}/renderToString.tsx | 0 .../scraps/src}/revealOnHover/index.tsx | 0 .../src}/revealOnHover/revealOnHover.spec.tsx | 29 ++- .../src}/revealOnHover/revealOnHover.tsx | 4 +- .../scraps/src}/select/reactSelectWrapper.tsx | 0 .../scraps/src}/sizeContext.tsx | 0 .../scraps/src}/slider/index.tsx | 0 .../scraps/src}/slider/slider.spec.tsx | 4 +- .../scraps/src}/slider/slider.tsx | 0 .../scraps/src}/slot/index.tsx | 0 .../packages/scraps/src/slot/knownContexts.ts | 4 + .../scraps/src}/slot/slot.spec.tsx | 7 +- .../scraps/src}/slot/slot.tsx | 0 .../scraps/src}/switch/index.tsx | 0 .../scraps/src}/switch/switch.spec.tsx | 4 +- .../scraps/src}/switch/switch.tsx | 2 +- .../scraps/src}/table/styles.tsx | 2 +- .../scraps/src}/tabs/utils.tsx | 0 static/packages/scraps/src/theme/index.ts | 30 ++- .../scraps/src/theme/runtime.spec.tsx | 21 ++ static/packages/scraps/src/theme/runtime.ts | 177 +++++++++++++++++ .../scraps/src}/toast/toaster.tsx | 2 +- .../scraps/src}/toast/types.tsx | 0 .../scraps/src}/translation/index.ts | 2 + .../src}/translation/translationContext.tsx | 0 .../scraps/src}/translation/useTranslation.ts | 0 .../scraps/src}/useScrollLock.spec.tsx | 24 ++- .../scraps/src}/useScrollLock.tsx | 0 static/packages/scraps/test/env/index.tsx | 4 +- static/packages/scraps/test/env/setup.ts | 7 + static/packages/scraps/tsconfig.json | 15 +- static/packages/scraps/tsconfig.test.json | 7 +- 142 files changed, 684 insertions(+), 424 deletions(-) delete mode 100644 static/app/components/core/slot/knownContexts.ts delete mode 100644 static/app/types/documentPictureInPicture.d.ts rename static/{app/components/core => packages/scraps/src}/avatar/avatarComponentStyles.tsx (100%) rename static/{app/components/core => packages/scraps/src}/avatar/letterAvatar/letterAvatar.spec.tsx (94%) rename static/{app/components/core => packages/scraps/src}/avatar/letterAvatar/letterAvatar.tsx (96%) rename static/{app/components/core => packages/scraps/src}/avatarButton/avatarImageAnalysis.spec.ts (100%) rename static/{app/components/core => packages/scraps/src}/avatarButton/avatarImageAnalysis.ts (100%) rename static/{app/components/core => packages/scraps/src}/backdrop/backdrop.tsx (100%) rename static/{app/components/core => packages/scraps/src}/backdrop/index.tsx (100%) rename static/{app/components/core => packages/scraps/src}/badge/projectsBadge.tsx (98%) rename static/{app/components/core => packages/scraps/src}/boundaryContext.tsx (100%) rename static/{app/components/core => packages/scraps/src}/breadcrumbList/breadcrumbDividerCombo.tsx (93%) rename static/{app/components/core => packages/scraps/src}/breadcrumbList/items/breadcrumbLeadingSlot.tsx (94%) rename static/{app/components/core => packages/scraps/src}/button/buttonBar.tsx (97%) rename static/{app/components/core => packages/scraps/src}/chat/assistantMessage.tsx (93%) rename static/{app/components/core => packages/scraps/src}/chat/messageRow.tsx (97%) rename static/{app/components/core => packages/scraps/src}/chat/spinner.tsx (100%) rename static/{app/components/core => packages/scraps/src}/chat/userMessage.tsx (93%) rename static/{app/components/core => packages/scraps/src}/code/prism.tsx (100%) rename static/{app/components/core => packages/scraps/src}/compactSelect/highlightText.spec.tsx (91%) rename static/{app/components/core => packages/scraps/src}/compactSelect/highlightText.tsx (96%) rename static/{app/components/core => packages/scraps/src}/composer/dom.spec.ts (100%) rename static/{app/components/core => packages/scraps/src}/composer/dom.ts (100%) rename static/{app/components/core => packages/scraps/src}/composer/matching.spec.ts (100%) rename static/{app/components/core => packages/scraps/src}/composer/matching.ts (100%) rename static/{app/components/core => packages/scraps/src}/composer/model.spec.ts (100%) rename static/{app/components/core => packages/scraps/src}/composer/model.ts (100%) rename static/{app/components/core => packages/scraps/src}/datetime.spec.tsx (87%) rename static/{app/components/core => packages/scraps/src}/datetime.tsx (100%) rename static/{app/components/core => packages/scraps/src}/dropdownMenu/footer.tsx (100%) rename static/{app/components/core => packages/scraps/src}/dropdownMenu/useSafetyTriangle.ts (100%) rename static/{app/components/core => packages/scraps/src}/emptyState/emptyState.spec.tsx (92%) rename static/{app/components/core => packages/scraps/src}/emptyState/emptyState.tsx (92%) rename static/{app/components/core => packages/scraps/src}/emptyState/index.tsx (100%) rename static/{app/components/core => packages/scraps/src}/entityHeader/constants.tsx (100%) rename static/{app/components/core => packages/scraps/src}/form/FormSearch.tsx (100%) rename static/{app/components/core => packages/scraps/src}/form/autoSaveContext.tsx (100%) rename static/{app/components/core => packages/scraps/src}/form/field/fieldIds.tsx (82%) rename static/{app/components/core => packages/scraps/src}/form/field/groupContext.tsx (100%) rename static/{app/components/core => packages/scraps/src}/form/formContext.ts (100%) rename static/{app/components/core => packages/scraps/src}/input/useAutosizeInput.tsx (100%) rename static/{app/components/core => packages/scraps/src}/markdown/extensions/index.ts (77%) rename static/{app/components/core => packages/scraps/src}/markdown/extensions/tag.spec.ts (100%) rename static/{app/components/core => packages/scraps/src}/markdown/extensions/tag.ts (100%) rename static/{app/components/core => packages/scraps/src}/markdown/marked.spec.tsx (99%) rename static/{app/components/core => packages/scraps/src}/markdown/marked.tsx (97%) rename static/{app/components/core => packages/scraps/src}/markdown/useStreamingAnimation.ts (100%) rename static/{app/components/core => packages/scraps/src}/pictureInPicture/pictureInPicture.spec.tsx (97%) rename static/{app/components/core => packages/scraps/src}/pictureInPicture/pictureInPicture.tsx (90%) rename static/{app/components/core => packages/scraps/src}/renderToString.spec.tsx (63%) rename static/{app/components/core => packages/scraps/src}/renderToString.tsx (100%) rename static/{app/components/core => packages/scraps/src}/revealOnHover/index.tsx (100%) rename static/{app/components/core => packages/scraps/src}/revealOnHover/revealOnHover.spec.tsx (87%) rename static/{app/components/core => packages/scraps/src}/revealOnHover/revealOnHover.tsx (93%) rename static/{app/components/core => packages/scraps/src}/select/reactSelectWrapper.tsx (100%) rename static/{app/components/core => packages/scraps/src}/sizeContext.tsx (100%) rename static/{app/components/core => packages/scraps/src}/slider/index.tsx (100%) rename static/{app/components/core => packages/scraps/src}/slider/slider.spec.tsx (98%) rename static/{app/components/core => packages/scraps/src}/slider/slider.tsx (100%) rename static/{app/components/core => packages/scraps/src}/slot/index.tsx (100%) create mode 100644 static/packages/scraps/src/slot/knownContexts.ts rename static/{app/components/core => packages/scraps/src}/slot/slot.spec.tsx (98%) rename static/{app/components/core => packages/scraps/src}/slot/slot.tsx (100%) rename static/{app/components/core => packages/scraps/src}/switch/index.tsx (100%) rename static/{app/components/core => packages/scraps/src}/switch/switch.spec.tsx (84%) rename static/{app/components/core => packages/scraps/src}/switch/switch.tsx (98%) rename static/{app/components/core => packages/scraps/src}/table/styles.tsx (97%) rename static/{app/components/core => packages/scraps/src}/tabs/utils.tsx (100%) create mode 100644 static/packages/scraps/src/theme/runtime.spec.tsx create mode 100644 static/packages/scraps/src/theme/runtime.ts rename static/{app/components/core => packages/scraps/src}/toast/toaster.tsx (93%) rename static/{app/components/core => packages/scraps/src}/toast/types.tsx (100%) rename static/{app/components/core => packages/scraps/src}/translation/index.ts (80%) rename static/{app/components/core => packages/scraps/src}/translation/translationContext.tsx (100%) rename static/{app/components/core => packages/scraps/src}/translation/useTranslation.ts (100%) rename static/{app/components/core => packages/scraps/src}/useScrollLock.spec.tsx (92%) rename static/{app/components/core => packages/scraps/src}/useScrollLock.tsx (100%) diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 815adfb94740..cb741f44bbfd 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -167,11 +167,6 @@ "count": 1 } }, - "static/app/components/core/useScrollLock.spec.tsx": { - "@sentry/require-fake-timer-cleanup": { - "count": 1 - } - }, "static/app/components/customCommitsResolutionModal.tsx": { "@sentry/scraps/prefer-primitives": { "count": 1 diff --git a/oxlint.config.ts b/oxlint.config.ts index 596bf39c1d78..19618833ca46 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -1806,7 +1806,7 @@ const config = defineConfig({ }, { files: [ - 'static/packages/scraps/src/**/*.spec.tsx', + 'static/packages/scraps/src/**/*.spec.{ts,tsx}', 'static/packages/scraps/test/**/*.{ts,tsx,mjs}', 'static/packages/icons/src/**/*.spec.tsx', 'static/packages/icons/test/**/*.{ts,tsx,mjs}', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index afe90ebd9fbb..e08b5b01780a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1322,18 +1322,75 @@ importers: '@emotion/styled': specifier: ^11.14.0 version: 11.14.0(@emotion/react@11.14.0(@types/react@19.3.0)(react@19.3.0)(supports-color@8.1.1))(@types/react@19.3.0)(react@19.3.0)(supports-color@8.1.1) + '@react-aria/i18n': + specifier: 3.13.1 + version: 3.13.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@react-aria/slider': + specifier: 3.9.1 + version: 3.9.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@react-aria/utils': specifier: 3.34.1 version: 3.34.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@react-aria/visually-hidden': + specifier: 3.9.1 + version: 3.9.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@react-stately/slider': + specifier: 3.8.1 + version: 3.8.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@sentry/icons': + specifier: workspace:* + version: link:../icons + '@sentry/react': + specifier: 11.6.0 + version: 11.6.0(react-router@6.30.6(react@19.3.0))(react@19.3.0) + '@tanstack/react-form': + specifier: 1.28.6 + version: 1.28.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@types/react': specifier: 19.3.0 version: 19.3.0 + '@types/react-select': + specifier: 4.0.18 + version: 4.0.18 + '@types/trusted-types': + specifier: ^2.0.7 + version: 2.0.7 + color: + specifier: 5.0.2 + version: 5.0.2 + dompurify: + specifier: 3.4.16 + version: 3.4.16 + framer-motion: + specifier: 12.38.0 + version: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + marked: + specifier: 18.0.2 + version: 18.0.2 + marked-highlight: + specifier: 2.2.4 + version: 2.2.4(marked@18.0.2) + platformicons: + specifier: ^9.8.0 + version: 9.8.0(react@19.3.0) + prismjs: + specifier: ^1.30.0 + version: 1.30.0 react: specifier: ^19.0.0 version: 19.3.0 react-dom: specifier: ^19.0.0 version: 19.3.0(react@19.3.0) + react-select: + specifier: 4.3.1 + version: 4.3.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(supports-color@8.1.1) + sonner: + specifier: 2.0.8 + version: 2.0.8(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + type-fest: + specifier: ^5.8.0 + version: 5.8.0 devDependencies: '@jest/environment': specifier: 30.5.1 @@ -1362,12 +1419,21 @@ importers: '@testing-library/react': specifier: 16.3.2 version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@testing-library/user-event': + specifier: 14.6.7 + version: 14.6.7(@testing-library/dom@10.4.1) '@types/jest': specifier: 30.0.0 version: 30.0.0 '@types/node': specifier: 26.6.4 version: 26.6.4 + '@types/prismjs': + specifier: ^1.26.0 + version: 1.26.0 + '@types/react-dom': + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) expect-type: specifier: 1.4.0 version: 1.4.0 diff --git a/static/app/components/core/avatar/avatar.tsx b/static/app/components/core/avatar/avatar.tsx index 1693969eb8a5..466ded59ab07 100644 --- a/static/app/components/core/avatar/avatar.tsx +++ b/static/app/components/core/avatar/avatar.tsx @@ -2,11 +2,11 @@ import type React from 'react'; import styled from '@emotion/styled'; import classNames from 'classnames'; +import type {BaseAvatarStyleProps} from '@sentry/scraps/avatar/avatarComponentStyles'; +import {LetterAvatar} from '@sentry/scraps/avatar/letterAvatar/letterAvatar'; import {Tooltip, type TooltipProps} from '@sentry/scraps/tooltip'; import {ImageAvatar} from './imageAvatar/imageAvatar'; -import {LetterAvatar} from './letterAvatar/letterAvatar'; -import type {BaseAvatarStyleProps} from './avatarComponentStyles'; import {useAvatar} from './useAvatar'; export interface AvatarProps extends BaseAvatarStyleProps { diff --git a/static/app/components/core/avatar/imageAvatar/imageAvatar.tsx b/static/app/components/core/avatar/imageAvatar/imageAvatar.tsx index d5cfe8c32dc1..32ad8c90be6d 100644 --- a/static/app/components/core/avatar/imageAvatar/imageAvatar.tsx +++ b/static/app/components/core/avatar/imageAvatar/imageAvatar.tsx @@ -1,11 +1,12 @@ import styled from '@emotion/styled'; import type {Tagged} from 'type-fest'; +import { + baseAvatarStyles, + type BaseAvatarStyleProps, +} from '@sentry/scraps/avatar/avatarComponentStyles'; import {Image, type ImageProps} from '@sentry/scraps/image'; -// eslint-disable-next-line @sentry/no-relative-import-paths -import {baseAvatarStyles, type BaseAvatarStyleProps} from '../avatarComponentStyles'; - export interface ImageAvatarProps extends BaseAvatarStyleProps, Omit { configuration: { diff --git a/static/app/components/core/avatar/index.tsx b/static/app/components/core/avatar/index.tsx index 75e22d7eabc6..e46a02eecc33 100644 --- a/static/app/components/core/avatar/index.tsx +++ b/static/app/components/core/avatar/index.tsx @@ -3,7 +3,7 @@ export {ActorAvatar, type ActorAvatarProps} from './actorAvatar'; export {AvatarList, CollapsedAvatars} from './avatarList'; export {DocIntegrationAvatar} from './docIntegrationAvatar'; export {ImageAvatar} from './imageAvatar/imageAvatar'; -export {LetterAvatar} from './letterAvatar/letterAvatar'; +export {LetterAvatar} from '@sentry/scraps/avatar/letterAvatar/letterAvatar'; export {OrganizationAvatar} from './organizationAvatar'; export {ProjectAvatar} from './projectAvatar'; export {SentryAppAvatar} from './sentryAppAvatar'; diff --git a/static/app/components/core/avatar/useAvatar.ts b/static/app/components/core/avatar/useAvatar.ts index 0e895f99762c..59dd26cc38bd 100644 --- a/static/app/components/core/avatar/useAvatar.ts +++ b/static/app/components/core/avatar/useAvatar.ts @@ -6,10 +6,11 @@ import {useQuery} from '@tanstack/react-query'; import * as qs from 'query-string'; import type {Tagged} from 'type-fest'; +import type {LetterAvatarProps} from '@sentry/scraps/avatar/letterAvatar/letterAvatar'; + import {ConfigStore} from 'sentry/stores/configStore'; import type {ImageAvatarProps} from './imageAvatar/imageAvatar'; -import type {LetterAvatarProps} from './letterAvatar/letterAvatar'; type GravatarDefinition = {gravatarId: string; type: 'gravatar'}; type UploadDefinition = {type: 'upload'; uploadUrl: string}; diff --git a/static/app/components/core/avatarButton/useAvatarColors.ts b/static/app/components/core/avatarButton/useAvatarColors.ts index 399a617f3830..456f87d31081 100644 --- a/static/app/components/core/avatarButton/useAvatarColors.ts +++ b/static/app/components/core/avatarButton/useAvatarColors.ts @@ -4,8 +4,7 @@ import color from 'color'; import type {BaseAvatarProps} from '@sentry/scraps/avatar'; import {useAvatar} from '@sentry/scraps/avatar'; - -import {resolveImageAvatarColors} from './avatarImageAnalysis'; +import {resolveImageAvatarColors} from '@sentry/scraps/avatarButton/avatarImageAnalysis'; export type AvatarColorsResult = | {chonk: string; style: 'padded' | 'fill'; type: 'image'} diff --git a/static/app/components/core/badge/index.tsx b/static/app/components/core/badge/index.tsx index 947fbd301ac7..e451916980ce 100644 --- a/static/app/components/core/badge/index.tsx +++ b/static/app/components/core/badge/index.tsx @@ -2,5 +2,5 @@ export {AlertBadge} from './alertBadge'; export {Badge} from './badge'; export {DeployBadge} from './deployBadge'; export {FeatureBadge, type FeatureBadgeProps} from './featureBadge'; -export {ProjectsBadge} from './projectsBadge'; +export {ProjectsBadge} from '@sentry/scraps/badge/projectsBadge'; export {Tag, type TagProps} from './tag'; diff --git a/static/app/components/core/breadcrumbList/breadcrumbList.tsx b/static/app/components/core/breadcrumbList/breadcrumbList.tsx index 52f41a0ac738..5dd7b997b45b 100644 --- a/static/app/components/core/breadcrumbList/breadcrumbList.tsx +++ b/static/app/components/core/breadcrumbList/breadcrumbList.tsx @@ -1,3 +1,4 @@ +import {BreadcrumbDividerCombo} from '@sentry/scraps/breadcrumbList/breadcrumbDividerCombo'; import {Container, Flex, useHasContainerQuery} from '@sentry/scraps/layout'; import {unreachable} from 'sentry/utils/unreachable'; @@ -11,7 +12,6 @@ import type {BreadcrumbItemPageTitleEditableProps} from './items/breadcrumbItemP import {BreadcrumbItemPageTitleEditable} from './items/breadcrumbItemPageTitleEditable'; import type {BreadcrumbItemSelectProps} from './items/breadcrumbItemSelect'; import {BreadcrumbItemSelect} from './items/breadcrumbItemSelect'; -import {BreadcrumbDividerCombo} from './breadcrumbDividerCombo'; type LinkBreadcrumbItem = {type: 'link'} & BreadcrumbItemLinkProps; type PageTitleBreadcrumbItem = { diff --git a/static/app/components/core/breadcrumbList/items/breadcrumbItemLink.tsx b/static/app/components/core/breadcrumbList/items/breadcrumbItemLink.tsx index 1e89c32558e6..9837ac148cc7 100644 --- a/static/app/components/core/breadcrumbList/items/breadcrumbItemLink.tsx +++ b/static/app/components/core/breadcrumbList/items/breadcrumbItemLink.tsx @@ -1,10 +1,9 @@ +import {BreadcrumbLeadingSlot} from '@sentry/scraps/breadcrumbList/items/breadcrumbLeadingSlot'; import {Container, Flex} from '@sentry/scraps/layout'; import type {LinkProps} from '@sentry/scraps/link'; import {ExternalLink, Link} from '@sentry/scraps/link'; import {Text} from '@sentry/scraps/text'; -import {BreadcrumbLeadingSlot} from './breadcrumbLeadingSlot'; - export type BreadcrumbItemLinkProps = { label: string; /** diff --git a/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitle.tsx b/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitle.tsx index 2c4d50a3c7b7..2d8cfaee36ed 100644 --- a/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitle.tsx +++ b/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitle.tsx @@ -6,6 +6,7 @@ import {BreadcrumbCopyAction} from '@sentry/scraps/breadcrumbList/actions/breadc import type {BreadcrumbCopyActionProps} from '@sentry/scraps/breadcrumbList/actions/breadcrumbCopyAction'; import {BreadcrumbMenuAction} from '@sentry/scraps/breadcrumbList/actions/breadcrumbMenuAction'; import type {BreadcrumbMenuActionProps} from '@sentry/scraps/breadcrumbList/actions/breadcrumbMenuAction'; +import {BreadcrumbLeadingSlot} from '@sentry/scraps/breadcrumbList/items/breadcrumbLeadingSlot'; import { Button, type ButtonProps, @@ -20,8 +21,6 @@ import {Tooltip} from '@sentry/scraps/tooltip'; import {unreachable} from 'sentry/utils/unreachable'; -import {BreadcrumbLeadingSlot} from './breadcrumbLeadingSlot'; - /** * A single trailing action for the page-title crumb. The discriminant keeps * the supported action shapes type-safe and the trailing slot visually diff --git a/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitleEditable.tsx b/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitleEditable.tsx index 2d9e663840f1..29194d45788d 100644 --- a/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitleEditable.tsx +++ b/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitleEditable.tsx @@ -1,11 +1,10 @@ +import {BreadcrumbLeadingSlot} from '@sentry/scraps/breadcrumbList/items/breadcrumbLeadingSlot'; import {Container, Flex} from '@sentry/scraps/layout'; import {Heading, Text} from '@sentry/scraps/text'; import {EditableText} from 'sentry/components/editableText'; import {ControlState} from 'sentry/components/forms/fieldGroup/controlState'; -import {BreadcrumbLeadingSlot} from './breadcrumbLeadingSlot'; - export interface BreadcrumbItemPageTitleEditableProps { /** Accessible name for the editable input. */ 'aria-label': string; diff --git a/static/app/components/core/breadcrumbList/items/breadcrumbItemSelect.tsx b/static/app/components/core/breadcrumbList/items/breadcrumbItemSelect.tsx index a336eed17169..0814651a49b3 100644 --- a/static/app/components/core/breadcrumbList/items/breadcrumbItemSelect.tsx +++ b/static/app/components/core/breadcrumbList/items/breadcrumbItemSelect.tsx @@ -1,5 +1,6 @@ import {IconChevron} from '@sentry/icons/chevron'; +import {BreadcrumbLeadingSlot} from '@sentry/scraps/breadcrumbList/items/breadcrumbLeadingSlot'; import {CompactSelect} from '@sentry/scraps/compactSelect'; import type {SelectKey, SingleSelectProps} from '@sentry/scraps/compactSelect'; import {Container, Flex} from '@sentry/scraps/layout'; @@ -9,7 +10,6 @@ import {Text} from '@sentry/scraps/text'; import {useTranslation} from '@sentry/scraps/translation/useTranslation'; import {BreadcrumbItemLink} from './breadcrumbItemLink'; -import {BreadcrumbLeadingSlot} from './breadcrumbLeadingSlot'; export type BreadcrumbItemSelectProps = Pick< Extract, {clearable?: false}>, diff --git a/static/app/components/core/button/index.tsx b/static/app/components/core/button/index.tsx index 300f0ad7524d..0f2bee1c1f70 100644 --- a/static/app/components/core/button/index.tsx +++ b/static/app/components/core/button/index.tsx @@ -1,5 +1,5 @@ export {Button, type ButtonProps} from './button'; -export {ButtonBar, type ButtonBarProps} from './buttonBar'; +export {ButtonBar, type ButtonBarProps} from '@sentry/scraps/button/buttonBar'; export {LinkButton, type LinkButtonProps} from './linkButton'; export {DO_NOT_USE_getButtonStyles} from './styles'; diff --git a/static/app/components/core/chat/index.tsx b/static/app/components/core/chat/index.tsx index a0991ea7e8b3..4229a813c4d6 100644 --- a/static/app/components/core/chat/index.tsx +++ b/static/app/components/core/chat/index.tsx @@ -1,11 +1,11 @@ -export {AssistantMessage} from './assistantMessage'; +export {AssistantMessage} from '@sentry/scraps/chat/assistantMessage'; export {AssistantActions} from './assistantActions'; export {ClippedDetail} from './clippedDetail'; -export {UserMessage} from './userMessage'; +export {UserMessage} from '@sentry/scraps/chat/userMessage'; export {ToolCallIndicator, type ToolCallStatus} from './toolCallIndicator'; /** @public */ export {ToolCall, getToolCallStatusLabel, type ToolCallReference} from './toolCall'; -export {Spinner} from './spinner'; -export {MessageRow} from './messageRow'; +export {Spinner} from '@sentry/scraps/chat/spinner'; +export {MessageRow} from '@sentry/scraps/chat/messageRow'; /** @public */ export {ThinkingBlock} from './thinkingBlock'; diff --git a/static/app/components/core/chat/toolCallIndicator.tsx b/static/app/components/core/chat/toolCallIndicator.tsx index 311f88553052..38a12a12395d 100644 --- a/static/app/components/core/chat/toolCallIndicator.tsx +++ b/static/app/components/core/chat/toolCallIndicator.tsx @@ -2,12 +2,11 @@ import {IconCheckmark} from '@sentry/icons/checkmark'; import {IconClose} from '@sentry/icons/close'; import {IconWarning} from '@sentry/icons/warning'; +import {Spinner} from '@sentry/scraps/chat/spinner'; import {useTranslation} from '@sentry/scraps/translation/useTranslation'; import {unreachable} from 'sentry/utils/unreachable'; -import {Spinner} from './spinner'; - /** * Aggregate status of a message's tool calls. * diff --git a/static/app/components/core/code/codeBlock.tsx b/static/app/components/core/code/codeBlock.tsx index dd807bd514f3..f9e4b6e2e617 100644 --- a/static/app/components/core/code/codeBlock.tsx +++ b/static/app/components/core/code/codeBlock.tsx @@ -5,13 +5,12 @@ import {IconCopy} from '@sentry/icons/copy'; import Prism from 'prismjs'; import {Button} from '@sentry/scraps/button'; +import {getPrismLanguage, loadPrismLanguage} from '@sentry/scraps/code/prism'; import {Container} from '@sentry/scraps/layout'; import {useTranslation} from '@sentry/scraps/translation/useTranslation'; import {darkTheme} from 'sentry/utils/theme/theme'; -import {getPrismLanguage, loadPrismLanguage} from './prism'; - interface CodeBlockProps { children: string; /** diff --git a/static/app/components/core/code/index.tsx b/static/app/components/core/code/index.tsx index c4f6afe2d8ca..cfa275c63479 100644 --- a/static/app/components/core/code/index.tsx +++ b/static/app/components/core/code/index.tsx @@ -1,3 +1,3 @@ export {InlineCode, inlineCodeStyles} from '@sentry/scraps/code/inlineCode'; export {CodeBlock} from './codeBlock'; -export {getPrismLanguage, loadPrismLanguage} from './prism'; +export {getPrismLanguage, loadPrismLanguage} from '@sentry/scraps/code/prism'; diff --git a/static/app/components/core/compactSelect/index.tsx b/static/app/components/core/compactSelect/index.tsx index 9325918549db..9902e29c85f4 100644 --- a/static/app/components/core/compactSelect/index.tsx +++ b/static/app/components/core/compactSelect/index.tsx @@ -13,7 +13,7 @@ export {CompositeSelect} from './composite'; export {ControlContext} from './controlContext'; -export {HighlightText} from './highlightText'; +export {HighlightText} from '@sentry/scraps/compactSelect/highlightText'; export {MenuComponents} from './menuComponents'; diff --git a/static/app/components/core/composer/composer.tsx b/static/app/components/core/composer/composer.tsx index 7894ed12c69c..cedff9e72577 100644 --- a/static/app/components/core/composer/composer.tsx +++ b/static/app/components/core/composer/composer.tsx @@ -5,13 +5,6 @@ import {mergeProps} from '@react-aria/utils'; import {VisuallyHidden} from '@react-aria/visually-hidden'; import type {QueryStatus} from '@tanstack/react-query'; -import {Container} from '@sentry/scraps/layout'; -import {useTranslation} from '@sentry/scraps/translation/useTranslation'; - -import {Overlay, PositionWrapper} from 'sentry/components/overlay'; -import {useOverlay} from 'sentry/utils/useOverlay'; -import {useStableMergeRef} from 'sentry/utils/useStableMergeRef'; - import { type EditorSelection, getDOMPoint, @@ -19,9 +12,24 @@ import { readEditorValue, setEditorSelection, writeEditorValue, -} from './dom'; -import {findActiveTrigger, getRequestKey, type ActiveTrigger} from './matching'; -import {type Mention, type ComposerValue, reconcileMentions} from './model'; +} from '@sentry/scraps/composer/dom'; +import { + findActiveTrigger, + getRequestKey, + type ActiveTrigger, +} from '@sentry/scraps/composer/matching'; +import { + type Mention, + type ComposerValue, + reconcileMentions, +} from '@sentry/scraps/composer/model'; +import {Container} from '@sentry/scraps/layout'; +import {useTranslation} from '@sentry/scraps/translation/useTranslation'; + +import {Overlay, PositionWrapper} from 'sentry/components/overlay'; +import {useOverlay} from 'sentry/utils/useOverlay'; +import {useStableMergeRef} from 'sentry/utils/useStableMergeRef'; + import {CaretAnchor, ComposerEditor, SuggestionListBox, SuggestionStatus} from './styles'; import type {ComposerProps} from './types'; import {useComposerSuggestions} from './useComposerSuggestions'; diff --git a/static/app/components/core/composer/index.tsx b/static/app/components/core/composer/index.tsx index 56edc9278dc3..d30e8aa75bf4 100644 --- a/static/app/components/core/composer/index.tsx +++ b/static/app/components/core/composer/index.tsx @@ -1,5 +1,5 @@ export {Composer} from './composer'; -export type {Mention, ComposerValue} from './model'; +export type {Mention, ComposerValue} from '@sentry/scraps/composer/model'; export type { ComposerActions, ComposerPlugin, diff --git a/static/app/components/core/composer/types.ts b/static/app/components/core/composer/types.ts index 078c042627c2..b0bc8a687540 100644 --- a/static/app/components/core/composer/types.ts +++ b/static/app/components/core/composer/types.ts @@ -1,9 +1,9 @@ import type React from 'react'; import type {AnyUseQueryOptions} from '@tanstack/react-query'; -import type {FormSize} from 'sentry/utils/theme'; +import type {ComposerValue} from '@sentry/scraps/composer/model'; -import type {ComposerValue} from './model'; +import type {FormSize} from 'sentry/utils/theme'; /** * Edits the value captured when a suggestion is selected. Call at most one action, diff --git a/static/app/components/core/composer/useComposerSuggestions.tsx b/static/app/components/core/composer/useComposerSuggestions.tsx index 296addec461b..adb0f84706da 100644 --- a/static/app/components/core/composer/useComposerSuggestions.tsx +++ b/static/app/components/core/composer/useComposerSuggestions.tsx @@ -4,7 +4,8 @@ import {Item} from '@react-stately/collections'; import {useListState} from '@react-stately/list'; import {skipToken, useQueries} from '@tanstack/react-query'; -import type {ActiveTrigger} from './matching'; +import type {ActiveTrigger} from '@sentry/scraps/composer/matching'; + import type {ComposerSource} from './types'; interface SuggestionListItem { diff --git a/static/app/components/core/dropdownMenu/context.tsx b/static/app/components/core/dropdownMenu/context.tsx index 2a9308067e92..b63cabdd9828 100644 --- a/static/app/components/core/dropdownMenu/context.tsx +++ b/static/app/components/core/dropdownMenu/context.tsx @@ -1,10 +1,11 @@ import {createContext} from 'react'; import type {TreeState} from '@react-stately/tree'; +import type {useSafetyTriangle} from '@sentry/scraps/dropdownMenu/useSafetyTriangle'; + import type {useOverlay} from 'sentry/utils/useOverlay'; import type {MenuItemProps} from './item'; -import type {useSafetyTriangle} from './useSafetyTriangle'; export type OverlayState = ReturnType['state']; diff --git a/static/app/components/core/dropdownMenu/index.tsx b/static/app/components/core/dropdownMenu/index.tsx index 498f80f17656..048bd7951895 100644 --- a/static/app/components/core/dropdownMenu/index.tsx +++ b/static/app/components/core/dropdownMenu/index.tsx @@ -4,4 +4,4 @@ export type {DropdownButtonProps} from './dropdownButton'; export {DropdownMenu} from './dropdownMenu'; export type {DropdownMenuProps, MenuItemProps} from './dropdownMenu'; -export {DropdownMenuFooter} from './footer'; +export {DropdownMenuFooter} from '@sentry/scraps/dropdownMenu/footer'; diff --git a/static/app/components/core/dropdownMenu/list.tsx b/static/app/components/core/dropdownMenu/list.tsx index 10fedf8982c0..dd95c6efdb3e 100644 --- a/static/app/components/core/dropdownMenu/list.tsx +++ b/static/app/components/core/dropdownMenu/list.tsx @@ -13,6 +13,7 @@ import {useTreeState} from '@react-stately/tree'; import type {Node} from '@react-types/shared'; import type {LocationDescriptor} from 'history'; +import {useSafetyTriangle} from '@sentry/scraps/dropdownMenu/useSafetyTriangle'; import {Container} from '@sentry/scraps/layout'; import {Overlay, PositionWrapper} from 'sentry/components/overlay'; @@ -24,7 +25,6 @@ import {DropdownMenuContext} from './context'; import type {MenuItemProps} from './item'; import {DropdownMenuItem} from './item'; import {DropdownMenuSection} from './section'; -import {useSafetyTriangle} from './useSafetyTriangle'; export interface DropdownMenuListProps extends diff --git a/static/app/components/core/entityHeader/entityHeader.tsx b/static/app/components/core/entityHeader/entityHeader.tsx index 03e6a09aec5d..936104f101ef 100644 --- a/static/app/components/core/entityHeader/entityHeader.tsx +++ b/static/app/components/core/entityHeader/entityHeader.tsx @@ -1,3 +1,4 @@ +import {METADATA_TEXT_HEIGHT, ROW_HEIGHT} from '@sentry/scraps/entityHeader/constants'; import {Container, Flex, Grid, Stack, useHasContainerQuery} from '@sentry/scraps/layout'; import {Text} from '@sentry/scraps/text'; @@ -14,7 +15,6 @@ import type {EntityHeaderStatProps} from './items/entityHeaderStat'; import {EntityHeaderStat} from './items/entityHeaderStat'; import type {EntityHeaderTitleProps} from './items/entityHeaderTitle'; import {EntityHeaderTitle} from './items/entityHeaderTitle'; -import {METADATA_TEXT_HEIGHT, ROW_HEIGHT} from './constants'; export interface EntityHeaderProps { /** diff --git a/static/app/components/core/form/field/baseField.tsx b/static/app/components/core/form/field/baseField.tsx index 9de4a12da674..c9a9415633b8 100644 --- a/static/app/components/core/form/field/baseField.tsx +++ b/static/app/components/core/form/field/baseField.tsx @@ -2,13 +2,13 @@ import {useEffect, useRef, type Ref} from 'react'; import {mergeRefs} from '@react-aria/utils'; import {useAutoSaveContext} from '@sentry/scraps/form/autoSaveContext'; +import {useFieldId, useHintTextId} from '@sentry/scraps/form/field/fieldIds'; import {useFieldContext} from '@sentry/scraps/form/formContext'; import {Checkmark, Spinner} from '@sentry/scraps/form/icons'; import {Flex} from '@sentry/scraps/layout'; import {useLocation} from 'sentry/utils/useLocation'; -import {useFieldId, useHintTextId} from './fieldIds'; import {FieldMeta} from './meta'; export type BaseFieldProps = { diff --git a/static/app/components/core/form/field/meta.tsx b/static/app/components/core/form/field/meta.tsx index 7cd27603cfd1..bf48807e0196 100644 --- a/static/app/components/core/form/field/meta.tsx +++ b/static/app/components/core/form/field/meta.tsx @@ -1,5 +1,6 @@ import {VisuallyHidden} from '@react-aria/visually-hidden'; +import {useFieldId, useHintTextId, useLabelId} from '@sentry/scraps/form/field/fieldIds'; import {useGroupContext} from '@sentry/scraps/form/field/groupContext'; import {useFieldContext} from '@sentry/scraps/form/formContext'; import {RequiredIndicator, Warning} from '@sentry/scraps/form/icons'; @@ -8,8 +9,6 @@ import {Container, Flex} from '@sentry/scraps/layout'; import {Text} from '@sentry/scraps/text'; import {Tooltip} from '@sentry/scraps/tooltip'; -import {useFieldId, useHintTextId, useLabelId} from './fieldIds'; - function HintText(props: {children: React.ReactNode}) { const id = useHintTextId(); diff --git a/static/app/components/core/form/field/radioField.tsx b/static/app/components/core/form/field/radioField.tsx index 5e83311b19bd..c2716ea8ab8d 100644 --- a/static/app/components/core/form/field/radioField.tsx +++ b/static/app/components/core/form/field/radioField.tsx @@ -1,14 +1,14 @@ import {createContext, useContext, useId} from 'react'; import {useAutoSaveContext} from '@sentry/scraps/form/autoSaveContext'; +import {useLabelId} from '@sentry/scraps/form/field/fieldIds'; +import {GroupProvider} from '@sentry/scraps/form/field/groupContext'; import {useFieldContext} from '@sentry/scraps/form/formContext'; import {Flex, Stack} from '@sentry/scraps/layout'; import {Radio} from '@sentry/scraps/radio'; import {Text} from '@sentry/scraps/text'; import {useAutoSaveIndicator} from './baseField'; -import {useLabelId} from './fieldIds'; -import {GroupProvider} from './groupContext'; import {FieldMeta} from './meta'; // Context for Radio.Group -> Radio.Item communication diff --git a/static/app/components/core/form/field/rangeField.tsx b/static/app/components/core/form/field/rangeField.tsx index a8117ac938c2..b258a4cef22d 100644 --- a/static/app/components/core/form/field/rangeField.tsx +++ b/static/app/components/core/form/field/rangeField.tsx @@ -2,11 +2,11 @@ import {Fragment} from 'react'; import type {DistributedOmit} from 'type-fest'; import {useAutoSaveContext} from '@sentry/scraps/form/autoSaveContext'; +import {useLabelId} from '@sentry/scraps/form/field/fieldIds'; import {Flex} from '@sentry/scraps/layout'; import {Slider, type SliderProps} from '@sentry/scraps/slider'; import {BaseField, type BaseFieldProps} from './baseField'; -import {useLabelId} from './fieldIds'; export function RangeField({ onChange, diff --git a/static/app/components/core/form/index.ts b/static/app/components/core/form/index.ts index a597bbac6721..5f5c991e125f 100644 --- a/static/app/components/core/form/index.ts +++ b/static/app/components/core/form/index.ts @@ -9,8 +9,8 @@ export { withForm, } from './scrapsForm'; export {AutoSaveForm} from './autoSaveForm'; -export {AutoSaveContextProvider} from './autoSaveContext'; +export {AutoSaveContextProvider} from '@sentry/scraps/form/autoSaveContext'; export {FieldGroup} from './layout/fieldGroup'; -export {FormSearch} from './FormSearch'; +export {FormSearch} from '@sentry/scraps/form/FormSearch'; // eslint-disable-next-line no-restricted-imports export {useStore} from '@tanstack/react-form'; diff --git a/static/app/components/core/form/scrapsForm.tsx b/static/app/components/core/form/scrapsForm.tsx index 29f1e9ebe62a..16a628613771 100644 --- a/static/app/components/core/form/scrapsForm.tsx +++ b/static/app/components/core/form/scrapsForm.tsx @@ -11,6 +11,13 @@ import {Button, type ButtonProps} from '@sentry/scraps/button'; import {BaseField} from '@sentry/scraps/form/field/baseField'; import {CheckboxField} from '@sentry/scraps/form/field/checkboxField'; import {FieldMeta} from '@sentry/scraps/form/field/meta'; +import { + FormElementContext, + fieldContext, + formContext, + useFormContext, + useIsInsideFormElement, +} from '@sentry/scraps/form/formContext'; import {FieldLayout} from '@sentry/scraps/form/layout'; import {FieldGroup} from '@sentry/scraps/form/layout/fieldGroup'; @@ -23,13 +30,6 @@ import {SelectAsyncField} from './field/selectAsyncField'; import {SelectField} from './field/selectField'; import {SwitchField} from './field/switchField'; import {TextAreaField} from './field/textAreaField'; -import { - FormElementContext, - fieldContext, - formContext, - useFormContext, - useIsInsideFormElement, -} from './formContext'; export const defaultFormOptions = formOptions({ onSubmitInvalid({ diff --git a/static/app/components/core/input/index.tsx b/static/app/components/core/input/index.tsx index a8e5b197769f..60c5a8a35ea2 100644 --- a/static/app/components/core/input/index.tsx +++ b/static/app/components/core/input/index.tsx @@ -5,4 +5,4 @@ export {NumberDragInput} from './numberDragInput'; export {NumberInput} from './numberInput'; /** @public */ export {OTPInput} from './otpInput'; -export {useAutosizeInput} from './useAutosizeInput'; +export {useAutosizeInput} from '@sentry/scraps/input/useAutosizeInput'; diff --git a/static/app/components/core/markdown/defaultComponents.tsx b/static/app/components/core/markdown/defaultComponents.tsx index 868280e95ee1..69ea1f21bc88 100644 --- a/static/app/components/core/markdown/defaultComponents.tsx +++ b/static/app/components/core/markdown/defaultComponents.tsx @@ -4,12 +4,11 @@ import styled from '@emotion/styled'; import {CodeBlock, InlineCode} from '@sentry/scraps/code'; import {Container, Flex, Stack} from '@sentry/scraps/layout'; import {ExternalLink, Link} from '@sentry/scraps/link'; +import {isSafeHref, isInternalHref} from '@sentry/scraps/markdown/marked'; import {Quote} from '@sentry/scraps/quote'; import {Separator} from '@sentry/scraps/separator'; import {Heading, Text} from '@sentry/scraps/text'; -import {isSafeHref, isInternalHref} from './marked'; - export function DefaultParagraph({children}: {children: ReactNode}) { return ( diff --git a/static/app/components/core/markdown/index.tsx b/static/app/components/core/markdown/index.tsx index 6e13d85e2d64..6c8c001bba8f 100644 --- a/static/app/components/core/markdown/index.tsx +++ b/static/app/components/core/markdown/index.tsx @@ -10,6 +10,14 @@ export { sanitizedMarked, singleLineRenderer, splitTags, -} from './marked'; -export type {ExtendedToken, MarkedToken, TagSegment, Token} from './marked'; -export {streamingAnimationStyles, useTextDecodeAnimation} from './useStreamingAnimation'; +} from '@sentry/scraps/markdown/marked'; +export type { + ExtendedToken, + MarkedToken, + TagSegment, + Token, +} from '@sentry/scraps/markdown/marked'; +export { + streamingAnimationStyles, + useTextDecodeAnimation, +} from '@sentry/scraps/markdown/useStreamingAnimation'; diff --git a/static/app/components/core/markdown/markdown.tsx b/static/app/components/core/markdown/markdown.tsx index 89d4999b1aa0..52e05a9a1ce3 100644 --- a/static/app/components/core/markdown/markdown.tsx +++ b/static/app/components/core/markdown/markdown.tsx @@ -3,11 +3,14 @@ import type {ComponentType, ReactNode} from 'react'; import {Global} from '@emotion/react'; import {Stack} from '@sentry/scraps/layout'; +import type {ExtendedToken} from '@sentry/scraps/markdown/marked'; +import {MarkedLexer} from '@sentry/scraps/markdown/marked'; +import { + streamingAnimationStyles, + useStreamingAnimation, +} from '@sentry/scraps/markdown/useStreamingAnimation'; -import type {ExtendedToken} from './marked'; -import {MarkedLexer} from './marked'; import {MarkdownStreamingContext, Token} from './token'; -import {streamingAnimationStyles, useStreamingAnimation} from './useStreamingAnimation'; type WithDefault = Props & {Default: ComponentType}; diff --git a/static/app/components/core/markdown/token.tsx b/static/app/components/core/markdown/token.tsx index bebeb7bd8440..30ea40413704 100644 --- a/static/app/components/core/markdown/token.tsx +++ b/static/app/components/core/markdown/token.tsx @@ -1,6 +1,8 @@ import {createContext, useContext, type ReactNode} from 'react'; import {Checkbox} from '@sentry/scraps/checkbox'; +import type {ExtendedToken, Token as TokenType} from '@sentry/scraps/markdown/marked'; +import {isSafeHref, isInternalHref, sanitizeHtml} from '@sentry/scraps/markdown/marked'; import {unreachable} from 'sentry/utils/unreachable'; @@ -32,8 +34,6 @@ import { DefaultUnorderedList, } from './defaultComponents'; import type {MarkdownComponents} from './markdown'; -import type {ExtendedToken, Token as TokenType} from './marked'; -import {isSafeHref, isInternalHref, sanitizeHtml} from './marked'; const TAG_START_RE = /\{%\s+[\w-]/; diff --git a/static/app/components/core/pictureInPicture/index.tsx b/static/app/components/core/pictureInPicture/index.tsx index bddc9b39134d..9f1bbb2801dd 100644 --- a/static/app/components/core/pictureInPicture/index.tsx +++ b/static/app/components/core/pictureInPicture/index.tsx @@ -1,2 +1,5 @@ -export {PictureInPictureProvider, usePictureInPicture} from './pictureInPicture'; +export { + PictureInPictureProvider, + usePictureInPicture, +} from '@sentry/scraps/pictureInPicture/pictureInPicture'; export {PictureInPicturePortal} from './pictureInPicturePortal'; diff --git a/static/app/components/core/scraps.spec.tsx b/static/app/components/core/scraps.spec.tsx index 1a6e1bdb1269..b8edf5070a11 100644 --- a/static/app/components/core/scraps.spec.tsx +++ b/static/app/components/core/scraps.spec.tsx @@ -1,8 +1,10 @@ import {render, screen} from 'sentry-test/reactTestingLibrary'; +import {Button, ButtonBar} from '@sentry/scraps/button'; import {InlineCode} from '@sentry/scraps/code'; import {Kbd} from '@sentry/scraps/hotkey'; import {Stack} from '@sentry/scraps/layout'; +import {Switch} from '@sentry/scraps/switch'; import {Prose, Text} from '@sentry/scraps/text'; it('composes package components through the remaining core barrels', () => { @@ -20,3 +22,15 @@ it('composes package components through the remaining core barrels', () => { expect(screen.getByText('Package code').tagName).toBe('CODE'); expect(screen.getByText('Package key').tagName).toBe('KBD'); }); + +it('composes moved controls with the remaining app button barrel', () => { + render( + + + + + ); + + expect(screen.getByRole('button', {name: 'Save'})).toBeEnabled(); + expect(screen.getByRole('checkbox', {name: 'Enabled'})).not.toBeChecked(); +}); diff --git a/static/app/components/core/select/index.tsx b/static/app/components/core/select/index.tsx index bd47e08c7a2a..4c809100b2a8 100644 --- a/static/app/components/core/select/index.tsx +++ b/static/app/components/core/select/index.tsx @@ -7,13 +7,13 @@ export { export {SelectOption} from './option'; export {CheckWrap} from './styles'; -export {components, createFilter} from './reactSelectWrapper'; +export {components, createFilter} from '@sentry/scraps/select/reactSelectWrapper'; export type { MultiValueProps, OptionsType, OptionTypeBase, SingleValueProps, ValueType, -} from './reactSelectWrapper'; +} from '@sentry/scraps/select/reactSelectWrapper'; export type {SelectValue} from './types'; diff --git a/static/app/components/core/select/option.tsx b/static/app/components/core/select/option.tsx index b62dee9021d9..887d3ff3296f 100644 --- a/static/app/components/core/select/option.tsx +++ b/static/app/components/core/select/option.tsx @@ -5,10 +5,10 @@ import {IconAdd} from '@sentry/icons/add'; import {IconCheckmark} from '@sentry/icons/checkmark'; import {MenuListItem} from '@sentry/scraps/menuListItem'; +import type {components as selectComponents} from '@sentry/scraps/select/reactSelectWrapper'; import {defined} from 'sentry/utils/defined'; -import type {components as selectComponents} from './reactSelectWrapper'; import {CheckWrap} from './styles'; type Props = React.ComponentProps; diff --git a/static/app/components/core/select/select.tsx b/static/app/components/core/select/select.tsx index ae34e197b7b4..dc1e6694b8f3 100644 --- a/static/app/components/core/select/select.tsx +++ b/static/app/components/core/select/select.tsx @@ -11,16 +11,6 @@ import {IconChevron} from '@sentry/icons/chevron'; import {IconClose} from '@sentry/icons/close'; import {Button} from '@sentry/scraps/button'; -import {useTranslation} from '@sentry/scraps/translation/useTranslation'; - -import {LoadingIndicator} from 'sentry/components/loadingIndicator'; -import type {Choices} from 'sentry/types/core'; -import {convertFromSelect2Choices} from 'sentry/utils/convertFromSelect2Choices'; -import {defined} from 'sentry/utils/defined'; -import {PanelProvider} from 'sentry/utils/panelProvider'; -import type {FormSize, Theme} from 'sentry/utils/theme'; - -import {SelectOption} from './option'; import type { GroupedOptionsType, OptionsType, @@ -28,13 +18,23 @@ import type { Props as ReactSelectProps, StylesConfig as ReactSelectStylesConfig, ValueType, -} from './reactSelectWrapper'; +} from '@sentry/scraps/select/reactSelectWrapper'; import { createFilter, mergeStyles, ReactSelect, components as selectComponents, -} from './reactSelectWrapper'; +} from '@sentry/scraps/select/reactSelectWrapper'; +import {useTranslation} from '@sentry/scraps/translation/useTranslation'; + +import {LoadingIndicator} from 'sentry/components/loadingIndicator'; +import type {Choices} from 'sentry/types/core'; +import {convertFromSelect2Choices} from 'sentry/utils/convertFromSelect2Choices'; +import {defined} from 'sentry/utils/defined'; +import {PanelProvider} from 'sentry/utils/panelProvider'; +import type {FormSize, Theme} from 'sentry/utils/theme'; + +import {SelectOption} from './option'; import type {SelectValue} from './types'; // We don't care about any options for the styles config diff --git a/static/app/components/core/slot/knownContexts.ts b/static/app/components/core/slot/knownContexts.ts deleted file mode 100644 index 668c8578aef7..000000000000 --- a/static/app/components/core/slot/knownContexts.ts +++ /dev/null @@ -1,4 +0,0 @@ -import {ContainerQueryContext} from '@sentry/scraps/layout/styles'; -import {SizeContext} from '@sentry/scraps/sizeContext'; - -export const KNOWN_BRIDGED_CONTEXTS = [SizeContext, ContainerQueryContext] as const; diff --git a/static/app/components/core/table/index.tsx b/static/app/components/core/table/index.tsx index 5cc9525114f3..35f1295e1a3d 100644 --- a/static/app/components/core/table/index.tsx +++ b/static/app/components/core/table/index.tsx @@ -14,4 +14,4 @@ export { fullWidthCellStyle, statusCellStyle, TableStatusCell, -} from './styles'; +} from '@sentry/scraps/table/styles'; diff --git a/static/app/components/core/table/table.tsx b/static/app/components/core/table/table.tsx index f671303c5170..260d4526836a 100644 --- a/static/app/components/core/table/table.tsx +++ b/static/app/components/core/table/table.tsx @@ -22,6 +22,16 @@ import type {LocationDescriptor} from 'history'; import {DragHandle} from '@sentry/scraps/dragHandle'; import {type Responsive, useResponsivePropResolver} from '@sentry/scraps/layout'; +import { + TableBody, + TableCell, + TableGrid, + TableHead, + TableHeadCell, + TableResizer, + TableRow, + TableStatusCell, +} from '@sentry/scraps/table/styles'; import { COLUMN_ALIGN_JUSTIFY, @@ -34,17 +44,6 @@ import {useColumnResize} from 'sentry/components/tables/useColumnResize'; import {useObservedColumnSize} from 'sentry/components/tables/useObservedColumnSize'; import {useStableMergeRef} from 'sentry/utils/useStableMergeRef'; -import { - TableBody, - TableCell, - TableGrid, - TableHead, - TableHeadCell, - TableResizer, - TableRow, - TableStatusCell, -} from './styles'; - // Auto layout width. export const COL_WIDTH_UNDEFINED = -1; diff --git a/static/app/components/core/tabs/tab.tsx b/static/app/components/core/tabs/tab.tsx index fe7b3c4da36d..85b255351983 100644 --- a/static/app/components/core/tabs/tab.tsx +++ b/static/app/components/core/tabs/tab.tsx @@ -7,12 +7,11 @@ import type {TabListState} from '@react-stately/tabs'; import type {DOMAttributes, Node, Orientation} from '@react-types/shared'; import {Link} from '@sentry/scraps/link'; +import {tabsShouldForwardProp} from '@sentry/scraps/tabs/utils'; import {Tooltip, type TooltipProps} from '@sentry/scraps/tooltip'; import type {Theme} from 'sentry/utils/theme'; -import {tabsShouldForwardProp} from './utils'; - interface BaseTabProps { children: React.ReactNode; disabled: boolean; diff --git a/static/app/components/core/tabs/tabList.tsx b/static/app/components/core/tabs/tabList.tsx index 7043883f4fc1..63c9a75fbc1e 100644 --- a/static/app/components/core/tabs/tabList.tsx +++ b/static/app/components/core/tabs/tabList.tsx @@ -22,6 +22,7 @@ import type {SelectOption} from '@sentry/scraps/compactSelect'; import {CompactSelect} from '@sentry/scraps/compactSelect'; import {Container, Flex} from '@sentry/scraps/layout'; import {OverlayTrigger} from '@sentry/scraps/overlayTrigger'; +import {tabsShouldForwardProp} from '@sentry/scraps/tabs/utils'; import {useTranslation} from '@sentry/scraps/translation/useTranslation'; import {useNavigate} from 'sentry/utils/useNavigate'; @@ -31,7 +32,6 @@ import {TabListItem} from './item'; import {Tab} from './tab'; import type {TabProps} from './tab'; import {TabsContext} from './tabs'; -import {tabsShouldForwardProp} from './utils'; const StyledTabListWrap = styled('ul', { shouldForwardProp: tabsShouldForwardProp, diff --git a/static/app/components/core/tabs/tabPanels.tsx b/static/app/components/core/tabs/tabPanels.tsx index 33ce347c9c6c..562f5e4a11db 100644 --- a/static/app/components/core/tabs/tabPanels.tsx +++ b/static/app/components/core/tabs/tabPanels.tsx @@ -7,9 +7,10 @@ import {ListCollection} from '@react-stately/list'; import type {TabListState} from '@react-stately/tabs'; import type {CollectionBase, Node, Orientation} from '@react-types/shared'; +import {tabsShouldForwardProp} from '@sentry/scraps/tabs/utils'; + import {TabPanelItem} from './item'; import {TabsContext} from './tabs'; -import {tabsShouldForwardProp} from './utils'; const collectionFactory = (nodes: Iterable>) => new ListCollection(nodes); diff --git a/static/app/components/core/tabs/tabs.tsx b/static/app/components/core/tabs/tabs.tsx index fada403ddd29..4ea276f3d1b6 100644 --- a/static/app/components/core/tabs/tabs.tsx +++ b/static/app/components/core/tabs/tabs.tsx @@ -5,8 +5,9 @@ import type {AriaTabListOptions} from '@react-aria/tabs'; import type {TabListState, TabListStateOptions} from '@react-stately/tabs'; import type {Orientation} from '@react-types/shared'; +import {tabsShouldForwardProp} from '@sentry/scraps/tabs/utils'; + import type {TabProps} from './tab'; -import {tabsShouldForwardProp} from './utils'; interface TabsProps extends diff --git a/static/app/components/core/toast/index.tsx b/static/app/components/core/toast/index.tsx index f10e862da6ea..d706d3d8d4bb 100644 --- a/static/app/components/core/toast/index.tsx +++ b/static/app/components/core/toast/index.tsx @@ -1,4 +1,4 @@ export {toast} from './toastApi'; -export {ToastProvider} from './toaster'; -export {DEFAULT_TOAST_DURATION} from './types'; -export type {ToastAction, ToastOptions, ToastVariant} from './types'; +export {ToastProvider} from '@sentry/scraps/toast/toaster'; +export {DEFAULT_TOAST_DURATION} from '@sentry/scraps/toast/types'; +export type {ToastAction, ToastOptions, ToastVariant} from '@sentry/scraps/toast/types'; diff --git a/static/app/components/core/toast/toast.tsx b/static/app/components/core/toast/toast.tsx index d19de2d5ce62..64a520b85cc0 100644 --- a/static/app/components/core/toast/toast.tsx +++ b/static/app/components/core/toast/toast.tsx @@ -6,14 +6,13 @@ import {IconWarning} from '@sentry/icons/warning'; import {Button} from '@sentry/scraps/button'; import {Container, Flex} from '@sentry/scraps/layout'; import {Text} from '@sentry/scraps/text'; +import type {ToastAction, ToastVariant} from '@sentry/scraps/toast/types'; import {useTranslation} from '@sentry/scraps/translation/useTranslation'; import {LoadingIndicator} from 'sentry/components/loadingIndicator'; import type {Theme} from 'sentry/utils/theme'; import {unreachable} from 'sentry/utils/unreachable'; -import type {ToastAction, ToastVariant} from './types'; - interface ToastProps { message: React.ReactNode; onDismiss: () => void; diff --git a/static/app/components/core/toast/toastApi.tsx b/static/app/components/core/toast/toastApi.tsx index 06e610e202c8..e3a8b45a8295 100644 --- a/static/app/components/core/toast/toastApi.tsx +++ b/static/app/components/core/toast/toastApi.tsx @@ -1,8 +1,9 @@ import type {ReactNode} from 'react'; import {toast as sonnerToast} from 'sonner'; +import type {ToastOptions, ToastVariant} from '@sentry/scraps/toast/types'; + import {Toast} from './toast'; -import type {ToastOptions, ToastVariant} from './types'; type ToastId = string | number; diff --git a/static/app/types/documentPictureInPicture.d.ts b/static/app/types/documentPictureInPicture.d.ts deleted file mode 100644 index bbeae6fb3a86..000000000000 --- a/static/app/types/documentPictureInPicture.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Type definitions for the Document Picture-in-Picture API -// https://developer.mozilla.org/en-US/docs/Web/API/DocumentPictureInPicture -// -// These are not (yet) part of the TypeScript DOM lib, so we declare the minimal -// surface we use here. - -interface DocumentPictureInPictureOptions { - /** - * Hides the "back to tab" button in the picture-in-picture window. - */ - disallowReturnToOpener?: boolean; - /** - * The initial height of the picture-in-picture window in pixels. - */ - height?: number; - /** - * Uses the default window placement instead of remembering the previous one. - */ - preferInitialWindowPlacement?: boolean; - /** - * The initial width of the picture-in-picture window in pixels. - */ - width?: number; -} - -interface DocumentPictureInPicture extends EventTarget { - readonly window: Window | null; - requestWindow(options?: DocumentPictureInPictureOptions): Promise; -} - -interface Window { - readonly documentPictureInPicture?: DocumentPictureInPicture; -} diff --git a/static/app/types/trustedTypes.d.ts b/static/app/types/trustedTypes.d.ts index e6f22e045f4c..bce5fffa62dc 100644 --- a/static/app/types/trustedTypes.d.ts +++ b/static/app/types/trustedTypes.d.ts @@ -1,10 +1,6 @@ // The DOM lib types these Trusted Types sinks as string-only, so these // overloads let the trusted values reach them without an assertion. -interface DOMParser { - parseFromString(string: TrustedHTML, type: DOMParserSupportedType): Document; -} - interface ServiceWorkerContainer { register( scriptURL: TrustedScriptURL, diff --git a/static/app/utils/theme/theme.tsx b/static/app/utils/theme/theme.tsx index f34d59fa46c7..3c89b380ec39 100644 --- a/static/app/utils/theme/theme.tsx +++ b/static/app/utils/theme/theme.tsx @@ -9,14 +9,13 @@ */ import type {CSSProperties} from 'react'; import {css} from '@emotion/react'; -import {spring, type Transition} from 'framer-motion'; import { darkTheme as baseDarkTheme, lightTheme as baseLightTheme, type Theme as ScrapsTheme, - type MotionDuration, - type MotionEasing, + generateMotion, + zIndex, } from '@sentry/scraps/theme'; import {color, typography} from '@sentry/scraps/tokens'; @@ -24,143 +23,8 @@ import {IS_ACCEPTANCE_TEST, NODE_ENV} from 'sentry/constants/env'; import {makeSwatch, type Swatch} from './swatch'; -type MotionDefinition = Record; - -const motionDurations: Record = { - fast: 120, - moderate: 160, - slow: 240, -}; - -const motionCurves: Record< - Exclude, - [number, number, number, number] -> = { - smooth: [0.72, 0, 0.16, 1], - snap: [0.8, -0.4, 0.5, 1], - enter: [0.24, 1, 0.32, 1], - exit: [0.64, 0, 0.8, 0], -}; - // Disable transitions in acceptance tests or node testing environments. const IS_ACCEPTANCE_OR_TESTING = IS_ACCEPTANCE_TEST || NODE_ENV === 'test'; -const EMPTY_TRANSITION: Transition = { - duration: 0, - staggerChildren: 0, - type: false, -}; - -const motionCurveWithDuration = ( - durations: Record, - easing: [number, number, number, number] -): [MotionDefinition, Record] => { - const motion: MotionDefinition = { - fast: `${durations.fast}ms cubic-bezier(${easing.join(', ')})`, - moderate: `${durations.moderate}ms cubic-bezier(${easing.join(', ')})`, - slow: `${durations.slow}ms cubic-bezier(${easing.join(', ')})`, - }; - - const framerMotion: Record = { - fast: IS_ACCEPTANCE_OR_TESTING - ? EMPTY_TRANSITION - : { - duration: durations.fast / 1000, - ease: easing, - }, - moderate: IS_ACCEPTANCE_OR_TESTING - ? EMPTY_TRANSITION - : { - duration: durations.moderate / 1000, - ease: easing, - }, - slow: IS_ACCEPTANCE_OR_TESTING - ? EMPTY_TRANSITION - : { - duration: durations.slow / 1000, - ease: easing, - }, - }; - - return [motion, framerMotion]; -}; - -const motionTransitions: Record<'spring', Record> = { - spring: { - fast: { - type: 'spring', - stiffness: 1400, - damping: 50, - }, - moderate: { - type: 'spring', - stiffness: 1000, - damping: 50, - }, - slow: { - type: 'spring', - stiffness: 600, - damping: 50, - }, - }, -}; - -const motionTransitionWithDuration = ( - transitionDefinitions: Record -): [MotionDefinition, Record] => { - const motion = { - fast: `${spring({ - keyframes: [0, 1], - ...transitionDefinitions.fast, - })}`, - moderate: `${spring({ - keyframes: [0, 1], - ...transitionDefinitions.moderate, - })}`, - slow: `${spring({ - keyframes: [0, 1], - ...transitionDefinitions.slow, - })}`, - }; - - return [motion, transitionDefinitions]; -}; - -function generateMotion() { - const [smoothMotion, smoothFramer] = motionCurveWithDuration( - motionDurations, - motionCurves.smooth - ); - const [snapMotion, snapFramer] = motionCurveWithDuration( - motionDurations, - motionCurves.snap - ); - const [enterMotion, enterFramer] = motionCurveWithDuration( - motionDurations, - motionCurves.enter - ); - const [exitMotion, exitFramer] = motionCurveWithDuration( - motionDurations, - motionCurves.exit - ); - const [springMotion, springFramer] = motionTransitionWithDuration( - motionTransitions.spring - ); - - return { - smooth: smoothMotion, - snap: snapMotion, - enter: enterMotion, - exit: exitMotion, - spring: springMotion, - framer: { - smooth: smoothFramer, - snap: snapFramer, - enter: enterFramer, - exit: exitFramer, - spring: springFramer, - }, - }; -} const generateThemeUtils = () => ({ // https://css-tricks.com/inclusively-hidden/ @@ -185,40 +49,9 @@ type Colors = typeof lightColors; * Values shared between light and dark theme */ const commonTheme = { - motion: generateMotion(), - - // Try to keep these ordered plz - zIndex: { - // Generic z-index when you hope your component is isolated and - // does not need to battle others for z-index priority - initial: 1, - truncationFullValue: 10, - - header: 1000, - dropdown: 1001, - stickyHeader: 1002, - - // dashboard widget builder backdrop sits behind the sidebar - // because it renders on the right next to the sidebar - // @TODO(design-engineering): resolve this inconsistency - widgetBuilderDrawer: 1016, - - sidebarPanel: 1019, - sidebar: 1020, - - // Sentry user feedback modal - sentryErrorEmbed: 1090, - - // If you change modal also update shared-components.less - // as the z-index for bootstrap modals lives there. - drawer: 9999, - modal: 10000, - toast: 10001, - - // tooltips and hovercards can be inside modals sometimes. - hovercard: 10002, - tooltip: 10003, - }, + motion: generateMotion(IS_ACCEPTANCE_OR_TESTING), + + zIndex, form: { md: { @@ -794,10 +627,9 @@ const darkColors: Colors = { }; const lightThemeDefinition = { - type: 'light' as 'light' | 'dark', // @TODO: color theme contains some colors (like chart color palette, diff, tag and level) - ...commonTheme, ...baseLightTheme, + ...commonTheme, focusRing: (baseShadow = `0 0 0 0 ${baseLightTheme.tokens.background.primary}`) => ({ outline: 'none', boxShadow: `${baseShadow}, 0 0 0 2px ${baseLightTheme.tokens.focus.default}`, @@ -833,10 +665,9 @@ export const lightTheme: SentryTheme = lightThemeDefinition; * @deprecated use useTheme hook instead of directly importing the theme. If you require a theme for your tests, use ThemeFixture. */ export const darkTheme: SentryTheme = { - type: 'dark', // @TODO: color theme contains some colors (like chart color palette, diff, tag and level) - ...commonTheme, ...baseDarkTheme, + ...commonTheme, // This sems to be a bug in the rule. baseShadow is optional. // eslint-disable-next-line @typescript-eslint/no-useless-default-assignment focusRing: (baseShadow = `0 0 0 0 ${baseDarkTheme.tokens.background.primary}`) => ({ diff --git a/static/packages/scraps/jest.config.mjs b/static/packages/scraps/jest.config.mjs index e35a5ee81c11..54b448e5ad01 100644 --- a/static/packages/scraps/jest.config.mjs +++ b/static/packages/scraps/jest.config.mjs @@ -1,11 +1,14 @@ const config = { testEnvironment: '/test/env/environment.mjs', setupFilesAfterEnv: ['/test/env/setup.ts'], - testMatch: ['/src/**/*.spec.tsx'], + testMatch: ['/src/**/*.spec.{ts,tsx}'], moduleNameMapper: { '^@sentry/scraps$': '/src/index.ts', '^@sentry/scraps/(.*)$': '/src/$1', }, + transformIgnorePatterns: [ + '/node_modules/.pnpm/(?!(marked|marked-highlight|color|color-convert|color-string|color-name)@)', + ], transform: { '^.+\\.[jt]sx?$': [ '@swc/jest', diff --git a/static/packages/scraps/package.json b/static/packages/scraps/package.json index 28a8022078ec..291a35552a7e 100644 --- a/static/packages/scraps/package.json +++ b/static/packages/scraps/package.json @@ -42,6 +42,54 @@ "./loader": { "types": "./dist/loader/index.d.ts", "import": "./dist/loader/index.js" + }, + "./backdrop": { + "types": "./dist/backdrop/index.d.ts", + "import": "./dist/backdrop/index.js" + }, + "./emptyState": { + "types": "./dist/emptyState/index.d.ts", + "import": "./dist/emptyState/index.js" + }, + "./revealOnHover": { + "types": "./dist/revealOnHover/index.d.ts", + "import": "./dist/revealOnHover/index.js" + }, + "./slider": { + "types": "./dist/slider/index.d.ts", + "import": "./dist/slider/index.js" + }, + "./slot": { + "types": "./dist/slot/index.d.ts", + "import": "./dist/slot/index.js" + }, + "./switch": { + "types": "./dist/switch/index.d.ts", + "import": "./dist/switch/index.js" + }, + "./translation": { + "types": "./dist/translation/index.d.ts", + "import": "./dist/translation/index.js" + }, + "./boundaryContext": { + "types": "./dist/boundaryContext.d.ts", + "import": "./dist/boundaryContext.js" + }, + "./datetime": { + "types": "./dist/datetime.d.ts", + "import": "./dist/datetime.js" + }, + "./sizeContext": { + "types": "./dist/sizeContext.d.ts", + "import": "./dist/sizeContext.js" + }, + "./renderToString": { + "types": "./dist/renderToString.d.ts", + "import": "./dist/renderToString.js" + }, + "./useScrollLock": { + "types": "./dist/useScrollLock.d.ts", + "import": "./dist/useScrollLock.js" } }, "publishConfig": { @@ -57,8 +105,27 @@ "dependencies": { "@emotion/is-prop-valid": "^1.4.0", "@emotion/styled": "^11.14.0", + "@react-aria/i18n": "3.13.1", + "@react-aria/slider": "3.9.1", "@react-aria/utils": "3.34.1", - "@types/react": "19.3.0" + "@react-aria/visually-hidden": "3.9.1", + "@react-stately/slider": "3.8.1", + "@sentry/icons": "workspace:*", + "@sentry/react": "11.6.0", + "@tanstack/react-form": "1.28.6", + "@types/react": "19.3.0", + "@types/react-select": "4.0.18", + "@types/trusted-types": "^2.0.7", + "color": "5.0.2", + "dompurify": "3.4.16", + "framer-motion": "12.38.0", + "marked": "18.0.2", + "marked-highlight": "2.2.4", + "platformicons": "^9.8.0", + "prismjs": "^1.30.0", + "react-select": "4.3.1", + "sonner": "2.0.8", + "type-fest": "^5.8.0" }, "devDependencies": { "@jest/environment": "30.5.1", @@ -70,8 +137,11 @@ "@testing-library/dom": "10.4.1", "@testing-library/jest-dom": "7.0.1", "@testing-library/react": "16.3.2", + "@testing-library/user-event": "14.6.7", "@types/jest": "30.0.0", "@types/node": "26.6.4", + "@types/prismjs": "^1.26.0", + "@types/react-dom": "19.3.0", "expect-type": "1.4.0", "jest": "30.5.1", "jsdom": "30.1.1", diff --git a/static/packages/scraps/rslib.config.mjs b/static/packages/scraps/rslib.config.mjs index 0c7301115f47..e21a937e0358 100644 --- a/static/packages/scraps/rslib.config.mjs +++ b/static/packages/scraps/rslib.config.mjs @@ -4,7 +4,7 @@ import {defineConfig} from '@rslib/core'; export default defineConfig({ lib: [{format: 'esm', bundle: false, dts: true, syntax: 'es2022'}], source: { - entry: {index: ['./src/**', '!src/**/*.spec.tsx']}, + entry: {index: ['./src/**', '!src/**/*.spec.{ts,tsx}']}, }, output: {target: 'web'}, plugins: [pluginReact({swcReactOptions: {importSource: '@emotion/react'}})], diff --git a/static/packages/scraps/scripts/verify.mjs b/static/packages/scraps/scripts/verify.mjs index ca25e192f4b7..5ea2e1d1ff42 100644 --- a/static/packages/scraps/scripts/verify.mjs +++ b/static/packages/scraps/scripts/verify.mjs @@ -36,7 +36,10 @@ try { const name = specifier.startsWith('@') ? specifier.split('/').slice(0, 2).join('/') : specifier.split('/')[0]; - if (name !== packageJson.name && !(name in dependencies)) { + const typesName = + '@types/' + (name.startsWith('@') ? name.slice(1).replace('/', '__') : name); + const declaredTypes = file.endsWith('.d.ts') && typesName in dependencies; + if (name !== packageJson.name && !(name in dependencies) && !declaredTypes) { throw new Error(`Undeclared dependency: ${specifier} in ${file}`); } } diff --git a/static/app/components/core/avatar/avatarComponentStyles.tsx b/static/packages/scraps/src/avatar/avatarComponentStyles.tsx similarity index 100% rename from static/app/components/core/avatar/avatarComponentStyles.tsx rename to static/packages/scraps/src/avatar/avatarComponentStyles.tsx diff --git a/static/app/components/core/avatar/letterAvatar/letterAvatar.spec.tsx b/static/packages/scraps/src/avatar/letterAvatar/letterAvatar.spec.tsx similarity index 94% rename from static/app/components/core/avatar/letterAvatar/letterAvatar.spec.tsx rename to static/packages/scraps/src/avatar/letterAvatar/letterAvatar.spec.tsx index 9b851338faa3..fb425c191723 100644 --- a/static/app/components/core/avatar/letterAvatar/letterAvatar.spec.tsx +++ b/static/packages/scraps/src/avatar/letterAvatar/letterAvatar.spec.tsx @@ -1,6 +1,6 @@ import type {Tagged} from 'type-fest'; -import {render, screen} from 'sentry-test/reactTestingLibrary'; +import {render, screen} from '../../../test/env'; import {LetterAvatar, type LetterAvatarProps} from './letterAvatar'; diff --git a/static/app/components/core/avatar/letterAvatar/letterAvatar.tsx b/static/packages/scraps/src/avatar/letterAvatar/letterAvatar.tsx similarity index 96% rename from static/app/components/core/avatar/letterAvatar/letterAvatar.tsx rename to static/packages/scraps/src/avatar/letterAvatar/letterAvatar.tsx index 3d63b28baf8d..bb879462c584 100644 --- a/static/app/components/core/avatar/letterAvatar/letterAvatar.tsx +++ b/static/packages/scraps/src/avatar/letterAvatar/letterAvatar.tsx @@ -4,7 +4,6 @@ import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import type {Tagged} from 'type-fest'; -// eslint-disable-next-line @sentry/no-relative-import-paths import {baseAvatarStyles, type BaseAvatarStyleProps} from '../avatarComponentStyles'; export interface LetterAvatarProps diff --git a/static/app/components/core/avatarButton/avatarImageAnalysis.spec.ts b/static/packages/scraps/src/avatarButton/avatarImageAnalysis.spec.ts similarity index 100% rename from static/app/components/core/avatarButton/avatarImageAnalysis.spec.ts rename to static/packages/scraps/src/avatarButton/avatarImageAnalysis.spec.ts diff --git a/static/app/components/core/avatarButton/avatarImageAnalysis.ts b/static/packages/scraps/src/avatarButton/avatarImageAnalysis.ts similarity index 100% rename from static/app/components/core/avatarButton/avatarImageAnalysis.ts rename to static/packages/scraps/src/avatarButton/avatarImageAnalysis.ts diff --git a/static/app/components/core/backdrop/backdrop.tsx b/static/packages/scraps/src/backdrop/backdrop.tsx similarity index 100% rename from static/app/components/core/backdrop/backdrop.tsx rename to static/packages/scraps/src/backdrop/backdrop.tsx diff --git a/static/app/components/core/backdrop/index.tsx b/static/packages/scraps/src/backdrop/index.tsx similarity index 100% rename from static/app/components/core/backdrop/index.tsx rename to static/packages/scraps/src/backdrop/index.tsx diff --git a/static/app/components/core/badge/projectsBadge.tsx b/static/packages/scraps/src/badge/projectsBadge.tsx similarity index 98% rename from static/app/components/core/badge/projectsBadge.tsx rename to static/packages/scraps/src/badge/projectsBadge.tsx index 273774bfe57a..df760c60092d 100644 --- a/static/app/components/core/badge/projectsBadge.tsx +++ b/static/packages/scraps/src/badge/projectsBadge.tsx @@ -3,7 +3,7 @@ import {IconAllProjects} from '@sentry/icons/allProjects'; import {IconMyProjects} from '@sentry/icons/myProjects'; import {PlatformIcon} from 'platformicons'; -import {Container, Stack} from '@sentry/scraps/layout'; +import {Container, Stack} from '../layout'; type ProjectsBadgeSize = 'md' | 'lg'; diff --git a/static/app/components/core/boundaryContext.tsx b/static/packages/scraps/src/boundaryContext.tsx similarity index 100% rename from static/app/components/core/boundaryContext.tsx rename to static/packages/scraps/src/boundaryContext.tsx diff --git a/static/app/components/core/breadcrumbList/breadcrumbDividerCombo.tsx b/static/packages/scraps/src/breadcrumbList/breadcrumbDividerCombo.tsx similarity index 93% rename from static/app/components/core/breadcrumbList/breadcrumbDividerCombo.tsx rename to static/packages/scraps/src/breadcrumbList/breadcrumbDividerCombo.tsx index 055448237410..41c8985080c2 100644 --- a/static/app/components/core/breadcrumbList/breadcrumbDividerCombo.tsx +++ b/static/packages/scraps/src/breadcrumbList/breadcrumbDividerCombo.tsx @@ -1,7 +1,7 @@ import {IconSlashForward} from '@sentry/icons/slashForward'; -import type {Responsive} from '@sentry/scraps/layout'; -import {Container, Flex} from '@sentry/scraps/layout'; +import type {Responsive} from '../layout'; +import {Container, Flex} from '../layout'; interface BreadcrumbDividerComboProps { children: React.ReactNode; diff --git a/static/app/components/core/breadcrumbList/items/breadcrumbLeadingSlot.tsx b/static/packages/scraps/src/breadcrumbList/items/breadcrumbLeadingSlot.tsx similarity index 94% rename from static/app/components/core/breadcrumbList/items/breadcrumbLeadingSlot.tsx rename to static/packages/scraps/src/breadcrumbList/items/breadcrumbLeadingSlot.tsx index 54651e9feb39..67c3f4d576e6 100644 --- a/static/app/components/core/breadcrumbList/items/breadcrumbLeadingSlot.tsx +++ b/static/packages/scraps/src/breadcrumbList/items/breadcrumbLeadingSlot.tsx @@ -1,4 +1,4 @@ -import {Stack} from '@sentry/scraps/layout'; +import {Stack} from '../../layout'; interface BreadcrumbLeadingSlotProps { children: React.ReactNode; diff --git a/static/app/components/core/button/buttonBar.tsx b/static/packages/scraps/src/button/buttonBar.tsx similarity index 97% rename from static/app/components/core/button/buttonBar.tsx rename to static/packages/scraps/src/button/buttonBar.tsx index 25be9093c117..a06a50578557 100644 --- a/static/app/components/core/button/buttonBar.tsx +++ b/static/packages/scraps/src/button/buttonBar.tsx @@ -1,8 +1,8 @@ import {css} from '@emotion/react'; import styled from '@emotion/styled'; -import {Grid, type GridProps} from '@sentry/scraps/layout'; -import {SizeProvider} from '@sentry/scraps/sizeContext'; +import {Grid, type GridProps} from '../layout'; +import {SizeProvider} from '../sizeContext'; export interface ButtonBarProps extends Omit { children: NonNullable; diff --git a/static/app/components/core/chat/assistantMessage.tsx b/static/packages/scraps/src/chat/assistantMessage.tsx similarity index 93% rename from static/app/components/core/chat/assistantMessage.tsx rename to static/packages/scraps/src/chat/assistantMessage.tsx index 55a5cd4fb3d4..a87096a0b269 100644 --- a/static/app/components/core/chat/assistantMessage.tsx +++ b/static/packages/scraps/src/chat/assistantMessage.tsx @@ -1,4 +1,4 @@ -import {Container} from '@sentry/scraps/layout'; +import {Container} from '../layout'; interface AssistantMessageProps extends React.HTMLAttributes { children: React.ReactNode; diff --git a/static/app/components/core/chat/messageRow.tsx b/static/packages/scraps/src/chat/messageRow.tsx similarity index 97% rename from static/app/components/core/chat/messageRow.tsx rename to static/packages/scraps/src/chat/messageRow.tsx index 3c69fd07697e..01da2399614a 100644 --- a/static/app/components/core/chat/messageRow.tsx +++ b/static/packages/scraps/src/chat/messageRow.tsx @@ -1,4 +1,4 @@ -import {Flex} from '@sentry/scraps/layout'; +import {Flex} from '../layout'; /** * How much vertical breathing room a row gets: diff --git a/static/app/components/core/chat/spinner.tsx b/static/packages/scraps/src/chat/spinner.tsx similarity index 100% rename from static/app/components/core/chat/spinner.tsx rename to static/packages/scraps/src/chat/spinner.tsx diff --git a/static/app/components/core/chat/userMessage.tsx b/static/packages/scraps/src/chat/userMessage.tsx similarity index 93% rename from static/app/components/core/chat/userMessage.tsx rename to static/packages/scraps/src/chat/userMessage.tsx index 6d404186b4aa..ebe7e50c4ec8 100644 --- a/static/app/components/core/chat/userMessage.tsx +++ b/static/packages/scraps/src/chat/userMessage.tsx @@ -1,7 +1,7 @@ import {css} from '@emotion/react'; -import type {CSS} from '@sentry/scraps/cssTypes'; -import {Container} from '@sentry/scraps/layout'; +import type {CSS} from '../cssTypes'; +import {Container} from '../layout'; interface UserMessageProps extends React.HTMLAttributes { children: React.ReactNode; diff --git a/static/app/components/core/code/prism.tsx b/static/packages/scraps/src/code/prism.tsx similarity index 100% rename from static/app/components/core/code/prism.tsx rename to static/packages/scraps/src/code/prism.tsx diff --git a/static/app/components/core/compactSelect/highlightText.spec.tsx b/static/packages/scraps/src/compactSelect/highlightText.spec.tsx similarity index 91% rename from static/app/components/core/compactSelect/highlightText.spec.tsx rename to static/packages/scraps/src/compactSelect/highlightText.spec.tsx index 7389c986019c..7add6dcea33a 100644 --- a/static/app/components/core/compactSelect/highlightText.spec.tsx +++ b/static/packages/scraps/src/compactSelect/highlightText.spec.tsx @@ -1,6 +1,6 @@ -import {render, screen} from 'sentry-test/reactTestingLibrary'; +import {render, screen} from '../../test/env'; -import {HighlightText} from '@sentry/scraps/compactSelect'; +import {HighlightText} from './highlightText'; describe('HighlightText', () => { it('highlights the matched substring', () => { diff --git a/static/app/components/core/compactSelect/highlightText.tsx b/static/packages/scraps/src/compactSelect/highlightText.tsx similarity index 96% rename from static/app/components/core/compactSelect/highlightText.tsx rename to static/packages/scraps/src/compactSelect/highlightText.tsx index 2714a20e09cc..7e7ead356333 100644 --- a/static/app/components/core/compactSelect/highlightText.tsx +++ b/static/packages/scraps/src/compactSelect/highlightText.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled'; -import {Text} from '@sentry/scraps/text'; +import {Text} from '../text'; type HighlightTextProps = { query: string; diff --git a/static/app/components/core/composer/dom.spec.ts b/static/packages/scraps/src/composer/dom.spec.ts similarity index 100% rename from static/app/components/core/composer/dom.spec.ts rename to static/packages/scraps/src/composer/dom.spec.ts diff --git a/static/app/components/core/composer/dom.ts b/static/packages/scraps/src/composer/dom.ts similarity index 100% rename from static/app/components/core/composer/dom.ts rename to static/packages/scraps/src/composer/dom.ts diff --git a/static/app/components/core/composer/matching.spec.ts b/static/packages/scraps/src/composer/matching.spec.ts similarity index 100% rename from static/app/components/core/composer/matching.spec.ts rename to static/packages/scraps/src/composer/matching.spec.ts diff --git a/static/app/components/core/composer/matching.ts b/static/packages/scraps/src/composer/matching.ts similarity index 100% rename from static/app/components/core/composer/matching.ts rename to static/packages/scraps/src/composer/matching.ts diff --git a/static/app/components/core/composer/model.spec.ts b/static/packages/scraps/src/composer/model.spec.ts similarity index 100% rename from static/app/components/core/composer/model.spec.ts rename to static/packages/scraps/src/composer/model.spec.ts diff --git a/static/app/components/core/composer/model.ts b/static/packages/scraps/src/composer/model.ts similarity index 100% rename from static/app/components/core/composer/model.ts rename to static/packages/scraps/src/composer/model.ts diff --git a/static/app/components/core/datetime.spec.tsx b/static/packages/scraps/src/datetime.spec.tsx similarity index 87% rename from static/app/components/core/datetime.spec.tsx rename to static/packages/scraps/src/datetime.spec.tsx index 965d4c424dee..5a9fa1816af4 100644 --- a/static/app/components/core/datetime.spec.tsx +++ b/static/packages/scraps/src/datetime.spec.tsx @@ -1,6 +1,6 @@ -import {render, screen} from 'sentry-test/reactTestingLibrary'; +import {render, screen} from '../test/env'; -import {DateTimeProvider, useClockDisplay, useTimezone} from '@sentry/scraps/datetime'; +import {DateTimeProvider, useClockDisplay, useTimezone} from './datetime'; describe('datetime', () => { function ShowDateTime() { diff --git a/static/app/components/core/datetime.tsx b/static/packages/scraps/src/datetime.tsx similarity index 100% rename from static/app/components/core/datetime.tsx rename to static/packages/scraps/src/datetime.tsx diff --git a/static/app/components/core/dropdownMenu/footer.tsx b/static/packages/scraps/src/dropdownMenu/footer.tsx similarity index 100% rename from static/app/components/core/dropdownMenu/footer.tsx rename to static/packages/scraps/src/dropdownMenu/footer.tsx diff --git a/static/app/components/core/dropdownMenu/useSafetyTriangle.ts b/static/packages/scraps/src/dropdownMenu/useSafetyTriangle.ts similarity index 100% rename from static/app/components/core/dropdownMenu/useSafetyTriangle.ts rename to static/packages/scraps/src/dropdownMenu/useSafetyTriangle.ts diff --git a/static/app/components/core/emptyState/emptyState.spec.tsx b/static/packages/scraps/src/emptyState/emptyState.spec.tsx similarity index 92% rename from static/app/components/core/emptyState/emptyState.spec.tsx rename to static/packages/scraps/src/emptyState/emptyState.spec.tsx index c7da69d13b71..6fbd7d6edb24 100644 --- a/static/app/components/core/emptyState/emptyState.spec.tsx +++ b/static/packages/scraps/src/emptyState/emptyState.spec.tsx @@ -1,6 +1,6 @@ -import {render, screen} from 'sentry-test/reactTestingLibrary'; +import {render, screen} from '../../test/env'; -import {EmptyState} from '@sentry/scraps/emptyState'; +import {EmptyState} from './index'; describe('EmptyState', () => { it('renders title and description', () => { diff --git a/static/app/components/core/emptyState/emptyState.tsx b/static/packages/scraps/src/emptyState/emptyState.tsx similarity index 92% rename from static/app/components/core/emptyState/emptyState.tsx rename to static/packages/scraps/src/emptyState/emptyState.tsx index ffdefd8bce8f..284291373c28 100644 --- a/static/app/components/core/emptyState/emptyState.tsx +++ b/static/packages/scraps/src/emptyState/emptyState.tsx @@ -1,5 +1,5 @@ -import {Flex, Stack, type FlexProps} from '@sentry/scraps/layout'; -import {Heading, Text, type TextProps} from '@sentry/scraps/text'; +import {Flex, Stack, type FlexProps} from '../layout'; +import {Heading, Text, type TextProps} from '../text'; interface EmptyStateProps extends Omit< FlexProps, diff --git a/static/app/components/core/emptyState/index.tsx b/static/packages/scraps/src/emptyState/index.tsx similarity index 100% rename from static/app/components/core/emptyState/index.tsx rename to static/packages/scraps/src/emptyState/index.tsx diff --git a/static/app/components/core/entityHeader/constants.tsx b/static/packages/scraps/src/entityHeader/constants.tsx similarity index 100% rename from static/app/components/core/entityHeader/constants.tsx rename to static/packages/scraps/src/entityHeader/constants.tsx diff --git a/static/app/components/core/form/FormSearch.tsx b/static/packages/scraps/src/form/FormSearch.tsx similarity index 100% rename from static/app/components/core/form/FormSearch.tsx rename to static/packages/scraps/src/form/FormSearch.tsx diff --git a/static/app/components/core/form/autoSaveContext.tsx b/static/packages/scraps/src/form/autoSaveContext.tsx similarity index 100% rename from static/app/components/core/form/autoSaveContext.tsx rename to static/packages/scraps/src/form/autoSaveContext.tsx diff --git a/static/app/components/core/form/field/fieldIds.tsx b/static/packages/scraps/src/form/field/fieldIds.tsx similarity index 82% rename from static/app/components/core/form/field/fieldIds.tsx rename to static/packages/scraps/src/form/field/fieldIds.tsx index c573f561f926..450e2dd4c640 100644 --- a/static/app/components/core/form/field/fieldIds.tsx +++ b/static/packages/scraps/src/form/field/fieldIds.tsx @@ -1,4 +1,4 @@ -import {useFieldContext} from '@sentry/scraps/form/formContext'; +import {useFieldContext} from '../formContext'; export const useFieldId = () => { const field = useFieldContext(); diff --git a/static/app/components/core/form/field/groupContext.tsx b/static/packages/scraps/src/form/field/groupContext.tsx similarity index 100% rename from static/app/components/core/form/field/groupContext.tsx rename to static/packages/scraps/src/form/field/groupContext.tsx diff --git a/static/app/components/core/form/formContext.ts b/static/packages/scraps/src/form/formContext.ts similarity index 100% rename from static/app/components/core/form/formContext.ts rename to static/packages/scraps/src/form/formContext.ts diff --git a/static/packages/scraps/src/index.ts b/static/packages/scraps/src/index.ts index f9b7a23e0655..04d258e1bcf9 100644 --- a/static/packages/scraps/src/index.ts +++ b/static/packages/scraps/src/index.ts @@ -7,3 +7,15 @@ export {InlineCode} from './code/inlineCode'; export {Kbd} from './hotkey/kbd'; export * from './theme'; export * from './tokens'; +export * from './backdrop'; +export * from './emptyState'; +export * from './revealOnHover'; +export * from './slider'; +export * from './slot'; +export * from './switch'; +export * from './translation'; +export * from './boundaryContext'; +export * from './datetime'; +export * from './sizeContext'; +export * from './renderToString'; +export * from './useScrollLock'; diff --git a/static/app/components/core/input/useAutosizeInput.tsx b/static/packages/scraps/src/input/useAutosizeInput.tsx similarity index 100% rename from static/app/components/core/input/useAutosizeInput.tsx rename to static/packages/scraps/src/input/useAutosizeInput.tsx diff --git a/static/app/components/core/markdown/extensions/index.ts b/static/packages/scraps/src/markdown/extensions/index.ts similarity index 77% rename from static/app/components/core/markdown/extensions/index.ts rename to static/packages/scraps/src/markdown/extensions/index.ts index c0331eb3529c..6453034ac168 100644 --- a/static/app/components/core/markdown/extensions/index.ts +++ b/static/packages/scraps/src/markdown/extensions/index.ts @@ -1,4 +1,4 @@ -import type {MarkedToken} from '@sentry/scraps/markdown/marked'; +import type {MarkedToken} from '../marked'; import type {TagToken} from './tag'; import {blockTagExtension, inlineTagExtension} from './tag'; diff --git a/static/app/components/core/markdown/extensions/tag.spec.ts b/static/packages/scraps/src/markdown/extensions/tag.spec.ts similarity index 100% rename from static/app/components/core/markdown/extensions/tag.spec.ts rename to static/packages/scraps/src/markdown/extensions/tag.spec.ts diff --git a/static/app/components/core/markdown/extensions/tag.ts b/static/packages/scraps/src/markdown/extensions/tag.ts similarity index 100% rename from static/app/components/core/markdown/extensions/tag.ts rename to static/packages/scraps/src/markdown/extensions/tag.ts diff --git a/static/app/components/core/markdown/marked.spec.tsx b/static/packages/scraps/src/markdown/marked.spec.tsx similarity index 99% rename from static/app/components/core/markdown/marked.spec.tsx rename to static/packages/scraps/src/markdown/marked.spec.tsx index 7f438925fea4..3490222265d2 100644 --- a/static/app/components/core/markdown/marked.spec.tsx +++ b/static/packages/scraps/src/markdown/marked.spec.tsx @@ -1,13 +1,14 @@ /* eslint no-script-url:0 */ -import {loadPrismLanguage} from '@sentry/scraps/code'; +import {loadPrismLanguage} from '../code/prism'; + import { asyncSanitizedMarked, markdownRendersVisibleContent, markdownToPlainText, sanitizedMarked, singleLineRenderer, -} from '@sentry/scraps/markdown'; +} from './marked'; jest.unmock('prismjs'); diff --git a/static/app/components/core/markdown/marked.tsx b/static/packages/scraps/src/markdown/marked.tsx similarity index 97% rename from static/app/components/core/markdown/marked.tsx rename to static/packages/scraps/src/markdown/marked.tsx index 3a5586d51d15..eb9fd065df45 100644 --- a/static/app/components/core/markdown/marked.tsx +++ b/static/packages/scraps/src/markdown/marked.tsx @@ -3,8 +3,9 @@ import type {MarkedExtension, MarkedToken, Token, Tokens} from 'marked'; // esli import {Lexer as MarkedLexer, Marked, marked} from 'marked'; // eslint-disable-line no-restricted-imports import {markedHighlight} from 'marked-highlight'; import Prism from 'prismjs'; +import type {TrustedHTML} from 'trusted-types/lib'; -import {loadPrismLanguage} from '@sentry/scraps/code/prism'; +import {loadPrismLanguage} from '../code/prism'; import type {TagToken} from './extensions/tag'; import {extensions} from './extensions'; @@ -346,3 +347,10 @@ function hasVisibleToken(token: Token): boolean { : false; } } + +// Trusted Types sinks accept trusted HTML in supporting browsers. +declare global { + interface DOMParser { + parseFromString(string: TrustedHTML, type: DOMParserSupportedType): Document; + } +} diff --git a/static/app/components/core/markdown/useStreamingAnimation.ts b/static/packages/scraps/src/markdown/useStreamingAnimation.ts similarity index 100% rename from static/app/components/core/markdown/useStreamingAnimation.ts rename to static/packages/scraps/src/markdown/useStreamingAnimation.ts diff --git a/static/app/components/core/pictureInPicture/pictureInPicture.spec.tsx b/static/packages/scraps/src/pictureInPicture/pictureInPicture.spec.tsx similarity index 97% rename from static/app/components/core/pictureInPicture/pictureInPicture.spec.tsx rename to static/packages/scraps/src/pictureInPicture/pictureInPicture.spec.tsx index bc113253e5a1..15ddf2821c4f 100644 --- a/static/app/components/core/pictureInPicture/pictureInPicture.spec.tsx +++ b/static/packages/scraps/src/pictureInPicture/pictureInPicture.spec.tsx @@ -1,9 +1,6 @@ -import {act, renderHook, waitFor} from 'sentry-test/reactTestingLibrary'; +import {act, renderHook, waitFor} from '../../test/env'; -import { - PictureInPictureProvider, - usePictureInPicture, -} from '@sentry/scraps/pictureInPicture'; +import {PictureInPictureProvider, usePictureInPicture} from './pictureInPicture'; type FakePipWindow = Window & { __listeners: Record void>>; diff --git a/static/app/components/core/pictureInPicture/pictureInPicture.tsx b/static/packages/scraps/src/pictureInPicture/pictureInPicture.tsx similarity index 90% rename from static/app/components/core/pictureInPicture/pictureInPicture.tsx rename to static/packages/scraps/src/pictureInPicture/pictureInPicture.tsx index a78fb007e0c1..9c4abf76a612 100644 --- a/static/app/components/core/pictureInPicture/pictureInPicture.tsx +++ b/static/packages/scraps/src/pictureInPicture/pictureInPicture.tsx @@ -224,3 +224,33 @@ export function usePictureInPicture(): PictureInPictureContextValue { return context; } + +interface DocumentPictureInPictureOptions { + /** + * Hides the "back to tab" button in the picture-in-picture window. + */ + disallowReturnToOpener?: boolean; + /** + * The initial height of the picture-in-picture window in pixels. + */ + height?: number; + /** + * Uses the default window placement instead of remembering the previous one. + */ + preferInitialWindowPlacement?: boolean; + /** + * The initial width of the picture-in-picture window in pixels. + */ + width?: number; +} + +interface DocumentPictureInPicture extends EventTarget { + requestWindow(options?: DocumentPictureInPictureOptions): Promise; + readonly window: Window | null; +} + +declare global { + interface Window { + readonly documentPictureInPicture?: DocumentPictureInPicture; + } +} diff --git a/static/app/components/core/renderToString.spec.tsx b/static/packages/scraps/src/renderToString.spec.tsx similarity index 63% rename from static/app/components/core/renderToString.spec.tsx rename to static/packages/scraps/src/renderToString.spec.tsx index 82251db584d2..69da20318c53 100644 --- a/static/app/components/core/renderToString.spec.tsx +++ b/static/packages/scraps/src/renderToString.spec.tsx @@ -1,8 +1,7 @@ -import {act, renderHook, renderHookWithProviders} from 'sentry-test/reactTestingLibrary'; - -import {Tag} from '@sentry/scraps/badge'; +import {act, renderHook} from '../test/env'; import {useRenderToString} from './renderToString'; +import {Text} from './text'; describe('renderToString', () => { it('should render a simple component to string', async () => { @@ -18,14 +17,18 @@ describe('renderToString', () => { }); it('should render a scraps component to string', async () => { function ScrapsComponent() { - return SuccessTag; + return ( + + SuccessText + + ); } - const {result} = renderHookWithProviders(useRenderToString); + const {result} = renderHook(useRenderToString); const string = await act(() => result.current()); - expect(string).toContain('data-test-id="tag-background"'); - expect(string).toContain('SuccessTag'); + expect(string).toContain('data-test-id="rendered-text"'); + expect(string).toContain('SuccessText'); }); }); diff --git a/static/app/components/core/renderToString.tsx b/static/packages/scraps/src/renderToString.tsx similarity index 100% rename from static/app/components/core/renderToString.tsx rename to static/packages/scraps/src/renderToString.tsx diff --git a/static/app/components/core/revealOnHover/index.tsx b/static/packages/scraps/src/revealOnHover/index.tsx similarity index 100% rename from static/app/components/core/revealOnHover/index.tsx rename to static/packages/scraps/src/revealOnHover/index.tsx diff --git a/static/app/components/core/revealOnHover/revealOnHover.spec.tsx b/static/packages/scraps/src/revealOnHover/revealOnHover.spec.tsx similarity index 87% rename from static/app/components/core/revealOnHover/revealOnHover.spec.tsx rename to static/packages/scraps/src/revealOnHover/revealOnHover.spec.tsx index a0083c28c73f..a25abaab246c 100644 --- a/static/app/components/core/revealOnHover/revealOnHover.spec.tsx +++ b/static/packages/scraps/src/revealOnHover/revealOnHover.spec.tsx @@ -1,7 +1,6 @@ -import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; +import {render, screen, userEvent} from '../../test/env'; -import {Button} from '@sentry/scraps/button'; -import {RevealOnHover} from '@sentry/scraps/revealOnHover'; +import {RevealOnHover} from './index'; describe('RevealOnHover', () => { it('renders children and action', () => { @@ -9,7 +8,7 @@ describe('RevealOnHover', () => { Label - + ); @@ -23,7 +22,7 @@ describe('RevealOnHover', () => { Label - + ); @@ -37,7 +36,7 @@ describe('RevealOnHover', () => { Label - + ); @@ -54,7 +53,7 @@ describe('RevealOnHover', () => { Label - + ); @@ -65,7 +64,7 @@ describe('RevealOnHover', () => { Label - + ); @@ -78,7 +77,7 @@ describe('RevealOnHover', () => { Label - + ); @@ -93,7 +92,7 @@ describe('RevealOnHover', () => { Label - + ); @@ -107,10 +106,10 @@ describe('RevealOnHover', () => { Label - + - + ); @@ -126,7 +125,7 @@ describe('RevealOnHover', () => { Label - + ); @@ -142,7 +141,7 @@ describe('RevealOnHover', () => {
Grid content - +
)} @@ -164,7 +163,7 @@ describe('RevealOnHover', () => {
Content - +
)} diff --git a/static/app/components/core/revealOnHover/revealOnHover.tsx b/static/packages/scraps/src/revealOnHover/revealOnHover.tsx similarity index 93% rename from static/app/components/core/revealOnHover/revealOnHover.tsx rename to static/packages/scraps/src/revealOnHover/revealOnHover.tsx index 4003792ad197..69efc77aa814 100644 --- a/static/app/components/core/revealOnHover/revealOnHover.tsx +++ b/static/packages/scraps/src/revealOnHover/revealOnHover.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; -import {Flex, type FlexProps} from '@sentry/scraps/layout'; -import type {ContainerElement} from '@sentry/scraps/layout/container'; +import {Flex, type FlexProps} from '../layout'; +import type {ContainerElement} from '../layout/container'; interface RevealOnHoverRenderProps { className: string; diff --git a/static/app/components/core/select/reactSelectWrapper.tsx b/static/packages/scraps/src/select/reactSelectWrapper.tsx similarity index 100% rename from static/app/components/core/select/reactSelectWrapper.tsx rename to static/packages/scraps/src/select/reactSelectWrapper.tsx diff --git a/static/app/components/core/sizeContext.tsx b/static/packages/scraps/src/sizeContext.tsx similarity index 100% rename from static/app/components/core/sizeContext.tsx rename to static/packages/scraps/src/sizeContext.tsx diff --git a/static/app/components/core/slider/index.tsx b/static/packages/scraps/src/slider/index.tsx similarity index 100% rename from static/app/components/core/slider/index.tsx rename to static/packages/scraps/src/slider/index.tsx diff --git a/static/app/components/core/slider/slider.spec.tsx b/static/packages/scraps/src/slider/slider.spec.tsx similarity index 98% rename from static/app/components/core/slider/slider.spec.tsx rename to static/packages/scraps/src/slider/slider.spec.tsx index 419494e7171e..2a3df05bffa0 100644 --- a/static/app/components/core/slider/slider.spec.tsx +++ b/static/packages/scraps/src/slider/slider.spec.tsx @@ -1,8 +1,8 @@ import {useState} from 'react'; -import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; +import {render, screen, userEvent} from '../../test/env'; -import {Slider} from '@sentry/scraps/slider'; +import {Slider} from './index'; function ControlledSlider( props: Omit, 'value' | 'onChange'> & { diff --git a/static/app/components/core/slider/slider.tsx b/static/packages/scraps/src/slider/slider.tsx similarity index 100% rename from static/app/components/core/slider/slider.tsx rename to static/packages/scraps/src/slider/slider.tsx diff --git a/static/app/components/core/slot/index.tsx b/static/packages/scraps/src/slot/index.tsx similarity index 100% rename from static/app/components/core/slot/index.tsx rename to static/packages/scraps/src/slot/index.tsx diff --git a/static/packages/scraps/src/slot/knownContexts.ts b/static/packages/scraps/src/slot/knownContexts.ts new file mode 100644 index 000000000000..3e6d2515311f --- /dev/null +++ b/static/packages/scraps/src/slot/knownContexts.ts @@ -0,0 +1,4 @@ +import {ContainerQueryContext} from '../layout/styles'; +import {SizeContext} from '../sizeContext'; + +export const KNOWN_BRIDGED_CONTEXTS = [SizeContext, ContainerQueryContext] as const; diff --git a/static/app/components/core/slot/slot.spec.tsx b/static/packages/scraps/src/slot/slot.spec.tsx similarity index 98% rename from static/app/components/core/slot/slot.spec.tsx rename to static/packages/scraps/src/slot/slot.spec.tsx index af7f61073054..3266ae1c04d5 100644 --- a/static/app/components/core/slot/slot.spec.tsx +++ b/static/packages/scraps/src/slot/slot.spec.tsx @@ -1,8 +1,7 @@ -import {render, screen} from 'sentry-test/reactTestingLibrary'; +import {render, screen} from '../../test/env'; +import {SizeProvider, useSizeContext} from '../sizeContext'; -import {SizeProvider, useSizeContext} from '@sentry/scraps/sizeContext'; - -import {slot} from './'; +import {slot} from './index'; describe('slot', () => { it('returns a module with Provider, Outlet, and Fallback sub-components', () => { diff --git a/static/app/components/core/slot/slot.tsx b/static/packages/scraps/src/slot/slot.tsx similarity index 100% rename from static/app/components/core/slot/slot.tsx rename to static/packages/scraps/src/slot/slot.tsx diff --git a/static/app/components/core/switch/index.tsx b/static/packages/scraps/src/switch/index.tsx similarity index 100% rename from static/app/components/core/switch/index.tsx rename to static/packages/scraps/src/switch/index.tsx diff --git a/static/app/components/core/switch/switch.spec.tsx b/static/packages/scraps/src/switch/switch.spec.tsx similarity index 84% rename from static/app/components/core/switch/switch.spec.tsx rename to static/packages/scraps/src/switch/switch.spec.tsx index 5349a756a5af..63f047402b1e 100644 --- a/static/app/components/core/switch/switch.spec.tsx +++ b/static/packages/scraps/src/switch/switch.spec.tsx @@ -1,6 +1,6 @@ -import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; +import {render, screen, userEvent} from '../../test/env'; -import {Switch} from '@sentry/scraps/switch'; +import {Switch} from './index'; describe('Switch', () => { it('disabled', () => { diff --git a/static/app/components/core/switch/switch.tsx b/static/packages/scraps/src/switch/switch.tsx similarity index 98% rename from static/app/components/core/switch/switch.tsx rename to static/packages/scraps/src/switch/switch.tsx index 231a73f078a0..9d145c797e17 100644 --- a/static/app/components/core/switch/switch.tsx +++ b/static/packages/scraps/src/switch/switch.tsx @@ -2,7 +2,7 @@ import styled from '@emotion/styled'; import {IconCheckmark} from '@sentry/icons/checkmark'; import {IconClose} from '@sentry/icons/close'; -import {Container, Flex} from '@sentry/scraps/layout'; +import {Container, Flex} from '../layout'; export interface SwitchProps extends Omit< React.InputHTMLAttributes, diff --git a/static/app/components/core/table/styles.tsx b/static/packages/scraps/src/table/styles.tsx similarity index 97% rename from static/app/components/core/table/styles.tsx rename to static/packages/scraps/src/table/styles.tsx index 1a9d5861dfdb..3b1cca5b9e3e 100644 --- a/static/app/components/core/table/styles.tsx +++ b/static/packages/scraps/src/table/styles.tsx @@ -2,7 +2,7 @@ import isPropValid from '@emotion/is-prop-valid'; import {css, type Theme} from '@emotion/react'; import styled from '@emotion/styled'; -import {Flex, type FlexProps} from '@sentry/scraps/layout'; +import {Flex, type FlexProps} from '../layout'; const TABLE_HEAD_ROW_HEIGHT = 45; diff --git a/static/app/components/core/tabs/utils.tsx b/static/packages/scraps/src/tabs/utils.tsx similarity index 100% rename from static/app/components/core/tabs/utils.tsx rename to static/packages/scraps/src/tabs/utils.tsx diff --git a/static/packages/scraps/src/theme/index.ts b/static/packages/scraps/src/theme/index.ts index 8ef3fb84ec6e..553d5a6d9dca 100644 --- a/static/packages/scraps/src/theme/index.ts +++ b/static/packages/scraps/src/theme/index.ts @@ -1,8 +1,30 @@ -import type {darkTheme} from './dark'; -import type {lightTheme} from './light'; +import {darkTheme as darkTokens} from './dark'; +import {lightTheme as lightTokens} from './light'; +import {generateMotion, zIndex} from './runtime'; -export {lightTheme} from './light'; -export {darkTheme} from './dark'; +export {generateMotion, zIndex} from './runtime'; + +const sharedTheme = {motion: generateMotion(), zIndex}; + +export const lightTheme = { + ...lightTokens, + ...sharedTheme, + type: 'light' as 'light' | 'dark', + focusRing: (baseShadow = `0 0 0 0 ${lightTokens.tokens.background.primary}`) => ({ + outline: 'none', + boxShadow: `${baseShadow}, 0 0 0 2px ${lightTokens.tokens.focus.default}`, + }), +}; + +export const darkTheme = { + ...darkTokens, + ...sharedTheme, + type: 'dark' as 'light' | 'dark', + focusRing: (baseShadow = `0 0 0 0 ${darkTokens.tokens.background.primary}`) => ({ + outline: 'none', + boxShadow: `${baseShadow}, 0 0 0 2px ${darkTokens.tokens.focus.default}`, + }), +}; export {baseTheme} from './base'; export type * from './types'; export type Theme = { diff --git a/static/packages/scraps/src/theme/runtime.spec.tsx b/static/packages/scraps/src/theme/runtime.spec.tsx new file mode 100644 index 000000000000..501068f5748d --- /dev/null +++ b/static/packages/scraps/src/theme/runtime.spec.tsx @@ -0,0 +1,21 @@ +import {darkTheme, generateMotion, lightTheme} from './index'; + +it.each([lightTheme, darkTheme])('provides focus styles for each theme', theme => { + expect(theme.focusRing()).toEqual({ + outline: 'none', + boxShadow: `0 0 0 0 ${theme.tokens.background.primary}, 0 0 0 2px ${theme.tokens.focus.default}`, + }); +}); + +it('can disable Framer transitions without changing the CSS motion tokens', () => { + const motion = generateMotion(); + const disabled = generateMotion(true); + + expect(disabled.smooth).toEqual(motion.smooth); + expect(motion.framer.smooth.fast.duration).toBe(0.12); + expect(disabled.framer.smooth.fast).toEqual({ + duration: 0, + staggerChildren: 0, + type: false, + }); +}); diff --git a/static/packages/scraps/src/theme/runtime.ts b/static/packages/scraps/src/theme/runtime.ts new file mode 100644 index 000000000000..ce2753970d39 --- /dev/null +++ b/static/packages/scraps/src/theme/runtime.ts @@ -0,0 +1,177 @@ +import {spring, type Transition} from 'framer-motion'; + +import type {MotionDuration, MotionEasing} from './types'; + +type MotionDefinition = Record; + +const motionDurations: Record = { + fast: 120, + moderate: 160, + slow: 240, +}; + +const motionCurves: Record< + Exclude, + [number, number, number, number] +> = { + smooth: [0.72, 0, 0.16, 1], + snap: [0.8, -0.4, 0.5, 1], + enter: [0.24, 1, 0.32, 1], + exit: [0.64, 0, 0.8, 0], +}; + +const EMPTY_TRANSITION: Transition = { + duration: 0, + staggerChildren: 0, + type: false, +}; + +const motionCurveWithDuration = ( + durations: Record, + easing: [number, number, number, number], + disableMotion: boolean +): [MotionDefinition, Record] => { + const motion: MotionDefinition = { + fast: `${durations.fast}ms cubic-bezier(${easing.join(', ')})`, + moderate: `${durations.moderate}ms cubic-bezier(${easing.join(', ')})`, + slow: `${durations.slow}ms cubic-bezier(${easing.join(', ')})`, + }; + + const framerMotion: Record = { + fast: disableMotion + ? EMPTY_TRANSITION + : { + duration: durations.fast / 1000, + ease: easing, + }, + moderate: disableMotion + ? EMPTY_TRANSITION + : { + duration: durations.moderate / 1000, + ease: easing, + }, + slow: disableMotion + ? EMPTY_TRANSITION + : { + duration: durations.slow / 1000, + ease: easing, + }, + }; + + return [motion, framerMotion]; +}; + +const motionTransitions: Record<'spring', Record> = { + spring: { + fast: { + type: 'spring', + stiffness: 1400, + damping: 50, + }, + moderate: { + type: 'spring', + stiffness: 1000, + damping: 50, + }, + slow: { + type: 'spring', + stiffness: 600, + damping: 50, + }, + }, +}; + +const motionTransitionWithDuration = ( + transitionDefinitions: Record +): [MotionDefinition, Record] => { + const motion = { + fast: `${spring({ + keyframes: [0, 1], + ...transitionDefinitions.fast, + })}`, + moderate: `${spring({ + keyframes: [0, 1], + ...transitionDefinitions.moderate, + })}`, + slow: `${spring({ + keyframes: [0, 1], + ...transitionDefinitions.slow, + })}`, + }; + + return [motion, transitionDefinitions]; +}; + +export function generateMotion(disableMotion = false) { + const [smoothMotion, smoothFramer] = motionCurveWithDuration( + motionDurations, + motionCurves.smooth, + disableMotion + ); + const [snapMotion, snapFramer] = motionCurveWithDuration( + motionDurations, + motionCurves.snap, + disableMotion + ); + const [enterMotion, enterFramer] = motionCurveWithDuration( + motionDurations, + motionCurves.enter, + disableMotion + ); + const [exitMotion, exitFramer] = motionCurveWithDuration( + motionDurations, + motionCurves.exit, + disableMotion + ); + const [springMotion, springFramer] = motionTransitionWithDuration( + motionTransitions.spring + ); + + return { + smooth: smoothMotion, + snap: snapMotion, + enter: enterMotion, + exit: exitMotion, + spring: springMotion, + framer: { + smooth: smoothFramer, + snap: snapFramer, + enter: enterFramer, + exit: exitFramer, + spring: springFramer, + }, + }; +} + +// Try to keep these ordered plz +export const zIndex = { + // Generic z-index when you hope your component is isolated and + // does not need to battle others for z-index priority + initial: 1, + truncationFullValue: 10, + + header: 1000, + dropdown: 1001, + stickyHeader: 1002, + + // dashboard widget builder backdrop sits behind the sidebar + // because it renders on the right next to the sidebar + // @TODO(design-engineering): resolve this inconsistency + widgetBuilderDrawer: 1016, + + sidebarPanel: 1019, + sidebar: 1020, + + // Sentry user feedback modal + sentryErrorEmbed: 1090, + + // If you change modal also update shared-components.less + // as the z-index for bootstrap modals lives there. + drawer: 9999, + modal: 10000, + toast: 10001, + + // tooltips and hovercards can be inside modals sometimes. + hovercard: 10002, + tooltip: 10003, +} as const; diff --git a/static/app/components/core/toast/toaster.tsx b/static/packages/scraps/src/toast/toaster.tsx similarity index 93% rename from static/app/components/core/toast/toaster.tsx rename to static/packages/scraps/src/toast/toaster.tsx index 444959d7058a..acfbfc21357d 100644 --- a/static/app/components/core/toast/toaster.tsx +++ b/static/packages/scraps/src/toast/toaster.tsx @@ -3,7 +3,7 @@ import {Fragment} from 'react'; import styled from '@emotion/styled'; import {Toaster} from 'sonner'; -import {useTranslation} from '@sentry/scraps/translation/useTranslation'; +import {useTranslation} from '../translation/useTranslation'; import {DEFAULT_TOAST_DURATION} from './types'; diff --git a/static/app/components/core/toast/types.tsx b/static/packages/scraps/src/toast/types.tsx similarity index 100% rename from static/app/components/core/toast/types.tsx rename to static/packages/scraps/src/toast/types.tsx diff --git a/static/app/components/core/translation/index.ts b/static/packages/scraps/src/translation/index.ts similarity index 80% rename from static/app/components/core/translation/index.ts rename to static/packages/scraps/src/translation/index.ts index 0af531a9465a..9d49ffa103f4 100644 --- a/static/app/components/core/translation/index.ts +++ b/static/packages/scraps/src/translation/index.ts @@ -4,3 +4,5 @@ import {TranslationContext} from './translationContext'; export type {TranslationContextValue} from './translationContext'; export const TranslationContextProvider = TranslationContext.Provider; + +export {useTranslation} from './useTranslation'; diff --git a/static/app/components/core/translation/translationContext.tsx b/static/packages/scraps/src/translation/translationContext.tsx similarity index 100% rename from static/app/components/core/translation/translationContext.tsx rename to static/packages/scraps/src/translation/translationContext.tsx diff --git a/static/app/components/core/translation/useTranslation.ts b/static/packages/scraps/src/translation/useTranslation.ts similarity index 100% rename from static/app/components/core/translation/useTranslation.ts rename to static/packages/scraps/src/translation/useTranslation.ts diff --git a/static/app/components/core/useScrollLock.spec.tsx b/static/packages/scraps/src/useScrollLock.spec.tsx similarity index 92% rename from static/app/components/core/useScrollLock.spec.tsx rename to static/packages/scraps/src/useScrollLock.spec.tsx index ce2432e7ca4e..d96eb3a2bc49 100644 --- a/static/app/components/core/useScrollLock.spec.tsx +++ b/static/packages/scraps/src/useScrollLock.spec.tsx @@ -1,4 +1,4 @@ -import {renderHook} from 'sentry-test/reactTestingLibrary'; +import {renderHook} from '../test/env'; import {useScrollLock} from './useScrollLock'; @@ -7,14 +7,20 @@ describe('useScrollLock', () => { beforeEach(() => { jest.useFakeTimers(); + jest.spyOn(window, 'scrollTo').mockImplementation(() => {}); container = document.createElement('div'); container.style.overflow = 'auto'; document.body.appendChild(container); }); afterEach(() => { - jest.useRealTimers(); - document.body.removeChild(container); + try { + jest.runOnlyPendingTimers(); + } finally { + jest.useRealTimers(); + document.body.removeChild(container); + jest.restoreAllMocks(); + } }); it('locks scroll when acquired', () => { @@ -193,12 +199,12 @@ describe('useScrollLock', () => { result.current.release(); - expect(document.body).toHaveStyle({position: ''}); - expect(document.body).toHaveStyle({top: ''}); - expect(document.body).toHaveStyle({left: ''}); - expect(document.body).toHaveStyle({right: ''}); - expect(document.body).toHaveStyle({width: ''}); - expect(document.body).toHaveStyle({paddingRight: ''}); + expect(document.body.style.getPropertyValue('position')).toBe(''); + expect(document.body.style.getPropertyValue('top')).toBe(''); + expect(document.body.style.getPropertyValue('left')).toBe(''); + expect(document.body.style.getPropertyValue('right')).toBe(''); + expect(document.body.style.getPropertyValue('width')).toBe(''); + expect(document.body.style.getPropertyValue('padding-right')).toBe(''); jest.runAllTimers(); expect(scrollToSpy).toHaveBeenCalledWith(scrollX, scrollY); diff --git a/static/app/components/core/useScrollLock.tsx b/static/packages/scraps/src/useScrollLock.tsx similarity index 100% rename from static/app/components/core/useScrollLock.tsx rename to static/packages/scraps/src/useScrollLock.tsx diff --git a/static/packages/scraps/test/env/index.tsx b/static/packages/scraps/test/env/index.tsx index b5d9de71996b..0a08e50b52c3 100644 --- a/static/packages/scraps/test/env/index.tsx +++ b/static/packages/scraps/test/env/index.tsx @@ -10,7 +10,9 @@ import { import {lightTheme} from '@sentry/scraps/theme'; -export {act, screen} from '@testing-library/react'; +export {default as userEvent} from '@testing-library/user-event'; + +export {act, screen, waitFor} from '@testing-library/react'; function ThemeWrapper({children}: {children: ReactNode}) { return {children}; diff --git a/static/packages/scraps/test/env/setup.ts b/static/packages/scraps/test/env/setup.ts index 10c80a2846e3..c39752cc67be 100644 --- a/static/packages/scraps/test/env/setup.ts +++ b/static/packages/scraps/test/env/setup.ts @@ -3,3 +3,10 @@ import '@testing-library/jest-dom'; import {configure} from '@testing-library/react'; configure({testIdAttribute: 'data-test-id'}); + +// jsdom does not implement ResizeObserver. Layout tests can supply measured sizes. +globalThis.ResizeObserver = class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} +}; diff --git a/static/packages/scraps/tsconfig.json b/static/packages/scraps/tsconfig.json index c1b683e01508..7c4762850a1e 100644 --- a/static/packages/scraps/tsconfig.json +++ b/static/packages/scraps/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../../tsconfig.base.json", "compilerOptions": { - "lib": ["ES2022", "DOM", "DOM.Iterable"], + "lib": ["ES2023", "DOM", "DOM.Iterable"], "module": "ESNext", "jsx": "react-jsx", "jsxImportSource": "@emotion/react", @@ -9,9 +9,16 @@ "emitDeclarationOnly": true, "rootDir": "src", "outDir": "dist", - "types": [], - "paths": {"@sentry/scraps/*": ["./src/*"]} + "types": ["node"], + "paths": { + "@sentry/scraps/*": ["./src/*"] + } }, "include": ["src"], - "exclude": ["src/**/*.spec.tsx"] + "exclude": ["src/**/*.spec.ts", "src/**/*.spec.tsx"], + "references": [ + { + "path": "../icons" + } + ] } diff --git a/static/packages/scraps/tsconfig.test.json b/static/packages/scraps/tsconfig.test.json index 78f6dda319c8..a5dbf741cc98 100644 --- a/static/packages/scraps/tsconfig.test.json +++ b/static/packages/scraps/tsconfig.test.json @@ -9,5 +9,10 @@ "types": ["jest", "node", "@testing-library/jest-dom"] }, "include": ["src", "test"], - "exclude": [] + "exclude": [], + "references": [ + { + "path": "../icons" + } + ] } From 214a16f2ba49fb5da9551fc2de3b2b923aa4947f Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 15 Oct 2026 11:05:55 +0200 Subject: [PATCH 02/11] fix(scraps): Remove unused dependency declarations --- package.json | 1 - pnpm-lock.yaml | 9 --------- static/app/package.json | 2 -- 3 files changed, 12 deletions(-) diff --git a/package.json b/package.json index 5f0fa1a1b575..c713dd2c2d7b 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,6 @@ "@types/jest": "30.0.0", "@types/lodash": "^4.17.24", "@types/react": "19.3.0", - "@types/react-dom": "19.3.0", "@types/trusted-types": "^2.0.7", "compression-webpack-plugin": "12.0.0", "core-js": "3.45.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e08b5b01780a..8e874824e970 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -296,9 +296,6 @@ importers: '@types/react': specifier: 19.3.0 version: 19.3.0 - '@types/react-dom': - specifier: 19.3.0 - version: 19.3.0(@types/react@19.3.0) '@types/trusted-types': specifier: ^2.0.7 version: 2.0.7 @@ -872,12 +869,6 @@ importers: lodash: specifier: ^4.18.1 version: 4.18.1 - marked: - specifier: 18.0.2 - version: 18.0.2 - marked-highlight: - specifier: 2.2.4 - version: 2.2.4(marked@18.0.2) mobx: specifier: 6.13.7 version: 6.13.7 diff --git a/static/app/package.json b/static/app/package.json index 74fc35c01fc4..a9b6239f130b 100644 --- a/static/app/package.json +++ b/static/app/package.json @@ -98,8 +98,6 @@ "js-cookie": "3.0.7", "jsonrepair": "^3.14.0", "lodash": "^4.18.1", - "marked": "18.0.2", - "marked-highlight": "2.2.4", "mobx": "6.13.7", "mobx-react-lite": "4.1.0", "moment": "2.31.0", From a8b02b0b176a7ddb629a9227498c743f0c48051c Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 15 Oct 2026 11:53:16 +0200 Subject: [PATCH 03/11] undo partial moves --- .github/workflows/frontend-snapshots.yml | 4 +- oxlint.config.ts | 2 +- pnpm-lock.yaml | 45 +- static/app/components/core/avatar/avatar.tsx | 4 +- .../core/avatar/imageAvatar/imageAvatar.tsx | 7 +- static/app/components/core/avatar/index.tsx | 2 +- .../app/components/core/avatar/useAvatar.ts | 3 +- .../core/avatarButton/useAvatarColors.ts | 3 +- static/app/components/core/badge/index.tsx | 2 +- .../core/breadcrumbList/breadcrumbList.tsx | 2 +- .../items/breadcrumbItemLink.tsx | 3 +- .../items/breadcrumbItemPageTitle.tsx | 3 +- .../items/breadcrumbItemPageTitleEditable.tsx | 3 +- .../items/breadcrumbItemSelect.tsx | 2 +- static/app/components/core/button/index.tsx | 2 +- static/app/components/core/chat/index.tsx | 8 +- .../core/chat/toolCallIndicator.tsx | 3 +- static/app/components/core/code/codeBlock.tsx | 3 +- static/app/components/core/code/index.tsx | 2 +- .../components/core/compactSelect/index.tsx | 2 +- .../app/components/core/composer/composer.tsx | 28 +- static/app/components/core/composer/index.tsx | 2 +- static/app/components/core/composer/types.ts | 4 +- .../core/composer/useComposerSuggestions.tsx | 3 +- .../components/core/dropdownMenu/context.tsx | 3 +- .../components/core/dropdownMenu/index.tsx | 2 +- .../app/components/core/dropdownMenu/list.tsx | 2 +- .../core/entityHeader/entityHeader.tsx | 2 +- .../components/core/form/field/baseField.tsx | 2 +- .../app/components/core/form/field/meta.tsx | 3 +- .../components/core/form/field/radioField.tsx | 4 +- .../components/core/form/field/rangeField.tsx | 2 +- static/app/components/core/form/index.ts | 4 +- .../app/components/core/form/scrapsForm.tsx | 14 +- static/app/components/core/input/index.tsx | 2 +- .../core/markdown/defaultComponents.tsx | 3 +- static/app/components/core/markdown/index.tsx | 14 +- .../app/components/core/markdown/markdown.tsx | 9 +- static/app/components/core/markdown/token.tsx | 4 +- .../core/pictureInPicture/index.tsx | 5 +- static/app/components/core/select/index.tsx | 4 +- static/app/components/core/select/option.tsx | 2 +- static/app/components/core/select/select.tsx | 24 +- static/app/components/core/table/index.tsx | 2 +- static/app/components/core/table/table.tsx | 21 +- static/app/components/core/tabs/tab.tsx | 3 +- static/app/components/core/tabs/tabList.tsx | 2 +- static/app/components/core/tabs/tabPanels.tsx | 3 +- static/app/components/core/tabs/tabs.tsx | 3 +- static/app/components/core/toast/index.tsx | 6 +- static/app/components/core/toast/toast.tsx | 3 +- static/app/components/core/toast/toastApi.tsx | 3 +- static/app/package.json | 2 + static/app/types/trustedTypes.d.ts | 4 + static/packages/scraps/jest.config.mjs | 5 +- static/packages/scraps/package.json | 15 +- static/packages/scraps/rslib.config.mjs | 2 +- static/packages/scraps/scripts/verify.mjs | 5 +- .../src/avatar/avatarComponentStyles.tsx | 17 - .../avatar/letterAvatar/letterAvatar.spec.tsx | 42 -- .../src/avatar/letterAvatar/letterAvatar.tsx | 52 -- .../avatarButton/avatarImageAnalysis.spec.ts | 124 ----- .../src/avatarButton/avatarImageAnalysis.ts | 289 ---------- .../scraps/src/badge/projectsBadge.tsx | 134 ----- .../breadcrumbList/breadcrumbDividerCombo.tsx | 37 -- .../items/breadcrumbLeadingSlot.tsx | 28 - .../packages/scraps/src/button/buttonBar.tsx | 150 ----- .../scraps/src/chat/assistantMessage.tsx | 21 - .../packages/scraps/src/chat/messageRow.tsx | 55 -- static/packages/scraps/src/chat/spinner.tsx | 46 -- .../packages/scraps/src/chat/userMessage.tsx | 56 -- static/packages/scraps/src/code/prism.tsx | 164 ------ .../src/compactSelect/highlightText.spec.tsx | 38 -- .../src/compactSelect/highlightText.tsx | 42 -- .../packages/scraps/src/composer/dom.spec.ts | 27 - static/packages/scraps/src/composer/dom.ts | 201 ------- .../scraps/src/composer/matching.spec.ts | 26 - .../packages/scraps/src/composer/matching.ts | 74 --- .../scraps/src/composer/model.spec.ts | 21 - static/packages/scraps/src/composer/model.ts | 69 --- .../scraps/src/dropdownMenu/footer.tsx | 13 - .../src/dropdownMenu/useSafetyTriangle.ts | 109 ---- .../scraps/src/entityHeader/constants.tsx | 5 - .../packages/scraps/src/form/FormSearch.tsx | 22 - .../scraps/src/form/autoSaveContext.tsx | 34 -- .../scraps/src/form/field/fieldIds.tsx | 19 - .../scraps/src/form/field/groupContext.tsx | 11 - .../packages/scraps/src/form/formContext.ts | 21 - .../scraps/src/input/useAutosizeInput.tsx | 111 ---- .../scraps/src/markdown/extensions/index.ts | 7 - .../src/markdown/extensions/tag.spec.ts | 345 ------------ .../scraps/src/markdown/extensions/tag.ts | 161 ------ .../scraps/src/markdown/marked.spec.tsx | 287 ---------- .../packages/scraps/src/markdown/marked.tsx | 356 ------------ .../src/markdown/useStreamingAnimation.ts | 525 ------------------ .../pictureInPicture.spec.tsx | 230 -------- .../src/pictureInPicture/pictureInPicture.tsx | 256 --------- .../scraps/src/select/reactSelectWrapper.tsx | 39 -- static/packages/scraps/src/table/styles.tsx | 113 ---- static/packages/scraps/src/tabs/utils.tsx | 4 - static/packages/scraps/src/toast/toaster.tsx | 43 -- static/packages/scraps/src/toast/types.tsx | 21 - static/packages/scraps/test/env/index.tsx | 2 +- static/packages/scraps/tsconfig.json | 2 +- 104 files changed, 133 insertions(+), 4642 deletions(-) delete mode 100644 static/packages/scraps/src/avatar/avatarComponentStyles.tsx delete mode 100644 static/packages/scraps/src/avatar/letterAvatar/letterAvatar.spec.tsx delete mode 100644 static/packages/scraps/src/avatar/letterAvatar/letterAvatar.tsx delete mode 100644 static/packages/scraps/src/avatarButton/avatarImageAnalysis.spec.ts delete mode 100644 static/packages/scraps/src/avatarButton/avatarImageAnalysis.ts delete mode 100644 static/packages/scraps/src/badge/projectsBadge.tsx delete mode 100644 static/packages/scraps/src/breadcrumbList/breadcrumbDividerCombo.tsx delete mode 100644 static/packages/scraps/src/breadcrumbList/items/breadcrumbLeadingSlot.tsx delete mode 100644 static/packages/scraps/src/button/buttonBar.tsx delete mode 100644 static/packages/scraps/src/chat/assistantMessage.tsx delete mode 100644 static/packages/scraps/src/chat/messageRow.tsx delete mode 100644 static/packages/scraps/src/chat/spinner.tsx delete mode 100644 static/packages/scraps/src/chat/userMessage.tsx delete mode 100644 static/packages/scraps/src/code/prism.tsx delete mode 100644 static/packages/scraps/src/compactSelect/highlightText.spec.tsx delete mode 100644 static/packages/scraps/src/compactSelect/highlightText.tsx delete mode 100644 static/packages/scraps/src/composer/dom.spec.ts delete mode 100644 static/packages/scraps/src/composer/dom.ts delete mode 100644 static/packages/scraps/src/composer/matching.spec.ts delete mode 100644 static/packages/scraps/src/composer/matching.ts delete mode 100644 static/packages/scraps/src/composer/model.spec.ts delete mode 100644 static/packages/scraps/src/composer/model.ts delete mode 100644 static/packages/scraps/src/dropdownMenu/footer.tsx delete mode 100644 static/packages/scraps/src/dropdownMenu/useSafetyTriangle.ts delete mode 100644 static/packages/scraps/src/entityHeader/constants.tsx delete mode 100644 static/packages/scraps/src/form/FormSearch.tsx delete mode 100644 static/packages/scraps/src/form/autoSaveContext.tsx delete mode 100644 static/packages/scraps/src/form/field/fieldIds.tsx delete mode 100644 static/packages/scraps/src/form/field/groupContext.tsx delete mode 100644 static/packages/scraps/src/form/formContext.ts delete mode 100644 static/packages/scraps/src/input/useAutosizeInput.tsx delete mode 100644 static/packages/scraps/src/markdown/extensions/index.ts delete mode 100644 static/packages/scraps/src/markdown/extensions/tag.spec.ts delete mode 100644 static/packages/scraps/src/markdown/extensions/tag.ts delete mode 100644 static/packages/scraps/src/markdown/marked.spec.tsx delete mode 100644 static/packages/scraps/src/markdown/marked.tsx delete mode 100644 static/packages/scraps/src/markdown/useStreamingAnimation.ts delete mode 100644 static/packages/scraps/src/pictureInPicture/pictureInPicture.spec.tsx delete mode 100644 static/packages/scraps/src/pictureInPicture/pictureInPicture.tsx delete mode 100644 static/packages/scraps/src/select/reactSelectWrapper.tsx delete mode 100644 static/packages/scraps/src/table/styles.tsx delete mode 100644 static/packages/scraps/src/tabs/utils.tsx delete mode 100644 static/packages/scraps/src/toast/toaster.tsx delete mode 100644 static/packages/scraps/src/toast/types.tsx diff --git a/.github/workflows/frontend-snapshots.yml b/.github/workflows/frontend-snapshots.yml index 3472301f385a..a91bebfd8015 100644 --- a/.github/workflows/frontend-snapshots.yml +++ b/.github/workflows/frontend-snapshots.yml @@ -57,7 +57,7 @@ jobs: echo 'Acquire::http::Timeout "30";' | sudo tee -a /etc/apt/apt.conf.d/99-retries for i in 1 2 3; do echo "::group::playwright install-deps (attempt $i)" - timeout -k 30 4m pnpm --fail-if-no-match --filter @sentry-internal/sentry-test exec playwright install-deps chromium && ok=1 || ok=0 + timeout -k 30 4m pnpm --filter @sentry-internal/sentry-test exec playwright install-deps chromium && ok=1 || ok=0 echo "::endgroup::" [ "$ok" = 1 ] && exit 0 echo "Attempt $i failed; clearing apt locks and retrying in 15s..." @@ -82,7 +82,7 @@ jobs: run: | for i in 1 2 3; do echo "::group::playwright install browser (attempt $i)" - timeout -k 30 4m pnpm --fail-if-no-match --filter @sentry-internal/sentry-test exec playwright install chromium && ok=1 || ok=0 + timeout -k 30 4m pnpm --filter @sentry-internal/sentry-test exec playwright install chromium && ok=1 || ok=0 echo "::endgroup::" [ "$ok" = 1 ] && exit 0 echo "Attempt $i failed or timed out; retrying in 15s..." diff --git a/oxlint.config.ts b/oxlint.config.ts index 9f9c9292b91c..4fde4e7b82fa 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -1811,7 +1811,7 @@ const config = defineConfig({ }, { files: [ - 'static/packages/scraps/src/**/*.spec.{ts,tsx}', + 'static/packages/scraps/src/**/*.spec.tsx', 'static/packages/scraps/test/**/*.{ts,tsx,mjs}', 'static/packages/icons/src/**/*.spec.tsx', 'static/packages/icons/test/**/*.{ts,tsx,mjs}', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af7f2eca1940..29daee01b1d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -737,6 +737,12 @@ importers: lodash: specifier: ^4.18.1 version: 4.18.1 + marked: + specifier: 18.0.2 + version: 18.0.2 + marked-highlight: + specifier: 2.2.4 + version: 2.2.4(marked@18.0.2) mobx: specifier: 6.13.7 version: 6.13.7 @@ -1202,54 +1208,18 @@ importers: '@sentry/react': specifier: 11.6.0 version: 11.6.0(react-router@6.30.6(react@19.3.0))(react@19.3.0) - '@tanstack/react-form': - specifier: 1.28.6 - version: 1.28.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@types/react': specifier: 19.3.0 version: 19.3.0 - '@types/react-select': - specifier: 4.0.18 - version: 4.0.18 - '@types/trusted-types': - specifier: ^2.0.7 - version: 2.0.7 - color: - specifier: 5.0.2 - version: 5.0.2 - dompurify: - specifier: 3.4.16 - version: 3.4.16 framer-motion: specifier: 12.38.0 version: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - marked: - specifier: 18.0.2 - version: 18.0.2 - marked-highlight: - specifier: 2.2.4 - version: 2.2.4(marked@18.0.2) - platformicons: - specifier: ^9.8.0 - version: 9.8.0(react@19.3.0) - prismjs: - specifier: ^1.30.0 - version: 1.30.0 react: specifier: ^19.0.0 version: 19.3.0 react-dom: specifier: ^19.0.0 version: 19.3.0(react@19.3.0) - react-select: - specifier: 4.3.1 - version: 4.3.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(supports-color@8.1.1) - sonner: - specifier: 2.0.8 - version: 2.0.8(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - type-fest: - specifier: ^5.8.0 - version: 5.8.0 devDependencies: '@jest/environment': specifier: 30.5.1 @@ -1287,9 +1257,6 @@ importers: '@types/node': specifier: 26.6.4 version: 26.6.4 - '@types/prismjs': - specifier: ^1.26.0 - version: 1.26.0 '@types/react-dom': specifier: 19.3.0 version: 19.3.0(@types/react@19.3.0) diff --git a/static/app/components/core/avatar/avatar.tsx b/static/app/components/core/avatar/avatar.tsx index 466ded59ab07..1693969eb8a5 100644 --- a/static/app/components/core/avatar/avatar.tsx +++ b/static/app/components/core/avatar/avatar.tsx @@ -2,11 +2,11 @@ import type React from 'react'; import styled from '@emotion/styled'; import classNames from 'classnames'; -import type {BaseAvatarStyleProps} from '@sentry/scraps/avatar/avatarComponentStyles'; -import {LetterAvatar} from '@sentry/scraps/avatar/letterAvatar/letterAvatar'; import {Tooltip, type TooltipProps} from '@sentry/scraps/tooltip'; import {ImageAvatar} from './imageAvatar/imageAvatar'; +import {LetterAvatar} from './letterAvatar/letterAvatar'; +import type {BaseAvatarStyleProps} from './avatarComponentStyles'; import {useAvatar} from './useAvatar'; export interface AvatarProps extends BaseAvatarStyleProps { diff --git a/static/app/components/core/avatar/imageAvatar/imageAvatar.tsx b/static/app/components/core/avatar/imageAvatar/imageAvatar.tsx index 32ad8c90be6d..d5cfe8c32dc1 100644 --- a/static/app/components/core/avatar/imageAvatar/imageAvatar.tsx +++ b/static/app/components/core/avatar/imageAvatar/imageAvatar.tsx @@ -1,12 +1,11 @@ import styled from '@emotion/styled'; import type {Tagged} from 'type-fest'; -import { - baseAvatarStyles, - type BaseAvatarStyleProps, -} from '@sentry/scraps/avatar/avatarComponentStyles'; import {Image, type ImageProps} from '@sentry/scraps/image'; +// eslint-disable-next-line @sentry/no-relative-import-paths +import {baseAvatarStyles, type BaseAvatarStyleProps} from '../avatarComponentStyles'; + export interface ImageAvatarProps extends BaseAvatarStyleProps, Omit { configuration: { diff --git a/static/app/components/core/avatar/index.tsx b/static/app/components/core/avatar/index.tsx index e46a02eecc33..75e22d7eabc6 100644 --- a/static/app/components/core/avatar/index.tsx +++ b/static/app/components/core/avatar/index.tsx @@ -3,7 +3,7 @@ export {ActorAvatar, type ActorAvatarProps} from './actorAvatar'; export {AvatarList, CollapsedAvatars} from './avatarList'; export {DocIntegrationAvatar} from './docIntegrationAvatar'; export {ImageAvatar} from './imageAvatar/imageAvatar'; -export {LetterAvatar} from '@sentry/scraps/avatar/letterAvatar/letterAvatar'; +export {LetterAvatar} from './letterAvatar/letterAvatar'; export {OrganizationAvatar} from './organizationAvatar'; export {ProjectAvatar} from './projectAvatar'; export {SentryAppAvatar} from './sentryAppAvatar'; diff --git a/static/app/components/core/avatar/useAvatar.ts b/static/app/components/core/avatar/useAvatar.ts index 59dd26cc38bd..0e895f99762c 100644 --- a/static/app/components/core/avatar/useAvatar.ts +++ b/static/app/components/core/avatar/useAvatar.ts @@ -6,11 +6,10 @@ import {useQuery} from '@tanstack/react-query'; import * as qs from 'query-string'; import type {Tagged} from 'type-fest'; -import type {LetterAvatarProps} from '@sentry/scraps/avatar/letterAvatar/letterAvatar'; - import {ConfigStore} from 'sentry/stores/configStore'; import type {ImageAvatarProps} from './imageAvatar/imageAvatar'; +import type {LetterAvatarProps} from './letterAvatar/letterAvatar'; type GravatarDefinition = {gravatarId: string; type: 'gravatar'}; type UploadDefinition = {type: 'upload'; uploadUrl: string}; diff --git a/static/app/components/core/avatarButton/useAvatarColors.ts b/static/app/components/core/avatarButton/useAvatarColors.ts index 456f87d31081..399a617f3830 100644 --- a/static/app/components/core/avatarButton/useAvatarColors.ts +++ b/static/app/components/core/avatarButton/useAvatarColors.ts @@ -4,7 +4,8 @@ import color from 'color'; import type {BaseAvatarProps} from '@sentry/scraps/avatar'; import {useAvatar} from '@sentry/scraps/avatar'; -import {resolveImageAvatarColors} from '@sentry/scraps/avatarButton/avatarImageAnalysis'; + +import {resolveImageAvatarColors} from './avatarImageAnalysis'; export type AvatarColorsResult = | {chonk: string; style: 'padded' | 'fill'; type: 'image'} diff --git a/static/app/components/core/badge/index.tsx b/static/app/components/core/badge/index.tsx index e451916980ce..947fbd301ac7 100644 --- a/static/app/components/core/badge/index.tsx +++ b/static/app/components/core/badge/index.tsx @@ -2,5 +2,5 @@ export {AlertBadge} from './alertBadge'; export {Badge} from './badge'; export {DeployBadge} from './deployBadge'; export {FeatureBadge, type FeatureBadgeProps} from './featureBadge'; -export {ProjectsBadge} from '@sentry/scraps/badge/projectsBadge'; +export {ProjectsBadge} from './projectsBadge'; export {Tag, type TagProps} from './tag'; diff --git a/static/app/components/core/breadcrumbList/breadcrumbList.tsx b/static/app/components/core/breadcrumbList/breadcrumbList.tsx index 5dd7b997b45b..52f41a0ac738 100644 --- a/static/app/components/core/breadcrumbList/breadcrumbList.tsx +++ b/static/app/components/core/breadcrumbList/breadcrumbList.tsx @@ -1,4 +1,3 @@ -import {BreadcrumbDividerCombo} from '@sentry/scraps/breadcrumbList/breadcrumbDividerCombo'; import {Container, Flex, useHasContainerQuery} from '@sentry/scraps/layout'; import {unreachable} from 'sentry/utils/unreachable'; @@ -12,6 +11,7 @@ import type {BreadcrumbItemPageTitleEditableProps} from './items/breadcrumbItemP import {BreadcrumbItemPageTitleEditable} from './items/breadcrumbItemPageTitleEditable'; import type {BreadcrumbItemSelectProps} from './items/breadcrumbItemSelect'; import {BreadcrumbItemSelect} from './items/breadcrumbItemSelect'; +import {BreadcrumbDividerCombo} from './breadcrumbDividerCombo'; type LinkBreadcrumbItem = {type: 'link'} & BreadcrumbItemLinkProps; type PageTitleBreadcrumbItem = { diff --git a/static/app/components/core/breadcrumbList/items/breadcrumbItemLink.tsx b/static/app/components/core/breadcrumbList/items/breadcrumbItemLink.tsx index 9837ac148cc7..1e89c32558e6 100644 --- a/static/app/components/core/breadcrumbList/items/breadcrumbItemLink.tsx +++ b/static/app/components/core/breadcrumbList/items/breadcrumbItemLink.tsx @@ -1,9 +1,10 @@ -import {BreadcrumbLeadingSlot} from '@sentry/scraps/breadcrumbList/items/breadcrumbLeadingSlot'; import {Container, Flex} from '@sentry/scraps/layout'; import type {LinkProps} from '@sentry/scraps/link'; import {ExternalLink, Link} from '@sentry/scraps/link'; import {Text} from '@sentry/scraps/text'; +import {BreadcrumbLeadingSlot} from './breadcrumbLeadingSlot'; + export type BreadcrumbItemLinkProps = { label: string; /** diff --git a/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitle.tsx b/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitle.tsx index 2d8cfaee36ed..2c4d50a3c7b7 100644 --- a/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitle.tsx +++ b/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitle.tsx @@ -6,7 +6,6 @@ import {BreadcrumbCopyAction} from '@sentry/scraps/breadcrumbList/actions/breadc import type {BreadcrumbCopyActionProps} from '@sentry/scraps/breadcrumbList/actions/breadcrumbCopyAction'; import {BreadcrumbMenuAction} from '@sentry/scraps/breadcrumbList/actions/breadcrumbMenuAction'; import type {BreadcrumbMenuActionProps} from '@sentry/scraps/breadcrumbList/actions/breadcrumbMenuAction'; -import {BreadcrumbLeadingSlot} from '@sentry/scraps/breadcrumbList/items/breadcrumbLeadingSlot'; import { Button, type ButtonProps, @@ -21,6 +20,8 @@ import {Tooltip} from '@sentry/scraps/tooltip'; import {unreachable} from 'sentry/utils/unreachable'; +import {BreadcrumbLeadingSlot} from './breadcrumbLeadingSlot'; + /** * A single trailing action for the page-title crumb. The discriminant keeps * the supported action shapes type-safe and the trailing slot visually diff --git a/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitleEditable.tsx b/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitleEditable.tsx index 29194d45788d..2d9e663840f1 100644 --- a/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitleEditable.tsx +++ b/static/app/components/core/breadcrumbList/items/breadcrumbItemPageTitleEditable.tsx @@ -1,10 +1,11 @@ -import {BreadcrumbLeadingSlot} from '@sentry/scraps/breadcrumbList/items/breadcrumbLeadingSlot'; import {Container, Flex} from '@sentry/scraps/layout'; import {Heading, Text} from '@sentry/scraps/text'; import {EditableText} from 'sentry/components/editableText'; import {ControlState} from 'sentry/components/forms/fieldGroup/controlState'; +import {BreadcrumbLeadingSlot} from './breadcrumbLeadingSlot'; + export interface BreadcrumbItemPageTitleEditableProps { /** Accessible name for the editable input. */ 'aria-label': string; diff --git a/static/app/components/core/breadcrumbList/items/breadcrumbItemSelect.tsx b/static/app/components/core/breadcrumbList/items/breadcrumbItemSelect.tsx index 0814651a49b3..a336eed17169 100644 --- a/static/app/components/core/breadcrumbList/items/breadcrumbItemSelect.tsx +++ b/static/app/components/core/breadcrumbList/items/breadcrumbItemSelect.tsx @@ -1,6 +1,5 @@ import {IconChevron} from '@sentry/icons/chevron'; -import {BreadcrumbLeadingSlot} from '@sentry/scraps/breadcrumbList/items/breadcrumbLeadingSlot'; import {CompactSelect} from '@sentry/scraps/compactSelect'; import type {SelectKey, SingleSelectProps} from '@sentry/scraps/compactSelect'; import {Container, Flex} from '@sentry/scraps/layout'; @@ -10,6 +9,7 @@ import {Text} from '@sentry/scraps/text'; import {useTranslation} from '@sentry/scraps/translation/useTranslation'; import {BreadcrumbItemLink} from './breadcrumbItemLink'; +import {BreadcrumbLeadingSlot} from './breadcrumbLeadingSlot'; export type BreadcrumbItemSelectProps = Pick< Extract, {clearable?: false}>, diff --git a/static/app/components/core/button/index.tsx b/static/app/components/core/button/index.tsx index 0f2bee1c1f70..300f0ad7524d 100644 --- a/static/app/components/core/button/index.tsx +++ b/static/app/components/core/button/index.tsx @@ -1,5 +1,5 @@ export {Button, type ButtonProps} from './button'; -export {ButtonBar, type ButtonBarProps} from '@sentry/scraps/button/buttonBar'; +export {ButtonBar, type ButtonBarProps} from './buttonBar'; export {LinkButton, type LinkButtonProps} from './linkButton'; export {DO_NOT_USE_getButtonStyles} from './styles'; diff --git a/static/app/components/core/chat/index.tsx b/static/app/components/core/chat/index.tsx index 4229a813c4d6..a0991ea7e8b3 100644 --- a/static/app/components/core/chat/index.tsx +++ b/static/app/components/core/chat/index.tsx @@ -1,11 +1,11 @@ -export {AssistantMessage} from '@sentry/scraps/chat/assistantMessage'; +export {AssistantMessage} from './assistantMessage'; export {AssistantActions} from './assistantActions'; export {ClippedDetail} from './clippedDetail'; -export {UserMessage} from '@sentry/scraps/chat/userMessage'; +export {UserMessage} from './userMessage'; export {ToolCallIndicator, type ToolCallStatus} from './toolCallIndicator'; /** @public */ export {ToolCall, getToolCallStatusLabel, type ToolCallReference} from './toolCall'; -export {Spinner} from '@sentry/scraps/chat/spinner'; -export {MessageRow} from '@sentry/scraps/chat/messageRow'; +export {Spinner} from './spinner'; +export {MessageRow} from './messageRow'; /** @public */ export {ThinkingBlock} from './thinkingBlock'; diff --git a/static/app/components/core/chat/toolCallIndicator.tsx b/static/app/components/core/chat/toolCallIndicator.tsx index 38a12a12395d..311f88553052 100644 --- a/static/app/components/core/chat/toolCallIndicator.tsx +++ b/static/app/components/core/chat/toolCallIndicator.tsx @@ -2,11 +2,12 @@ import {IconCheckmark} from '@sentry/icons/checkmark'; import {IconClose} from '@sentry/icons/close'; import {IconWarning} from '@sentry/icons/warning'; -import {Spinner} from '@sentry/scraps/chat/spinner'; import {useTranslation} from '@sentry/scraps/translation/useTranslation'; import {unreachable} from 'sentry/utils/unreachable'; +import {Spinner} from './spinner'; + /** * Aggregate status of a message's tool calls. * diff --git a/static/app/components/core/code/codeBlock.tsx b/static/app/components/core/code/codeBlock.tsx index f9e4b6e2e617..dd807bd514f3 100644 --- a/static/app/components/core/code/codeBlock.tsx +++ b/static/app/components/core/code/codeBlock.tsx @@ -5,12 +5,13 @@ import {IconCopy} from '@sentry/icons/copy'; import Prism from 'prismjs'; import {Button} from '@sentry/scraps/button'; -import {getPrismLanguage, loadPrismLanguage} from '@sentry/scraps/code/prism'; import {Container} from '@sentry/scraps/layout'; import {useTranslation} from '@sentry/scraps/translation/useTranslation'; import {darkTheme} from 'sentry/utils/theme/theme'; +import {getPrismLanguage, loadPrismLanguage} from './prism'; + interface CodeBlockProps { children: string; /** diff --git a/static/app/components/core/code/index.tsx b/static/app/components/core/code/index.tsx index cfa275c63479..c4f6afe2d8ca 100644 --- a/static/app/components/core/code/index.tsx +++ b/static/app/components/core/code/index.tsx @@ -1,3 +1,3 @@ export {InlineCode, inlineCodeStyles} from '@sentry/scraps/code/inlineCode'; export {CodeBlock} from './codeBlock'; -export {getPrismLanguage, loadPrismLanguage} from '@sentry/scraps/code/prism'; +export {getPrismLanguage, loadPrismLanguage} from './prism'; diff --git a/static/app/components/core/compactSelect/index.tsx b/static/app/components/core/compactSelect/index.tsx index 9902e29c85f4..9325918549db 100644 --- a/static/app/components/core/compactSelect/index.tsx +++ b/static/app/components/core/compactSelect/index.tsx @@ -13,7 +13,7 @@ export {CompositeSelect} from './composite'; export {ControlContext} from './controlContext'; -export {HighlightText} from '@sentry/scraps/compactSelect/highlightText'; +export {HighlightText} from './highlightText'; export {MenuComponents} from './menuComponents'; diff --git a/static/app/components/core/composer/composer.tsx b/static/app/components/core/composer/composer.tsx index cedff9e72577..7894ed12c69c 100644 --- a/static/app/components/core/composer/composer.tsx +++ b/static/app/components/core/composer/composer.tsx @@ -5,24 +5,6 @@ import {mergeProps} from '@react-aria/utils'; import {VisuallyHidden} from '@react-aria/visually-hidden'; import type {QueryStatus} from '@tanstack/react-query'; -import { - type EditorSelection, - getDOMPoint, - getEditorSelection, - readEditorValue, - setEditorSelection, - writeEditorValue, -} from '@sentry/scraps/composer/dom'; -import { - findActiveTrigger, - getRequestKey, - type ActiveTrigger, -} from '@sentry/scraps/composer/matching'; -import { - type Mention, - type ComposerValue, - reconcileMentions, -} from '@sentry/scraps/composer/model'; import {Container} from '@sentry/scraps/layout'; import {useTranslation} from '@sentry/scraps/translation/useTranslation'; @@ -30,6 +12,16 @@ import {Overlay, PositionWrapper} from 'sentry/components/overlay'; import {useOverlay} from 'sentry/utils/useOverlay'; import {useStableMergeRef} from 'sentry/utils/useStableMergeRef'; +import { + type EditorSelection, + getDOMPoint, + getEditorSelection, + readEditorValue, + setEditorSelection, + writeEditorValue, +} from './dom'; +import {findActiveTrigger, getRequestKey, type ActiveTrigger} from './matching'; +import {type Mention, type ComposerValue, reconcileMentions} from './model'; import {CaretAnchor, ComposerEditor, SuggestionListBox, SuggestionStatus} from './styles'; import type {ComposerProps} from './types'; import {useComposerSuggestions} from './useComposerSuggestions'; diff --git a/static/app/components/core/composer/index.tsx b/static/app/components/core/composer/index.tsx index d30e8aa75bf4..56edc9278dc3 100644 --- a/static/app/components/core/composer/index.tsx +++ b/static/app/components/core/composer/index.tsx @@ -1,5 +1,5 @@ export {Composer} from './composer'; -export type {Mention, ComposerValue} from '@sentry/scraps/composer/model'; +export type {Mention, ComposerValue} from './model'; export type { ComposerActions, ComposerPlugin, diff --git a/static/app/components/core/composer/types.ts b/static/app/components/core/composer/types.ts index b0bc8a687540..078c042627c2 100644 --- a/static/app/components/core/composer/types.ts +++ b/static/app/components/core/composer/types.ts @@ -1,10 +1,10 @@ import type React from 'react'; import type {AnyUseQueryOptions} from '@tanstack/react-query'; -import type {ComposerValue} from '@sentry/scraps/composer/model'; - import type {FormSize} from 'sentry/utils/theme'; +import type {ComposerValue} from './model'; + /** * Edits the value captured when a suggestion is selected. Call at most one action, * synchronously inside `onSelect`; actions cannot be retained or used after awaiting. diff --git a/static/app/components/core/composer/useComposerSuggestions.tsx b/static/app/components/core/composer/useComposerSuggestions.tsx index adb0f84706da..296addec461b 100644 --- a/static/app/components/core/composer/useComposerSuggestions.tsx +++ b/static/app/components/core/composer/useComposerSuggestions.tsx @@ -4,8 +4,7 @@ import {Item} from '@react-stately/collections'; import {useListState} from '@react-stately/list'; import {skipToken, useQueries} from '@tanstack/react-query'; -import type {ActiveTrigger} from '@sentry/scraps/composer/matching'; - +import type {ActiveTrigger} from './matching'; import type {ComposerSource} from './types'; interface SuggestionListItem { diff --git a/static/app/components/core/dropdownMenu/context.tsx b/static/app/components/core/dropdownMenu/context.tsx index b63cabdd9828..2a9308067e92 100644 --- a/static/app/components/core/dropdownMenu/context.tsx +++ b/static/app/components/core/dropdownMenu/context.tsx @@ -1,11 +1,10 @@ import {createContext} from 'react'; import type {TreeState} from '@react-stately/tree'; -import type {useSafetyTriangle} from '@sentry/scraps/dropdownMenu/useSafetyTriangle'; - import type {useOverlay} from 'sentry/utils/useOverlay'; import type {MenuItemProps} from './item'; +import type {useSafetyTriangle} from './useSafetyTriangle'; export type OverlayState = ReturnType['state']; diff --git a/static/app/components/core/dropdownMenu/index.tsx b/static/app/components/core/dropdownMenu/index.tsx index 048bd7951895..498f80f17656 100644 --- a/static/app/components/core/dropdownMenu/index.tsx +++ b/static/app/components/core/dropdownMenu/index.tsx @@ -4,4 +4,4 @@ export type {DropdownButtonProps} from './dropdownButton'; export {DropdownMenu} from './dropdownMenu'; export type {DropdownMenuProps, MenuItemProps} from './dropdownMenu'; -export {DropdownMenuFooter} from '@sentry/scraps/dropdownMenu/footer'; +export {DropdownMenuFooter} from './footer'; diff --git a/static/app/components/core/dropdownMenu/list.tsx b/static/app/components/core/dropdownMenu/list.tsx index dd95c6efdb3e..10fedf8982c0 100644 --- a/static/app/components/core/dropdownMenu/list.tsx +++ b/static/app/components/core/dropdownMenu/list.tsx @@ -13,7 +13,6 @@ import {useTreeState} from '@react-stately/tree'; import type {Node} from '@react-types/shared'; import type {LocationDescriptor} from 'history'; -import {useSafetyTriangle} from '@sentry/scraps/dropdownMenu/useSafetyTriangle'; import {Container} from '@sentry/scraps/layout'; import {Overlay, PositionWrapper} from 'sentry/components/overlay'; @@ -25,6 +24,7 @@ import {DropdownMenuContext} from './context'; import type {MenuItemProps} from './item'; import {DropdownMenuItem} from './item'; import {DropdownMenuSection} from './section'; +import {useSafetyTriangle} from './useSafetyTriangle'; export interface DropdownMenuListProps extends diff --git a/static/app/components/core/entityHeader/entityHeader.tsx b/static/app/components/core/entityHeader/entityHeader.tsx index 936104f101ef..03e6a09aec5d 100644 --- a/static/app/components/core/entityHeader/entityHeader.tsx +++ b/static/app/components/core/entityHeader/entityHeader.tsx @@ -1,4 +1,3 @@ -import {METADATA_TEXT_HEIGHT, ROW_HEIGHT} from '@sentry/scraps/entityHeader/constants'; import {Container, Flex, Grid, Stack, useHasContainerQuery} from '@sentry/scraps/layout'; import {Text} from '@sentry/scraps/text'; @@ -15,6 +14,7 @@ import type {EntityHeaderStatProps} from './items/entityHeaderStat'; import {EntityHeaderStat} from './items/entityHeaderStat'; import type {EntityHeaderTitleProps} from './items/entityHeaderTitle'; import {EntityHeaderTitle} from './items/entityHeaderTitle'; +import {METADATA_TEXT_HEIGHT, ROW_HEIGHT} from './constants'; export interface EntityHeaderProps { /** diff --git a/static/app/components/core/form/field/baseField.tsx b/static/app/components/core/form/field/baseField.tsx index c9a9415633b8..9de4a12da674 100644 --- a/static/app/components/core/form/field/baseField.tsx +++ b/static/app/components/core/form/field/baseField.tsx @@ -2,13 +2,13 @@ import {useEffect, useRef, type Ref} from 'react'; import {mergeRefs} from '@react-aria/utils'; import {useAutoSaveContext} from '@sentry/scraps/form/autoSaveContext'; -import {useFieldId, useHintTextId} from '@sentry/scraps/form/field/fieldIds'; import {useFieldContext} from '@sentry/scraps/form/formContext'; import {Checkmark, Spinner} from '@sentry/scraps/form/icons'; import {Flex} from '@sentry/scraps/layout'; import {useLocation} from 'sentry/utils/useLocation'; +import {useFieldId, useHintTextId} from './fieldIds'; import {FieldMeta} from './meta'; export type BaseFieldProps = { diff --git a/static/app/components/core/form/field/meta.tsx b/static/app/components/core/form/field/meta.tsx index bf48807e0196..7cd27603cfd1 100644 --- a/static/app/components/core/form/field/meta.tsx +++ b/static/app/components/core/form/field/meta.tsx @@ -1,6 +1,5 @@ import {VisuallyHidden} from '@react-aria/visually-hidden'; -import {useFieldId, useHintTextId, useLabelId} from '@sentry/scraps/form/field/fieldIds'; import {useGroupContext} from '@sentry/scraps/form/field/groupContext'; import {useFieldContext} from '@sentry/scraps/form/formContext'; import {RequiredIndicator, Warning} from '@sentry/scraps/form/icons'; @@ -9,6 +8,8 @@ import {Container, Flex} from '@sentry/scraps/layout'; import {Text} from '@sentry/scraps/text'; import {Tooltip} from '@sentry/scraps/tooltip'; +import {useFieldId, useHintTextId, useLabelId} from './fieldIds'; + function HintText(props: {children: React.ReactNode}) { const id = useHintTextId(); diff --git a/static/app/components/core/form/field/radioField.tsx b/static/app/components/core/form/field/radioField.tsx index c2716ea8ab8d..5e83311b19bd 100644 --- a/static/app/components/core/form/field/radioField.tsx +++ b/static/app/components/core/form/field/radioField.tsx @@ -1,14 +1,14 @@ import {createContext, useContext, useId} from 'react'; import {useAutoSaveContext} from '@sentry/scraps/form/autoSaveContext'; -import {useLabelId} from '@sentry/scraps/form/field/fieldIds'; -import {GroupProvider} from '@sentry/scraps/form/field/groupContext'; import {useFieldContext} from '@sentry/scraps/form/formContext'; import {Flex, Stack} from '@sentry/scraps/layout'; import {Radio} from '@sentry/scraps/radio'; import {Text} from '@sentry/scraps/text'; import {useAutoSaveIndicator} from './baseField'; +import {useLabelId} from './fieldIds'; +import {GroupProvider} from './groupContext'; import {FieldMeta} from './meta'; // Context for Radio.Group -> Radio.Item communication diff --git a/static/app/components/core/form/field/rangeField.tsx b/static/app/components/core/form/field/rangeField.tsx index b258a4cef22d..a8117ac938c2 100644 --- a/static/app/components/core/form/field/rangeField.tsx +++ b/static/app/components/core/form/field/rangeField.tsx @@ -2,11 +2,11 @@ import {Fragment} from 'react'; import type {DistributedOmit} from 'type-fest'; import {useAutoSaveContext} from '@sentry/scraps/form/autoSaveContext'; -import {useLabelId} from '@sentry/scraps/form/field/fieldIds'; import {Flex} from '@sentry/scraps/layout'; import {Slider, type SliderProps} from '@sentry/scraps/slider'; import {BaseField, type BaseFieldProps} from './baseField'; +import {useLabelId} from './fieldIds'; export function RangeField({ onChange, diff --git a/static/app/components/core/form/index.ts b/static/app/components/core/form/index.ts index 5f5c991e125f..a597bbac6721 100644 --- a/static/app/components/core/form/index.ts +++ b/static/app/components/core/form/index.ts @@ -9,8 +9,8 @@ export { withForm, } from './scrapsForm'; export {AutoSaveForm} from './autoSaveForm'; -export {AutoSaveContextProvider} from '@sentry/scraps/form/autoSaveContext'; +export {AutoSaveContextProvider} from './autoSaveContext'; export {FieldGroup} from './layout/fieldGroup'; -export {FormSearch} from '@sentry/scraps/form/FormSearch'; +export {FormSearch} from './FormSearch'; // eslint-disable-next-line no-restricted-imports export {useStore} from '@tanstack/react-form'; diff --git a/static/app/components/core/form/scrapsForm.tsx b/static/app/components/core/form/scrapsForm.tsx index 16a628613771..29f1e9ebe62a 100644 --- a/static/app/components/core/form/scrapsForm.tsx +++ b/static/app/components/core/form/scrapsForm.tsx @@ -11,13 +11,6 @@ import {Button, type ButtonProps} from '@sentry/scraps/button'; import {BaseField} from '@sentry/scraps/form/field/baseField'; import {CheckboxField} from '@sentry/scraps/form/field/checkboxField'; import {FieldMeta} from '@sentry/scraps/form/field/meta'; -import { - FormElementContext, - fieldContext, - formContext, - useFormContext, - useIsInsideFormElement, -} from '@sentry/scraps/form/formContext'; import {FieldLayout} from '@sentry/scraps/form/layout'; import {FieldGroup} from '@sentry/scraps/form/layout/fieldGroup'; @@ -30,6 +23,13 @@ import {SelectAsyncField} from './field/selectAsyncField'; import {SelectField} from './field/selectField'; import {SwitchField} from './field/switchField'; import {TextAreaField} from './field/textAreaField'; +import { + FormElementContext, + fieldContext, + formContext, + useFormContext, + useIsInsideFormElement, +} from './formContext'; export const defaultFormOptions = formOptions({ onSubmitInvalid({ diff --git a/static/app/components/core/input/index.tsx b/static/app/components/core/input/index.tsx index 60c5a8a35ea2..a8e5b197769f 100644 --- a/static/app/components/core/input/index.tsx +++ b/static/app/components/core/input/index.tsx @@ -5,4 +5,4 @@ export {NumberDragInput} from './numberDragInput'; export {NumberInput} from './numberInput'; /** @public */ export {OTPInput} from './otpInput'; -export {useAutosizeInput} from '@sentry/scraps/input/useAutosizeInput'; +export {useAutosizeInput} from './useAutosizeInput'; diff --git a/static/app/components/core/markdown/defaultComponents.tsx b/static/app/components/core/markdown/defaultComponents.tsx index 69ea1f21bc88..868280e95ee1 100644 --- a/static/app/components/core/markdown/defaultComponents.tsx +++ b/static/app/components/core/markdown/defaultComponents.tsx @@ -4,11 +4,12 @@ import styled from '@emotion/styled'; import {CodeBlock, InlineCode} from '@sentry/scraps/code'; import {Container, Flex, Stack} from '@sentry/scraps/layout'; import {ExternalLink, Link} from '@sentry/scraps/link'; -import {isSafeHref, isInternalHref} from '@sentry/scraps/markdown/marked'; import {Quote} from '@sentry/scraps/quote'; import {Separator} from '@sentry/scraps/separator'; import {Heading, Text} from '@sentry/scraps/text'; +import {isSafeHref, isInternalHref} from './marked'; + export function DefaultParagraph({children}: {children: ReactNode}) { return ( diff --git a/static/app/components/core/markdown/index.tsx b/static/app/components/core/markdown/index.tsx index 6c8c001bba8f..6e13d85e2d64 100644 --- a/static/app/components/core/markdown/index.tsx +++ b/static/app/components/core/markdown/index.tsx @@ -10,14 +10,6 @@ export { sanitizedMarked, singleLineRenderer, splitTags, -} from '@sentry/scraps/markdown/marked'; -export type { - ExtendedToken, - MarkedToken, - TagSegment, - Token, -} from '@sentry/scraps/markdown/marked'; -export { - streamingAnimationStyles, - useTextDecodeAnimation, -} from '@sentry/scraps/markdown/useStreamingAnimation'; +} from './marked'; +export type {ExtendedToken, MarkedToken, TagSegment, Token} from './marked'; +export {streamingAnimationStyles, useTextDecodeAnimation} from './useStreamingAnimation'; diff --git a/static/app/components/core/markdown/markdown.tsx b/static/app/components/core/markdown/markdown.tsx index 52e05a9a1ce3..89d4999b1aa0 100644 --- a/static/app/components/core/markdown/markdown.tsx +++ b/static/app/components/core/markdown/markdown.tsx @@ -3,14 +3,11 @@ import type {ComponentType, ReactNode} from 'react'; import {Global} from '@emotion/react'; import {Stack} from '@sentry/scraps/layout'; -import type {ExtendedToken} from '@sentry/scraps/markdown/marked'; -import {MarkedLexer} from '@sentry/scraps/markdown/marked'; -import { - streamingAnimationStyles, - useStreamingAnimation, -} from '@sentry/scraps/markdown/useStreamingAnimation'; +import type {ExtendedToken} from './marked'; +import {MarkedLexer} from './marked'; import {MarkdownStreamingContext, Token} from './token'; +import {streamingAnimationStyles, useStreamingAnimation} from './useStreamingAnimation'; type WithDefault = Props & {Default: ComponentType}; diff --git a/static/app/components/core/markdown/token.tsx b/static/app/components/core/markdown/token.tsx index 30ea40413704..bebeb7bd8440 100644 --- a/static/app/components/core/markdown/token.tsx +++ b/static/app/components/core/markdown/token.tsx @@ -1,8 +1,6 @@ import {createContext, useContext, type ReactNode} from 'react'; import {Checkbox} from '@sentry/scraps/checkbox'; -import type {ExtendedToken, Token as TokenType} from '@sentry/scraps/markdown/marked'; -import {isSafeHref, isInternalHref, sanitizeHtml} from '@sentry/scraps/markdown/marked'; import {unreachable} from 'sentry/utils/unreachable'; @@ -34,6 +32,8 @@ import { DefaultUnorderedList, } from './defaultComponents'; import type {MarkdownComponents} from './markdown'; +import type {ExtendedToken, Token as TokenType} from './marked'; +import {isSafeHref, isInternalHref, sanitizeHtml} from './marked'; const TAG_START_RE = /\{%\s+[\w-]/; diff --git a/static/app/components/core/pictureInPicture/index.tsx b/static/app/components/core/pictureInPicture/index.tsx index 9f1bbb2801dd..bddc9b39134d 100644 --- a/static/app/components/core/pictureInPicture/index.tsx +++ b/static/app/components/core/pictureInPicture/index.tsx @@ -1,5 +1,2 @@ -export { - PictureInPictureProvider, - usePictureInPicture, -} from '@sentry/scraps/pictureInPicture/pictureInPicture'; +export {PictureInPictureProvider, usePictureInPicture} from './pictureInPicture'; export {PictureInPicturePortal} from './pictureInPicturePortal'; diff --git a/static/app/components/core/select/index.tsx b/static/app/components/core/select/index.tsx index 4c809100b2a8..bd47e08c7a2a 100644 --- a/static/app/components/core/select/index.tsx +++ b/static/app/components/core/select/index.tsx @@ -7,13 +7,13 @@ export { export {SelectOption} from './option'; export {CheckWrap} from './styles'; -export {components, createFilter} from '@sentry/scraps/select/reactSelectWrapper'; +export {components, createFilter} from './reactSelectWrapper'; export type { MultiValueProps, OptionsType, OptionTypeBase, SingleValueProps, ValueType, -} from '@sentry/scraps/select/reactSelectWrapper'; +} from './reactSelectWrapper'; export type {SelectValue} from './types'; diff --git a/static/app/components/core/select/option.tsx b/static/app/components/core/select/option.tsx index 887d3ff3296f..b62dee9021d9 100644 --- a/static/app/components/core/select/option.tsx +++ b/static/app/components/core/select/option.tsx @@ -5,10 +5,10 @@ import {IconAdd} from '@sentry/icons/add'; import {IconCheckmark} from '@sentry/icons/checkmark'; import {MenuListItem} from '@sentry/scraps/menuListItem'; -import type {components as selectComponents} from '@sentry/scraps/select/reactSelectWrapper'; import {defined} from 'sentry/utils/defined'; +import type {components as selectComponents} from './reactSelectWrapper'; import {CheckWrap} from './styles'; type Props = React.ComponentProps; diff --git a/static/app/components/core/select/select.tsx b/static/app/components/core/select/select.tsx index dc1e6694b8f3..ae34e197b7b4 100644 --- a/static/app/components/core/select/select.tsx +++ b/static/app/components/core/select/select.tsx @@ -11,6 +11,16 @@ import {IconChevron} from '@sentry/icons/chevron'; import {IconClose} from '@sentry/icons/close'; import {Button} from '@sentry/scraps/button'; +import {useTranslation} from '@sentry/scraps/translation/useTranslation'; + +import {LoadingIndicator} from 'sentry/components/loadingIndicator'; +import type {Choices} from 'sentry/types/core'; +import {convertFromSelect2Choices} from 'sentry/utils/convertFromSelect2Choices'; +import {defined} from 'sentry/utils/defined'; +import {PanelProvider} from 'sentry/utils/panelProvider'; +import type {FormSize, Theme} from 'sentry/utils/theme'; + +import {SelectOption} from './option'; import type { GroupedOptionsType, OptionsType, @@ -18,23 +28,13 @@ import type { Props as ReactSelectProps, StylesConfig as ReactSelectStylesConfig, ValueType, -} from '@sentry/scraps/select/reactSelectWrapper'; +} from './reactSelectWrapper'; import { createFilter, mergeStyles, ReactSelect, components as selectComponents, -} from '@sentry/scraps/select/reactSelectWrapper'; -import {useTranslation} from '@sentry/scraps/translation/useTranslation'; - -import {LoadingIndicator} from 'sentry/components/loadingIndicator'; -import type {Choices} from 'sentry/types/core'; -import {convertFromSelect2Choices} from 'sentry/utils/convertFromSelect2Choices'; -import {defined} from 'sentry/utils/defined'; -import {PanelProvider} from 'sentry/utils/panelProvider'; -import type {FormSize, Theme} from 'sentry/utils/theme'; - -import {SelectOption} from './option'; +} from './reactSelectWrapper'; import type {SelectValue} from './types'; // We don't care about any options for the styles config diff --git a/static/app/components/core/table/index.tsx b/static/app/components/core/table/index.tsx index 35f1295e1a3d..5cc9525114f3 100644 --- a/static/app/components/core/table/index.tsx +++ b/static/app/components/core/table/index.tsx @@ -14,4 +14,4 @@ export { fullWidthCellStyle, statusCellStyle, TableStatusCell, -} from '@sentry/scraps/table/styles'; +} from './styles'; diff --git a/static/app/components/core/table/table.tsx b/static/app/components/core/table/table.tsx index 260d4526836a..f671303c5170 100644 --- a/static/app/components/core/table/table.tsx +++ b/static/app/components/core/table/table.tsx @@ -22,16 +22,6 @@ import type {LocationDescriptor} from 'history'; import {DragHandle} from '@sentry/scraps/dragHandle'; import {type Responsive, useResponsivePropResolver} from '@sentry/scraps/layout'; -import { - TableBody, - TableCell, - TableGrid, - TableHead, - TableHeadCell, - TableResizer, - TableRow, - TableStatusCell, -} from '@sentry/scraps/table/styles'; import { COLUMN_ALIGN_JUSTIFY, @@ -44,6 +34,17 @@ import {useColumnResize} from 'sentry/components/tables/useColumnResize'; import {useObservedColumnSize} from 'sentry/components/tables/useObservedColumnSize'; import {useStableMergeRef} from 'sentry/utils/useStableMergeRef'; +import { + TableBody, + TableCell, + TableGrid, + TableHead, + TableHeadCell, + TableResizer, + TableRow, + TableStatusCell, +} from './styles'; + // Auto layout width. export const COL_WIDTH_UNDEFINED = -1; diff --git a/static/app/components/core/tabs/tab.tsx b/static/app/components/core/tabs/tab.tsx index 85b255351983..fe7b3c4da36d 100644 --- a/static/app/components/core/tabs/tab.tsx +++ b/static/app/components/core/tabs/tab.tsx @@ -7,11 +7,12 @@ import type {TabListState} from '@react-stately/tabs'; import type {DOMAttributes, Node, Orientation} from '@react-types/shared'; import {Link} from '@sentry/scraps/link'; -import {tabsShouldForwardProp} from '@sentry/scraps/tabs/utils'; import {Tooltip, type TooltipProps} from '@sentry/scraps/tooltip'; import type {Theme} from 'sentry/utils/theme'; +import {tabsShouldForwardProp} from './utils'; + interface BaseTabProps { children: React.ReactNode; disabled: boolean; diff --git a/static/app/components/core/tabs/tabList.tsx b/static/app/components/core/tabs/tabList.tsx index 63c9a75fbc1e..7043883f4fc1 100644 --- a/static/app/components/core/tabs/tabList.tsx +++ b/static/app/components/core/tabs/tabList.tsx @@ -22,7 +22,6 @@ import type {SelectOption} from '@sentry/scraps/compactSelect'; import {CompactSelect} from '@sentry/scraps/compactSelect'; import {Container, Flex} from '@sentry/scraps/layout'; import {OverlayTrigger} from '@sentry/scraps/overlayTrigger'; -import {tabsShouldForwardProp} from '@sentry/scraps/tabs/utils'; import {useTranslation} from '@sentry/scraps/translation/useTranslation'; import {useNavigate} from 'sentry/utils/useNavigate'; @@ -32,6 +31,7 @@ import {TabListItem} from './item'; import {Tab} from './tab'; import type {TabProps} from './tab'; import {TabsContext} from './tabs'; +import {tabsShouldForwardProp} from './utils'; const StyledTabListWrap = styled('ul', { shouldForwardProp: tabsShouldForwardProp, diff --git a/static/app/components/core/tabs/tabPanels.tsx b/static/app/components/core/tabs/tabPanels.tsx index 562f5e4a11db..33ce347c9c6c 100644 --- a/static/app/components/core/tabs/tabPanels.tsx +++ b/static/app/components/core/tabs/tabPanels.tsx @@ -7,10 +7,9 @@ import {ListCollection} from '@react-stately/list'; import type {TabListState} from '@react-stately/tabs'; import type {CollectionBase, Node, Orientation} from '@react-types/shared'; -import {tabsShouldForwardProp} from '@sentry/scraps/tabs/utils'; - import {TabPanelItem} from './item'; import {TabsContext} from './tabs'; +import {tabsShouldForwardProp} from './utils'; const collectionFactory = (nodes: Iterable>) => new ListCollection(nodes); diff --git a/static/app/components/core/tabs/tabs.tsx b/static/app/components/core/tabs/tabs.tsx index 4ea276f3d1b6..fada403ddd29 100644 --- a/static/app/components/core/tabs/tabs.tsx +++ b/static/app/components/core/tabs/tabs.tsx @@ -5,9 +5,8 @@ import type {AriaTabListOptions} from '@react-aria/tabs'; import type {TabListState, TabListStateOptions} from '@react-stately/tabs'; import type {Orientation} from '@react-types/shared'; -import {tabsShouldForwardProp} from '@sentry/scraps/tabs/utils'; - import type {TabProps} from './tab'; +import {tabsShouldForwardProp} from './utils'; interface TabsProps extends diff --git a/static/app/components/core/toast/index.tsx b/static/app/components/core/toast/index.tsx index d706d3d8d4bb..f10e862da6ea 100644 --- a/static/app/components/core/toast/index.tsx +++ b/static/app/components/core/toast/index.tsx @@ -1,4 +1,4 @@ export {toast} from './toastApi'; -export {ToastProvider} from '@sentry/scraps/toast/toaster'; -export {DEFAULT_TOAST_DURATION} from '@sentry/scraps/toast/types'; -export type {ToastAction, ToastOptions, ToastVariant} from '@sentry/scraps/toast/types'; +export {ToastProvider} from './toaster'; +export {DEFAULT_TOAST_DURATION} from './types'; +export type {ToastAction, ToastOptions, ToastVariant} from './types'; diff --git a/static/app/components/core/toast/toast.tsx b/static/app/components/core/toast/toast.tsx index 64a520b85cc0..d19de2d5ce62 100644 --- a/static/app/components/core/toast/toast.tsx +++ b/static/app/components/core/toast/toast.tsx @@ -6,13 +6,14 @@ import {IconWarning} from '@sentry/icons/warning'; import {Button} from '@sentry/scraps/button'; import {Container, Flex} from '@sentry/scraps/layout'; import {Text} from '@sentry/scraps/text'; -import type {ToastAction, ToastVariant} from '@sentry/scraps/toast/types'; import {useTranslation} from '@sentry/scraps/translation/useTranslation'; import {LoadingIndicator} from 'sentry/components/loadingIndicator'; import type {Theme} from 'sentry/utils/theme'; import {unreachable} from 'sentry/utils/unreachable'; +import type {ToastAction, ToastVariant} from './types'; + interface ToastProps { message: React.ReactNode; onDismiss: () => void; diff --git a/static/app/components/core/toast/toastApi.tsx b/static/app/components/core/toast/toastApi.tsx index e3a8b45a8295..06e610e202c8 100644 --- a/static/app/components/core/toast/toastApi.tsx +++ b/static/app/components/core/toast/toastApi.tsx @@ -1,9 +1,8 @@ import type {ReactNode} from 'react'; import {toast as sonnerToast} from 'sonner'; -import type {ToastOptions, ToastVariant} from '@sentry/scraps/toast/types'; - import {Toast} from './toast'; +import type {ToastOptions, ToastVariant} from './types'; type ToastId = string | number; diff --git a/static/app/package.json b/static/app/package.json index ca8111ad6bbe..1959700ae1d5 100644 --- a/static/app/package.json +++ b/static/app/package.json @@ -101,6 +101,8 @@ "js-cookie": "3.0.7", "jsonrepair": "^3.14.0", "lodash": "^4.18.1", + "marked": "18.0.2", + "marked-highlight": "2.2.4", "mobx": "6.13.7", "mobx-react-lite": "4.1.0", "moment": "2.31.0", diff --git a/static/app/types/trustedTypes.d.ts b/static/app/types/trustedTypes.d.ts index bce5fffa62dc..e6f22e045f4c 100644 --- a/static/app/types/trustedTypes.d.ts +++ b/static/app/types/trustedTypes.d.ts @@ -1,6 +1,10 @@ // The DOM lib types these Trusted Types sinks as string-only, so these // overloads let the trusted values reach them without an assertion. +interface DOMParser { + parseFromString(string: TrustedHTML, type: DOMParserSupportedType): Document; +} + interface ServiceWorkerContainer { register( scriptURL: TrustedScriptURL, diff --git a/static/packages/scraps/jest.config.mjs b/static/packages/scraps/jest.config.mjs index 54b448e5ad01..e35a5ee81c11 100644 --- a/static/packages/scraps/jest.config.mjs +++ b/static/packages/scraps/jest.config.mjs @@ -1,14 +1,11 @@ const config = { testEnvironment: '/test/env/environment.mjs', setupFilesAfterEnv: ['/test/env/setup.ts'], - testMatch: ['/src/**/*.spec.{ts,tsx}'], + testMatch: ['/src/**/*.spec.tsx'], moduleNameMapper: { '^@sentry/scraps$': '/src/index.ts', '^@sentry/scraps/(.*)$': '/src/$1', }, - transformIgnorePatterns: [ - '/node_modules/.pnpm/(?!(marked|marked-highlight|color|color-convert|color-string|color-name)@)', - ], transform: { '^.+\\.[jt]sx?$': [ '@swc/jest', diff --git a/static/packages/scraps/package.json b/static/packages/scraps/package.json index 1b2cbd86a37d..01db5d55a678 100644 --- a/static/packages/scraps/package.json +++ b/static/packages/scraps/package.json @@ -112,20 +112,8 @@ "@react-stately/slider": "3.8.1", "@sentry/icons": "workspace:*", "@sentry/react": "11.6.0", - "@tanstack/react-form": "1.28.6", "@types/react": "19.3.0", - "@types/react-select": "4.0.18", - "@types/trusted-types": "^2.0.7", - "color": "5.0.2", - "dompurify": "3.4.16", - "framer-motion": "12.38.0", - "marked": "18.0.2", - "marked-highlight": "2.2.4", - "platformicons": "^9.8.0", - "prismjs": "^1.30.0", - "react-select": "4.3.1", - "sonner": "2.0.8", - "type-fest": "^5.8.0" + "framer-motion": "12.38.0" }, "devDependencies": { "@jest/environment": "30.5.1", @@ -140,7 +128,6 @@ "@testing-library/user-event": "14.6.7", "@types/jest": "30.0.0", "@types/node": "26.6.4", - "@types/prismjs": "^1.26.0", "@types/react-dom": "19.3.0", "expect-type": "1.4.0", "jest": "30.5.1", diff --git a/static/packages/scraps/rslib.config.mjs b/static/packages/scraps/rslib.config.mjs index e21a937e0358..0c7301115f47 100644 --- a/static/packages/scraps/rslib.config.mjs +++ b/static/packages/scraps/rslib.config.mjs @@ -4,7 +4,7 @@ import {defineConfig} from '@rslib/core'; export default defineConfig({ lib: [{format: 'esm', bundle: false, dts: true, syntax: 'es2022'}], source: { - entry: {index: ['./src/**', '!src/**/*.spec.{ts,tsx}']}, + entry: {index: ['./src/**', '!src/**/*.spec.tsx']}, }, output: {target: 'web'}, plugins: [pluginReact({swcReactOptions: {importSource: '@emotion/react'}})], diff --git a/static/packages/scraps/scripts/verify.mjs b/static/packages/scraps/scripts/verify.mjs index 5ea2e1d1ff42..ca25e192f4b7 100644 --- a/static/packages/scraps/scripts/verify.mjs +++ b/static/packages/scraps/scripts/verify.mjs @@ -36,10 +36,7 @@ try { const name = specifier.startsWith('@') ? specifier.split('/').slice(0, 2).join('/') : specifier.split('/')[0]; - const typesName = - '@types/' + (name.startsWith('@') ? name.slice(1).replace('/', '__') : name); - const declaredTypes = file.endsWith('.d.ts') && typesName in dependencies; - if (name !== packageJson.name && !(name in dependencies) && !declaredTypes) { + if (name !== packageJson.name && !(name in dependencies)) { throw new Error(`Undeclared dependency: ${specifier} in ${file}`); } } diff --git a/static/packages/scraps/src/avatar/avatarComponentStyles.tsx b/static/packages/scraps/src/avatar/avatarComponentStyles.tsx deleted file mode 100644 index 6d194627f9cd..000000000000 --- a/static/packages/scraps/src/avatar/avatarComponentStyles.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import {css} from '@emotion/react'; - -/** Defines the styling interface for all avatar components */ -export interface BaseAvatarStyleProps { - round?: boolean; - size?: number; - suggested?: boolean; -} - -export const baseAvatarStyles = (props: BaseAvatarStyleProps) => css` - position: absolute; - top: 0px; - left: 0px; - border-radius: ${props.round ? '50%' : '3px'}; - user-select: none; - filter: ${props.suggested ? 'grayscale(100%)' : 'none'}; -`; diff --git a/static/packages/scraps/src/avatar/letterAvatar/letterAvatar.spec.tsx b/static/packages/scraps/src/avatar/letterAvatar/letterAvatar.spec.tsx deleted file mode 100644 index fb425c191723..000000000000 --- a/static/packages/scraps/src/avatar/letterAvatar/letterAvatar.spec.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import type {Tagged} from 'type-fest'; - -import {render, screen} from '../../../test/env'; - -import {LetterAvatar, type LetterAvatarProps} from './letterAvatar'; - -function makeConfiguration( - initials: string, - background: string, - content: string -): LetterAvatarProps['configuration'] { - return { - initials: initials as Tagged, - background: background as Tagged, - content: content as Tagged, - }; -} - -describe('LetterAvatar', () => { - describe('initials rendering', () => { - it.each([['JB'], ['J'], ['☃☃'], ['1'], ['?']])( - 'renders initials "%s" as passed', - initials => { - render( - - ); - expect(screen.getByText(initials)).toBeInTheDocument(); - } - ); - }); - - describe('color rendering', () => { - it('applies the provided background color to the rect', () => { - render( - - ); - expect(screen.getByText('JD')).toBeInTheDocument(); - }); - }); -}); diff --git a/static/packages/scraps/src/avatar/letterAvatar/letterAvatar.tsx b/static/packages/scraps/src/avatar/letterAvatar/letterAvatar.tsx deleted file mode 100644 index bb879462c584..000000000000 --- a/static/packages/scraps/src/avatar/letterAvatar/letterAvatar.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import type React from 'react'; -import isPropValid from '@emotion/is-prop-valid'; -import {useTheme} from '@emotion/react'; -import styled from '@emotion/styled'; -import type {Tagged} from 'type-fest'; - -import {baseAvatarStyles, type BaseAvatarStyleProps} from '../avatarComponentStyles'; - -export interface LetterAvatarProps - extends React.HTMLAttributes, BaseAvatarStyleProps { - configuration: { - background: Tagged; - content: Tagged; - initials: Tagged; - }; - ref?: React.Ref; -} - -export function LetterAvatar({configuration, ...props}: LetterAvatarProps) { - const theme = useTheme(); - return ( - - - - {configuration.initials} - - - ); -} - -const LetterAvatarComponent = styled('svg', { - shouldForwardProp: prop => - isPropValid(prop) && prop !== 'suggested' && prop !== 'round', -})` - ${baseAvatarStyles}; -`; diff --git a/static/packages/scraps/src/avatarButton/avatarImageAnalysis.spec.ts b/static/packages/scraps/src/avatarButton/avatarImageAnalysis.spec.ts deleted file mode 100644 index 142d45fb9553..000000000000 --- a/static/packages/scraps/src/avatarButton/avatarImageAnalysis.spec.ts +++ /dev/null @@ -1,124 +0,0 @@ -// eslint-disable-next-line no-restricted-imports -import color from 'color'; - -import { - darkenUntilContrasts, - dominantColor, - MIN_EDGE_CONTRAST, - SAMPLE_SIZE, - shouldPadImage, - toPageOriginIfSameHost, -} from './avatarImageAnalysis'; - -function makePixelData( - fill: (col: number, row: number) => [number, number, number, number] -): Uint8ClampedArray { - const data = new Uint8ClampedArray(SAMPLE_SIZE * SAMPLE_SIZE * 4); - for (let row = 0; row < SAMPLE_SIZE; row++) { - for (let col = 0; col < SAMPLE_SIZE; col++) { - const [r, g, b, a] = fill(col, row); - const i = (row * SAMPLE_SIZE + col) * 4; - data[i] = r; - data[i + 1] = g; - data[i + 2] = b; - data[i + 3] = a; - } - } - return data; -} - -describe('shouldPadImage', () => { - it('returns fill for a fully opaque image', () => { - const data = makePixelData(() => [200, 100, 50, 255]); - expect(shouldPadImage(data)).toBe('fill'); - }); - - it('returns padded when the left edge is transparent', () => { - const data = makePixelData((col, _row) => - col === 0 ? [0, 0, 0, 0] : [200, 100, 50, 255] - ); - expect(shouldPadImage(data)).toBe('padded'); - }); - - it('returns padded when the right edge is transparent', () => { - const data = makePixelData((col, _row) => - col === SAMPLE_SIZE - 1 ? [0, 0, 0, 0] : [200, 100, 50, 255] - ); - expect(shouldPadImage(data)).toBe('padded'); - }); - - it('returns padded when the top edge is transparent', () => { - const data = makePixelData((_col, row) => - row === 0 ? [0, 0, 0, 0] : [200, 100, 50, 255] - ); - expect(shouldPadImage(data)).toBe('padded'); - }); - - it('returns padded when the bottom edge is transparent', () => { - const data = makePixelData((_col, row) => - row === SAMPLE_SIZE - 1 ? [0, 0, 0, 0] : [200, 100, 50, 255] - ); - expect(shouldPadImage(data)).toBe('padded'); - }); - - it('returns padded when any corner is transparent', () => { - const data = makePixelData((col, row) => - col === 0 && row === 0 ? [0, 0, 0, 0] : [200, 100, 50, 255] - ); - expect(shouldPadImage(data)).toBe('padded'); - }); -}); - -describe('dominantColor', () => { - it('returns the dominant color of a uniform image', () => { - const data = makePixelData(() => [0, 100, 200, 255]); - expect(dominantColor(data)).toBe('#0064c8'); - }); - - it('returns null for all-transparent data', () => { - const data = makePixelData(() => [0, 0, 0, 0]); - expect(dominantColor(data)).toBeNull(); - }); - - it('prefers chromatic over grayscale when both exist', () => { - const data = makePixelData((col, _row) => - col < 6 ? [128, 128, 128, 255] : [0, 0, 200, 255] - ); - const result = dominantColor(data); - expect(result).not.toBeNull(); - const b = parseInt(result!.slice(5, 7), 16); - expect(b).toBeGreaterThan(150); - }); -}); - -describe('darkenUntilContrasts', () => { - it('returns a color with sufficient contrast against the original', () => { - const result = darkenUntilContrasts('#888888', 'light'); - const contrast = color(result).contrast(color('#888888')); - expect(contrast).toBeGreaterThanOrEqual(MIN_EDGE_CONTRAST); - }); - - it('lightens near-black colors to reach contrast', () => { - const result = darkenUntilContrasts('#0A0A0A', 'light'); - const contrast = color(result).contrast(color('#0A0A0A')); - expect(contrast).toBeGreaterThanOrEqual(MIN_EDGE_CONTRAST); - expect(color(result).luminosity()).toBeGreaterThan(color('#0A0A0A').luminosity()); - }); -}); - -describe('toPageOriginIfSameHost', () => { - it('coerces same-host different-protocol URLs to page origin', () => { - const result = toPageOriginIfSameHost('https://localhost/avatar/123'); - expect(result).toBe('http://localhost/avatar/123'); - }); - - it('leaves truly cross-origin URLs untouched', () => { - const result = toPageOriginIfSameHost('https://gravatar.com/avatar/abc'); - expect(result).toBe('https://gravatar.com/avatar/abc'); - }); - - it('returns the input for malformed URLs', () => { - const result = toPageOriginIfSameHost('not a url at all'); - expect(typeof result).toBe('string'); - }); -}); diff --git a/static/packages/scraps/src/avatarButton/avatarImageAnalysis.ts b/static/packages/scraps/src/avatarButton/avatarImageAnalysis.ts deleted file mode 100644 index 50bd0e720556..000000000000 --- a/static/packages/scraps/src/avatarButton/avatarImageAnalysis.ts +++ /dev/null @@ -1,289 +0,0 @@ -import type {Theme} from '@emotion/react'; -import color from 'color'; - -export const SAMPLE_SIZE = 12; - -// WCAG 1.4.11 (non-text contrast) recommends a 3:1 minimum for UI components -// like borders against their adjacent content. -export const MIN_EDGE_CONTRAST = 3; - -// TS can't prove typed-array index access is defined; this centralizes the -// single suppression so callers stay clean. -// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -const px = (data: Uint8ClampedArray, i: number): number => data[i]!; - -// Returns 'fill' when the image covers the full frame edge-to-edge, 'padded' otherwise. -// Each edge check returns 'padded' when every pixel on that edge is transparent (alpha < 128). -// Pixel (col, row) has its alpha channel at (row * 12 + col) * 4 + 3 in a 12×12 RGBA canvas. -export function shouldPadImage(data: Uint8ClampedArray): 'fill' | 'padded' { - // oxfmt-ignore - if (!(px(data,3)>=128 || px(data,51)>=128 || px(data,99)>=128 || - px(data,147)>=128 || px(data,195)>=128 || px(data,243)>=128 || - px(data,291)>=128 || px(data,339)>=128 || px(data,387)>=128 || - px(data,435)>=128 || px(data,483)>=128 || px(data,531)>=128)) {return 'padded';} - // oxfmt-ignore - if (!(px(data,47)>=128 || px(data,95)>=128 || px(data,143)>=128 || - px(data,191)>=128 || px(data,239)>=128 || px(data,287)>=128 || - px(data,335)>=128 || px(data,383)>=128 || px(data,431)>=128 || - px(data,479)>=128 || px(data,527)>=128 || px(data,575)>=128)) {return 'padded';} - // oxfmt-ignore - if (!(px(data,3)>=128 || px(data,7)>=128 || px(data,11)>=128 || - px(data,15)>=128 || px(data,19)>=128 || px(data,23)>=128 || - px(data,27)>=128 || px(data,31)>=128 || px(data,35)>=128 || - px(data,39)>=128 || px(data,43)>=128 || px(data,47)>=128)) {return 'padded';} - // oxfmt-ignore - if (!(px(data,531)>=128 || px(data,535)>=128 || px(data,539)>=128 || - px(data,543)>=128 || px(data,547)>=128 || px(data,551)>=128 || - px(data,555)>=128 || px(data,559)>=128 || px(data,563)>=128 || - px(data,567)>=128 || px(data,571)>=128 || px(data,575)>=128)) {return 'padded';} - if ( - px(data, 3) < 128 || - px(data, 47) < 128 || - px(data, 531) < 128 || - px(data, 575) < 128 - ) { - return 'padded'; - } - - return 'fill'; -} - -function readPixels(img: HTMLImageElement): Uint8ClampedArray | null { - try { - const canvas = document.createElement('canvas'); - canvas.width = SAMPLE_SIZE; - canvas.height = SAMPLE_SIZE; - const ctx = canvas.getContext('2d'); - if (!ctx) { - return null; - } - - const naturalW = img.naturalWidth || img.width; - const naturalH = img.naturalHeight || img.height; - let drawW = SAMPLE_SIZE; - let drawH = SAMPLE_SIZE; - let offsetX = 0; - let offsetY = 0; - if (naturalW > 0 && naturalH > 0) { - const scale = Math.min(SAMPLE_SIZE / naturalW, SAMPLE_SIZE / naturalH); - drawW = naturalW * scale; - drawH = naturalH * scale; - offsetX = (SAMPLE_SIZE - drawW) / 2; - offsetY = (SAMPLE_SIZE - drawH) / 2; - } - - ctx.drawImage(img, offsetX, offsetY, drawW, drawH); - return ctx.getImageData(0, 0, SAMPLE_SIZE, SAMPLE_SIZE).data; - } catch { - return null; - } -} - -// Finds the dominant vibrant color by bucketing pixels into coarse RGB -// buckets, preferring chromatic buckets (saturation ≥ 0.15) over grayscale. -// Among tied-for-largest buckets, picks the most saturated one. -const DOMINANT_BUCKET_SIZE = 24; -const DOMINANT_TIE_THRESHOLD = 0.8; - -function quantizeChannel(v: number): number { - return Math.round(v / DOMINANT_BUCKET_SIZE) * DOMINANT_BUCKET_SIZE; -} - -function saturationOf(r: number, g: number, b: number): number { - return (Math.max(r, g, b) - Math.min(r, g, b)) / 255; -} - -export function dominantColor(data: Uint8ClampedArray): string | null { - let hasChromatic = false; - for (let i = 0; i < data.length; i += 4) { - if (px(data, i + 3) < 128) { - continue; - } - const r = px(data, i), - g = px(data, i + 1), - b = px(data, i + 2); - if (saturationOf(r, g, b) >= 0.15) { - hasChromatic = true; - break; - } - } - - const buckets = new Map(); - - for (let i = 0; i < data.length; i += 4) { - if (px(data, i + 3) < 128) { - continue; - } - const r = px(data, i), - g = px(data, i + 1), - b = px(data, i + 2); - - const chromatic = saturationOf(r, g, b) >= 0.15; - if (hasChromatic && !chromatic) { - continue; - } - - const key = `${quantizeChannel(r)},${quantizeChannel(g)},${quantizeChannel(b)}`; - const bucket = buckets.get(key); - if (bucket) { - bucket.r += r; - bucket.g += g; - bucket.b += b; - bucket.count++; - } else { - buckets.set(key, {r, g, b, count: 1}); - } - } - - if (buckets.size === 0) { - return null; - } - - let topCount = 0; - for (const bucket of buckets.values()) { - topCount = Math.max(topCount, bucket.count); - } - - let dominant: {b: number; count: number; g: number; r: number} | null = null; - let dominantSaturation = -1; - for (const bucket of buckets.values()) { - if (bucket.count < topCount * DOMINANT_TIE_THRESHOLD) { - continue; - } - - const saturation = saturationOf( - bucket.r / bucket.count, - bucket.g / bucket.count, - bucket.b / bucket.count - ); - if (saturation > dominantSaturation) { - dominant = bucket; - dominantSaturation = saturation; - } - } - - if (!dominant) { - return null; - } - - const {count} = dominant; - const toHex = (v: number) => - Math.round(v / count) - .toString(16) - .padStart(2, '0'); - return `#${toHex(dominant.r)}${toHex(dominant.g)}${toHex(dominant.b)}`; -} - -function sampleAvatarColor( - img: HTMLImageElement -): {chonk: string | null; style: 'fill' | 'padded'} | null { - const data = readPixels(img); - if (!data) { - return null; - } - - return { - style: shouldPadImage(data), - chonk: dominantColor(data), - }; -} - -function loadImageElement(src: string): Promise { - return new Promise(resolve => { - const img = new Image(); - img.onload = () => resolve(img); - img.onerror = () => resolve(null); - img.src = src; - }); -} - -// Avatar URLs are absolute, built from the `system.url-prefix` option on the -// backend — in local dev that's plain http, but self-uploaded avatars can -// still come back as https. Coerce it back to the page's real origin so it's -// a genuine same-origin request. Truly cross-origin URLs are left untouched. -export function toPageOriginIfSameHost(url: string): string { - try { - const parsed = new URL(url, window.location.origin); - if ( - parsed.hostname === window.location.hostname && - parsed.protocol !== window.location.protocol - ) { - return `${window.location.origin}${parsed.pathname}${parsed.search}${parsed.hash}`; - } - return parsed.toString(); - } catch { - return url; - } -} - -async function fetchAvatarColor( - url: string -): Promise | null> { - let objectUrl: string | undefined; - try { - const response = await fetch(toPageOriginIfSameHost(url), { - mode: 'cors', - credentials: 'omit', - }); - if (!response.ok) { - return null; - } - - const blob = await response.blob(); - objectUrl = URL.createObjectURL(blob); - - const img = await loadImageElement(objectUrl); - return img ? sampleAvatarColor(img) : null; - } catch { - return null; - } finally { - if (objectUrl) { - URL.revokeObjectURL(objectUrl); - } - } -} - -// Shifts `hex` away from itself until it clears MIN_EDGE_CONTRAST. Tries -// darkening first; if the color is too close to black to reach 3:1 that way, -// falls back to lightening. -export function darkenUntilContrasts(hex: string, theme: Theme['type']): string { - const reference = color(hex); - let candidate = color(hex); - const step = theme === 'dark' ? 0.2 : 0.12; - - for (let i = 0; i < 12 && candidate.contrast(reference) < MIN_EDGE_CONTRAST; i++) { - candidate = candidate.darken(step); - } - - if (candidate.contrast(reference) >= MIN_EDGE_CONTRAST) { - return candidate.hex(); - } - - // Near-black: darkening bottoms out, use absolute lightness steps instead. - candidate = color(hex); - for (let i = 0; i < 12 && candidate.contrast(reference) < MIN_EDGE_CONTRAST; i++) { - candidate = candidate.lightness(candidate.lightness() + 5); - } - - return candidate.hex(); -} - -export async function resolveImageAvatarColors( - url: string, - theme: Theme['type'] -): Promise<{chonk: string | undefined; style: 'fill' | 'padded'} | null> { - const sampled = await fetchAvatarColor(url); - if (!sampled?.chonk) { - return null; - } - - // Padded logos sit on their own background — use the dominant color as-is. - // Full-bleed images need the chonk to contrast against the image's own - // dominant color so the border reads clearly. - const chonk = - sampled.style === 'padded' - ? sampled.chonk - : darkenUntilContrasts(sampled.chonk, theme); - - return {chonk, style: sampled.style}; -} diff --git a/static/packages/scraps/src/badge/projectsBadge.tsx b/static/packages/scraps/src/badge/projectsBadge.tsx deleted file mode 100644 index df760c60092d..000000000000 --- a/static/packages/scraps/src/badge/projectsBadge.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import {Fragment} from 'react'; -import {IconAllProjects} from '@sentry/icons/allProjects'; -import {IconMyProjects} from '@sentry/icons/myProjects'; -import {PlatformIcon} from 'platformicons'; - -import {Container, Stack} from '../layout'; - -type ProjectsBadgeSize = 'md' | 'lg'; - -/** - * Geometry per size, read off the spec frames rather than scaled from the - * edge: the two sizes do not share one ratio, and at `md` the contents fill - * the frame while at `lg` they sit inside it. - * - * `single` is one platform icon, centred in the frame. `stacked` is each of - * the two overlapping ones, which start at opposite corners — so their offset - * is whatever the frame has left over. - * - * `radius` is passed to the icon, which rounds itself. Its own default of 3 - * suits `md`; `lg` is drawn a little softer. - */ -const SIZES = { - md: {frame: 16, single: 16, stacked: 11, radius: 3}, - lg: {frame: 24, single: 20, stacked: 16, radius: 4}, -} as const satisfies Record< - ProjectsBadgeSize, - {frame: number; radius: number; single: number; stacked: number} ->; - -export interface ProjectsBadgeProps { - /** - * Platform slugs for the project(s) to display. - * - 0 entries: renders an all-projects or my-projects icon - * - 1 entry: renders a single platform icon - * - 2+ entries: renders two overlapping platform icons (top-left + bottom-right) - */ - projectPlatforms: string[]; - /** When projectPlatforms is empty, use all-projects icon instead of my-projects */ - allProjects?: boolean; - /** - * `md` (16px) suits a crumb or a nav row; `lg` (24px) a page header, where it - * sits against 16px type and the avatars beside it. - * @default 'md' - */ - size?: ProjectsBadgeSize; -} - -/** - * A square badge representing the project(s) tied to something — a starred - * project, saved query, dashboard, issue view, or the entity a page is about. - * Absorbs the 0/1/2+ platform logic so every call site shares one component. - * - * The badge is decorative: it names a platform, not a project, so it cannot say - * *which* projects on its own. A caller that needs that gives the surrounding - * element an accessible name. - */ -export function ProjectsBadge({ - projectPlatforms, - allProjects, - size = 'md', -}: ProjectsBadgeProps) { - const {frame, single, stacked, radius} = SIZES[size]; - const stackedOffset = frame - stacked; - - let icons: React.ReactNode; - - switch (projectPlatforms.length) { - case 0: { - const Icon = allProjects ? IconAllProjects : IconMyProjects; - icons = - size === 'md' ? ( -