diff --git a/apps/site/components/MDX/Image/index.tsx b/apps/site/components/MDX/Image/index.tsx index 844a9596acf0b..e2e39f1727213 100644 --- a/apps/site/components/MDX/Image/index.tsx +++ b/apps/site/components/MDX/Image/index.tsx @@ -2,11 +2,7 @@ import type { ImageProps } from 'next/image'; import Image from 'next/image'; import type { FC } from 'react'; -import { isSvgImage } from '#site/util/imageUtils'; - const MDXImage: FC = ({ width, height, alt, src, ...props }) => { - const isUnoptimizedImage = isSvgImage(src.toString()); - if (!width || !height) { // Since `width` and `height` are not provided in the Markdown image format, // we provide the height and width automatically. @@ -15,7 +11,6 @@ const MDXImage: FC = ({ width, height, alt, src, ...props }) => { {alt} = ({ width, height, alt, src, ...props }) => { ); } - return ( - {alt} - ); + return {alt}; }; export default MDXImage; diff --git a/apps/site/components/withAvatarGroup.tsx b/apps/site/components/withAvatarGroup.tsx index 0c82aabe74c07..fc0569877c50f 100644 --- a/apps/site/components/withAvatarGroup.tsx +++ b/apps/site/components/withAvatarGroup.tsx @@ -1,7 +1,6 @@ 'use client'; import AvatarGroup from '@node-core/ui-components/Common/AvatarGroup'; -import Image from 'next/image'; import type { ComponentProps, FC } from 'react'; import Link from '#site/components/Link'; @@ -27,7 +26,6 @@ const WithAvatarGroup: FC = ({ clickable: clickable, })} as={Link} - img={Image} {...props} /> ); diff --git a/apps/site/next.config.mjs b/apps/site/next.config.mjs index ce4f056acb9f5..d3a0778633722 100644 --- a/apps/site/next.config.mjs +++ b/apps/site/next.config.mjs @@ -88,6 +88,7 @@ const nextConfig = { '@radix-ui/react-tabs', '@radix-ui/react-toast', '@radix-ui/react-tooltip', + '@radix-ui/react-avatar', '@orama/highlight', '@orama/react-components', '@heroicons/react', diff --git a/apps/site/util/__tests__/imageUtils.test.mjs b/apps/site/util/__tests__/imageUtils.test.mjs deleted file mode 100644 index 76174a7b4e23e..0000000000000 --- a/apps/site/util/__tests__/imageUtils.test.mjs +++ /dev/null @@ -1,56 +0,0 @@ -import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; - -import { isSvgImage } from '#site/util/imageUtils'; -describe('isSvgImage', () => { - const testCases = [ - { - description: 'should return true for a valid .svg URL', - input: 'https://nodejs.org/image.svg', - expected: true, - }, - { - description: 'should return true for a URL with query params', - input: 'https://nodejs.org/image.svg?query=param', - expected: true, - }, - { - description: 'should return false for a URL without a .svg extension', - input: 'https://nodejs.org/image', - expected: false, - }, - { - description: - 'should return false for a URL containing ".svg" but not ending with it', - input: 'https://nodejs.org/image.svg.png', - expected: false, - }, - { - description: 'should return false for an empty string', - input: '', - expected: false, - }, - { - description: 'should return false for a non-URL string', - input: 'not-a-url', - expected: false, - }, - ]; - - testCases.forEach(({ description, input, expected }) => { - it(description, () => { - assert.equal(isSvgImage(input), expected); - }); - }); -}); - -describe('imageUtils', () => { - describe('isSvgImage', () => { - it('should detect .svg extension properly', () => { - assert.equal(isSvgImage('icon.svg'), true); - }); - it('should return false for non-svg source', () => { - assert.equal(isSvgImage('logo.png'), false); - }); - }); -}); diff --git a/apps/site/util/imageUtils.ts b/apps/site/util/imageUtils.ts deleted file mode 100644 index d75193e8c561b..0000000000000 --- a/apps/site/util/imageUtils.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This is a temporary workaround that can be removed once Next.js is upgraded. - * See https://github.com/vercel/next.js/pull/72970 - * - * Checks if the given source string points to an SVG image. - * - * This function examines the base part of the provided string (ignoring query parameters) - * to determine if it ends with the `.svg` extension. - * - * @param src - The URL or string representing the source of the image. - * @returns `true` if the source points to an SVG image, otherwise `false`. - */ -export const isSvgImage = (src: string): boolean => { - // Split the source string at the '?' character to separate the main path from query parameters - const [image] = src.split('?'); - - // Check if the base path (before any query parameters) ends with '.svg' - return image.endsWith('.svg'); -}; diff --git a/packages/ui-components/Common/AvatarGroup/Avatar/index.tsx b/packages/ui-components/Common/AvatarGroup/Avatar/index.tsx index 666dca159918a..46f0ac699c4f0 100644 --- a/packages/ui-components/Common/AvatarGroup/Avatar/index.tsx +++ b/packages/ui-components/Common/AvatarGroup/Avatar/index.tsx @@ -1,5 +1,6 @@ +import * as RadixAvatar from '@radix-ui/react-avatar'; import classNames from 'classnames'; -import type { HTMLAttributes, ElementType } from 'react'; +import type { HTMLAttributes } from 'react'; import { forwardRef } from 'react'; import type { LinkLike } from '#ui/types'; @@ -14,12 +15,8 @@ export type AvatarProps = { size?: 'small' | 'medium'; url?: string; as?: LinkLike | 'div'; - img?: ElementType | 'img'; }; -// @TODO: We temporarily removed the Avatar Radix UI primitive, since it was causing flashing -// during initial load and not being able to render nicely when images are already cached. -// @see https://github.com/radix-ui/primitives/pull/3008 const Avatar = forwardRef< HTMLSpanElement, HTMLAttributes & AvatarProps @@ -33,14 +30,13 @@ const Avatar = forwardRef< url, size = 'small', as: Component = 'a', - img: Image = 'img', ...props }, ref ) => { if (!url) Component = 'div'; return ( - - {image && ( - {name - )} - - {!image && ( - - {fallback} - - )} + + + {fallback} + - + ); } ); diff --git a/packages/ui-components/Common/AvatarGroup/Overlay/index.tsx b/packages/ui-components/Common/AvatarGroup/Overlay/index.tsx index 2b91a8bd21143..a281e1cdf18e1 100644 --- a/packages/ui-components/Common/AvatarGroup/Overlay/index.tsx +++ b/packages/ui-components/Common/AvatarGroup/Overlay/index.tsx @@ -16,7 +16,6 @@ const AvatarOverlay: FC = ({ fallback, url, as: Component = 'a', - img, }) => ( = ({ nickname={nickname} fallback={fallback} size="medium" - img={img} />
diff --git a/packages/ui-components/Common/AvatarGroup/index.tsx b/packages/ui-components/Common/AvatarGroup/index.tsx index 51d08744f61b3..53bc368393bb7 100644 --- a/packages/ui-components/Common/AvatarGroup/index.tsx +++ b/packages/ui-components/Common/AvatarGroup/index.tsx @@ -1,7 +1,7 @@ 'use client'; import classNames from 'classnames'; -import type { FC, ElementType } from 'react'; +import type { FC } from 'react'; import { useState, useMemo } from 'react'; import type { AvatarProps } from '#ui/Common/AvatarGroup/Avatar'; @@ -20,7 +20,6 @@ type AvatarGroupProps = { size?: AvatarProps['size']; container?: HTMLElement; as?: LinkLike; - img?: ElementType | 'img'; }; const AvatarGroup: FC = ({ @@ -30,7 +29,6 @@ const AvatarGroup: FC = ({ size = 'small', container, as, - img, }) => { const [showMore, setShowMore] = useState(false); @@ -54,7 +52,7 @@ const AvatarGroup: FC = ({ key={avatar.nickname} asChild container={container} - content={} + content={} > = ({ 'pointer-events-none': !avatar.url, })} as={as} - img={img} /> ))} diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index ea4952e3d4738..1a4d7ca65fa5c 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@heroicons/react": "^2.2.0", + "@radix-ui/react-avatar": "^1.1.9", "@radix-ui/react-dialog": "^1.1.7", "@radix-ui/react-dropdown-menu": "~2.1.6", "@radix-ui/react-label": "~2.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3fe1dfb43b960..4bef89954622a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -322,6 +322,9 @@ importers: '@heroicons/react': specifier: ^2.2.0 version: 2.2.0(react@19.1.0) + '@radix-ui/react-avatar': + specifier: ^1.1.9 + version: 1.1.9(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-dialog': specifier: ^1.1.7 version: 1.1.11(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -2063,6 +2066,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-avatar@1.1.9': + resolution: {integrity: sha512-10tQokfvZdFvnvDkcOJPjm2pWiP8A0R4T83MoD7tb15bC/k2GU7B1YBuzJi8lNQ8V1QqhP8ocNqp27ByZaNagQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.4': resolution: {integrity: sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg==} peerDependencies: @@ -2251,6 +2267,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.1.2': + resolution: {integrity: sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-roving-focus@1.1.7': resolution: {integrity: sha512-C6oAg451/fQT3EGbWHbCQjYTtbyjNO1uzQgMzwyivcHT3GKNEmu1q3UuREhN+HzHAVtv3ivMVK08QlC+PkYw9Q==} peerDependencies: @@ -2299,6 +2328,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.2.2': + resolution: {integrity: sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-tabs@1.1.9': resolution: {integrity: sha512-KIjtwciYvquiW/wAFkELZCVnaNLBsYNhTNcvl+zfMAbMhRkcvNuCLXDDd22L0j7tagpzVh/QwbFpwAATg7ILPw==} peerDependencies: @@ -2374,6 +2412,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-layout-effect@1.1.1': resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} peerDependencies: @@ -7563,6 +7610,11 @@ packages: '@types/react': optional: true + use-sync-external-store@1.5.0: + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + user-agent-data-types@0.4.2: resolution: {integrity: sha512-jXep3kO/dGNmDOkbDa8ccp4QArgxR4I76m3QVcJ1aOF0B9toc+YtSXtX5gLdDTZXyWlpQYQrABr6L1L2GZOghw==} @@ -9941,6 +9993,18 @@ snapshots: optionalDependencies: '@types/react': 19.1.2 + '@radix-ui/react-avatar@1.1.9(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.2(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.2 + '@radix-ui/react-collection@1.1.4(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) @@ -10116,6 +10180,14 @@ snapshots: optionalDependencies: '@types/react': 19.1.2 + '@radix-ui/react-primitive@2.1.2(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-slot': 1.2.2(@types/react@19.1.2)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.2 + '@radix-ui/react-roving-focus@1.1.7(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -10175,6 +10247,13 @@ snapshots: optionalDependencies: '@types/react': 19.1.2 + '@radix-ui/react-slot@1.2.2(@types/react@19.1.2)(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.2 + '@radix-ui/react-tabs@1.1.9(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -10256,6 +10335,13 @@ snapshots: optionalDependencies: '@types/react': 19.1.2 + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.1.2)(react@19.1.0)': + dependencies: + react: 19.1.0 + use-sync-external-store: 1.5.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.2 + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.2)(react@19.1.0)': dependencies: react: 19.1.0 @@ -12688,7 +12774,7 @@ snapshots: tinyglobby: 0.2.13 unrs-resolver: 1.7.2 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.31.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@4.3.4)(eslint@9.26.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(eslint-import-resolver-typescript@4.3.4)(eslint@9.26.0(jiti@2.4.2)) eslint-plugin-import-x: 4.11.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) transitivePeerDependencies: - supports-color @@ -12716,17 +12802,28 @@ snapshots: - bluebird - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.31.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.26.0(jiti@2.4.2)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.31.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.3.4)(eslint@9.26.0(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.31.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.26.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.11.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-plugin-import@2.31.0)(eslint@9.26.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 4.3.4(eslint-plugin-import-x@4.11.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-plugin-import@2.31.0)(eslint@9.26.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color + eslint-module-utils@2.12.0(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.3.4)(eslint@9.26.0(jiti@2.4.2)): + dependencies: + debug: 3.2.7 + optionalDependencies: + eslint: 9.26.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 4.3.4(eslint-plugin-import-x@4.11.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-plugin-import@2.31.0)(eslint@9.26.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + optional: true + eslint-plugin-import-x@4.11.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3): dependencies: '@typescript-eslint/utils': 8.31.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) @@ -12756,7 +12853,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.26.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.31.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.26.0(jiti@2.4.2)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.31.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.3.4)(eslint@9.26.0(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -12774,6 +12871,34 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-import@2.31.0(eslint-import-resolver-typescript@4.3.4)(eslint@9.26.0(jiti@2.4.2)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.26.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.3.4)(eslint@9.26.0(jiti@2.4.2)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + optional: true + eslint-plugin-jsx-a11y@6.10.2(eslint@9.26.0(jiti@2.4.2)): dependencies: aria-query: 5.3.2 @@ -16936,6 +17061,10 @@ snapshots: optionalDependencies: '@types/react': 19.1.2 + use-sync-external-store@1.5.0(react@19.1.0): + dependencies: + react: 19.1.0 + user-agent-data-types@0.4.2: {} util-deprecate@1.0.2: {}