diff --git a/packages/stl/src/lib/react/useResizeObserver/useResizeObserver.test.ts b/packages/stl/src/lib/react/useResizeObserver/useResizeObserver.test.ts index 5cebd97..ac3fd38 100644 --- a/packages/stl/src/lib/react/useResizeObserver/useResizeObserver.test.ts +++ b/packages/stl/src/lib/react/useResizeObserver/useResizeObserver.test.ts @@ -1,15 +1,79 @@ -import { renderHook } from '@testing-library/react'; -import React from 'react'; +import { renderHook, act } from '@testing-library/react'; +import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; import { useResizeObserver } from './useResizeObserver'; describe('useResizeObserver', () => { + let observerCallback: ResizeObserverCallback; + const observeMock = vi.fn(); + const unobserveMock = vi.fn(); + const disconnectMock = vi.fn(); + + beforeEach(() => { + class MockResizeObserver { + constructor(callback: ResizeObserverCallback) { + observerCallback = callback; + } + observe = observeMock; + unobserve = unobserveMock; + disconnect = disconnectMock; + } + + vi.stubGlobal('ResizeObserver', MockResizeObserver); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + vi.clearAllMocks(); + }); + it('should return initial dimensions', () => { - const ref = React.createRef(); + const ref = { current: document.createElement('div') }; const { result } = renderHook(() => useResizeObserver(ref)); expect(result.current).toEqual({ width: 0, height: 0 }); }); - // Testing the actual resize observation is tricky in JSDOM. - // A more complete test would require a full browser environment. + it('should call observe on mount', () => { + const element = document.createElement('div'); + const ref = { current: element }; + renderHook(() => useResizeObserver(ref)); + + expect(observeMock).toHaveBeenCalledWith(element); + }); + + it('should update dimensions when ResizeObserver triggers', () => { + const element = document.createElement('div'); + const ref = { current: element }; + const { result } = renderHook(() => useResizeObserver(ref)); + + const entries = [ + { + contentRect: { width: 100, height: 200 }, + target: element, + }, + ] as unknown as ResizeObserverEntry[]; + + act(() => { + observerCallback(entries, {} as ResizeObserver); + }); + + expect(result.current).toEqual({ width: 100, height: 200 }); + }); + + it('should call disconnect on unmount', () => { + const element = document.createElement('div'); + const ref = { current: element }; + const { unmount } = renderHook(() => useResizeObserver(ref)); + + unmount(); + + expect(disconnectMock).toHaveBeenCalled(); + }); + + it('should not observe if ref is null', () => { + const ref = { current: null }; + renderHook(() => useResizeObserver(ref)); + + expect(observeMock).not.toHaveBeenCalled(); + }); }); diff --git a/packages/stl/src/lib/react/useResizeObserver/useResizeObserver.ts b/packages/stl/src/lib/react/useResizeObserver/useResizeObserver.ts index b93c639..02f966c 100644 --- a/packages/stl/src/lib/react/useResizeObserver/useResizeObserver.ts +++ b/packages/stl/src/lib/react/useResizeObserver/useResizeObserver.ts @@ -49,7 +49,7 @@ export const useResizeObserver = ( resizeObserver.observe(observeTarget); return () => { - resizeObserver.unobserve(observeTarget); + resizeObserver.disconnect(); }; }, [ref]); diff --git a/packages/stl/src/lib/react/useScroll/useScroll.test.ts b/packages/stl/src/lib/react/useScroll/useScroll.test.ts index 1876e52..5e41c4a 100644 --- a/packages/stl/src/lib/react/useScroll/useScroll.test.ts +++ b/packages/stl/src/lib/react/useScroll/useScroll.test.ts @@ -1,17 +1,35 @@ import { renderHook, act } from '@testing-library/react'; -import { vi, describe, it, expect } from 'vitest'; +import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; import { useScroll } from './useScroll'; describe('useScroll', () => { + const originalScrollX = window.scrollX; + const originalScrollY = window.scrollY; + + beforeEach(() => { + vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => { + return setTimeout(callback, 0); + }); + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + vi.useRealTimers(); + window.scrollX = originalScrollX; + window.scrollY = originalScrollY; + }); + it('should return initial scroll position', () => { + window.scrollX = 50; + window.scrollY = 150; const { result } = renderHook(() => useScroll()); - expect(result.current).toEqual({ x: 0, y: 0 }); + expect(result.current).toEqual({ x: 50, y: 150 }); }); it('should update scroll position on scroll', () => { - vi.useFakeTimers(); - renderHook(() => useScroll()); + const { result } = renderHook(() => useScroll()); act(() => { window.scrollX = 100; @@ -19,13 +37,34 @@ describe('useScroll', () => { window.dispatchEvent(new Event('scroll')); }); - // We need to wait for the next animation frame + // Wait for requestAnimationFrame (mocked via setTimeout) act(() => { vi.runAllTimers(); }); - // In JSDOM, window.scrollX/Y might not persist the manual assignment - // depending on the environment setup, but we'll try to verify it. - vi.useRealTimers(); + expect(result.current).toEqual({ x: 100, y: 200 }); + }); + + it('should throttle updates using requestAnimationFrame', () => { + const { result } = renderHook(() => useScroll()); + + act(() => { + window.scrollX = 100; + window.scrollY = 200; + window.dispatchEvent(new Event('scroll')); + + window.scrollX = 300; + window.scrollY = 400; + window.dispatchEvent(new Event('scroll')); + }); + + // Before timers run, it should still be initial or first set if not throttled correctly + // But since we trigger twice in same tick, ticking.current prevents second RAF. + + act(() => { + vi.runAllTimers(); + }); + + expect(result.current).toEqual({ x: 300, y: 400 }); }); }); diff --git a/packages/stl/src/lib/react/useScroll/useScroll.ts b/packages/stl/src/lib/react/useScroll/useScroll.ts index d5ebf06..8753be3 100644 --- a/packages/stl/src/lib/react/useScroll/useScroll.ts +++ b/packages/stl/src/lib/react/useScroll/useScroll.ts @@ -19,7 +19,10 @@ export interface UseScrollResult { * ``` */ export const useScroll = (): UseScrollResult => { - const [scroll, setScroll] = useState({ x: 0, y: 0 }); + const [scroll, setScroll] = useState(() => ({ + x: typeof window !== 'undefined' ? window.scrollX : 0, + y: typeof window !== 'undefined' ? window.scrollY : 0, + })); const ticking = useRef(false); useEffect(() => { diff --git a/packages/ui/src/components/Article/Article.tsx b/packages/ui/src/components/Article/Article.tsx index ab52085..84398df 100644 --- a/packages/ui/src/components/Article/Article.tsx +++ b/packages/ui/src/components/Article/Article.tsx @@ -6,8 +6,8 @@ import { Separator } from '../Separator/Separator'; import { Text } from '../Text/Text'; import { VStack } from '../VStack/VStack'; -import type { ClassNameProps } from '@ermnvldmr/stl'; import type { ContainerProps } from '../Container/Container'; +import type { ClassNameProps } from '@ermnvldmr/stl'; /** * Props for the Article component. @@ -21,12 +21,12 @@ export interface ArticleProps extends ClassNameProps { additionalText?: string; /** * Press interaction handler. - * @note Cannot be used with `href`. + * Cannot be used with `href`. */ onPress?: ContainerProps['onPress']; /** * If provided, the container will be rendered as an `` tag for navigation. - * @note Cannot be used with `onPress`. + * Cannot be used with `onPress`. */ href?: ContainerProps['href']; /** The main article body content */ @@ -54,7 +54,7 @@ export const Article = memo(function Article({ className, }: ArticleProps) { return ( - +
{headline}
diff --git a/packages/ui/src/components/Code/Code.test.tsx b/packages/ui/src/components/Code/Code.test.tsx index 80650e0..51926c9 100644 --- a/packages/ui/src/components/Code/Code.test.tsx +++ b/packages/ui/src/components/Code/Code.test.tsx @@ -8,7 +8,9 @@ import { CodeBlock } from './CodeBlock/CodeBlock'; // Mock shiki vi.mock('shiki', () => ({ createHighlighter: vi.fn().mockResolvedValue({ - codeToHtml: vi.fn().mockReturnValue('
highlighted code
'), + codeToHtml: vi + .fn() + .mockReturnValue('
highlighted code
'), }), })); @@ -49,18 +51,18 @@ describe('Code Component', () => { expect(screen.getByText('test.ts')).toBeInTheDocument(); }); - it('copies content on click in inline mode', async () => { + it('copies content on click in inline mode', () => { render(Display Text); const codeElement = screen.getByText('Display Text'); - await act(async () => { + act(() => { fireEvent.click(codeElement); }); expect(mockWriteText).toHaveBeenCalledWith('copied!'); }); - it('copies content via button in block mode', async () => { + it('copies content via button in block mode', () => { render( @@ -70,18 +72,18 @@ describe('Code Component', () => { ); const copyButton = screen.getByLabelText('Copy code'); - await act(async () => { + act(() => { fireEvent.click(copyButton); }); expect(mockWriteText).toHaveBeenCalledWith('block copy'); }); - it('extracts text from children if copyValue is not provided', async () => { + it('extracts text from children if copyValue is not provided', () => { render(extracted); const codeElement = screen.getByText('extracted'); - await act(async () => { + act(() => { fireEvent.click(codeElement); }); @@ -89,7 +91,7 @@ describe('Code Component', () => { }); it('uses shiki for highlighting when language is provided in block mode', async () => { - await act(async () => { + act(() => { render( const x: number = 1; diff --git a/packages/ui/src/components/Code/Code.tsx b/packages/ui/src/components/Code/Code.tsx index 30e874d..6b55252 100644 --- a/packages/ui/src/components/Code/Code.tsx +++ b/packages/ui/src/components/Code/Code.tsx @@ -5,8 +5,8 @@ import { usePress } from 'react-aria'; import { useCodeContext } from './contexts/CodeContext/CodeContext'; import { codeHighlighter } from './lib/highlighter/highlighter'; -import type { ClassNameProps, TestIdProps } from '@ermnvldmr/stl'; import type { CodeLanguage } from './lib/highlighter/highlighter'; +import type { ClassNameProps, TestIdProps } from '@ermnvldmr/stl'; export type { CodeLanguage }; @@ -47,6 +47,7 @@ export const Code = memo(function Code({ const context = useCodeContext(); const [isCopied, setIsCopied] = useState(false); const codeRef = useRef(null); + const divRef = useRef(null); const [highlightedHtml, setHighlightedHtml] = useState(null); const language = propsLanguage ?? context?.language; @@ -54,10 +55,13 @@ export const Code = memo(function Code({ const handleCopy = useCallback(async () => { try { let textToCopy = copyValue; - if (!textToCopy && codeRef.current) { - textToCopy = codeRef.current.innerText; - if (!textToCopy) { - textToCopy = codeRef.current.textContent ?? undefined; + if (!textToCopy) { + const ref = codeRef.current ?? divRef.current; + if (ref) { + textToCopy = ref.innerText; + if (!textToCopy) { + textToCopy = ref.textContent ?? undefined; + } } } @@ -109,7 +113,9 @@ export const Code = memo(function Code({ if (highlightedHtml) { return ( ), }; + +/** + * Showcases code blocks with language-specific syntax highlighting. + */ +export const CodeLanguages: Story = { + render: () => ( +
+
+ TypeScript + + + {`interface User { + id: number; + name: string; +} + +const greet = (user: User) => { + console.log(\`Hello, \${user.name}!\`); +};`} + + +
+ +
+ Rust + + + {`fn main() { + let name = "World"; + println!("Hello, {}!", name); +}`} + + +
+ +
+ Python + + + {`def greet(name: str): + print(f"Hello, {name}!") + +if __name__ == "__main__": + greet("World")`} + + +
+ +
+ JSON + + + {`{ + "project": "ermnvldmr.com", + "version": "1.0.0", + "private": true +}`} + + +
+
+ ), +}; diff --git a/packages/ui/src/components/Markdown/MarkdownCode/MarkdownCode.tsx b/packages/ui/src/components/Markdown/MarkdownCode/MarkdownCode.tsx index 14d4359..581cd64 100644 --- a/packages/ui/src/components/Markdown/MarkdownCode/MarkdownCode.tsx +++ b/packages/ui/src/components/Markdown/MarkdownCode/MarkdownCode.tsx @@ -1,10 +1,25 @@ import React, { memo, useMemo } from 'react'; import { Code } from '../../Code/Code'; +import { SUPPORTED_LANGUAGES } from '../../Code/lib/highlighter/highlighter'; import type { CodeProps } from '../../Code/Code'; import type { CodeLanguage } from '../../Code/lib/highlighter/highlighter'; +/** + * Checks if a string is a valid code language. + * @param lang The string to check. + * @returns True if the string is a valid code language. + * @example + * ```typescript + * isCodeLanguage('typescript'); // true + * isCodeLanguage('foo'); // false + * ``` + */ +function isCodeLanguage(lang: string): lang is CodeLanguage { + return SUPPORTED_LANGUAGES.some((l) => l === lang); +} + /** * A Markdown code component for inline snippets. */ @@ -15,7 +30,10 @@ export const MarkdownCode = memo(function MarkdownCode(props: CodeProps) { if (className) { const match = /language-(\w+)/.exec(className); if (match) { - return match[1] as CodeLanguage; + const lang = match[1]; + if (isCodeLanguage(lang)) { + return lang; + } } } return undefined; diff --git a/packages/ui/src/components/Markdown/MarkdownPre/MarkdownPre.tsx b/packages/ui/src/components/Markdown/MarkdownPre/MarkdownPre.tsx index 1752daa..513111d 100644 --- a/packages/ui/src/components/Markdown/MarkdownPre/MarkdownPre.tsx +++ b/packages/ui/src/components/Markdown/MarkdownPre/MarkdownPre.tsx @@ -1,10 +1,25 @@ import React, { memo, useMemo } from 'react'; import { CodeBlock } from '../../Code/CodeBlock/CodeBlock'; +import { SUPPORTED_LANGUAGES } from '../../Code/lib/highlighter/highlighter'; import type { CodeBlockProps } from '../../Code/CodeBlock/CodeBlock'; import type { CodeLanguage } from '../../Code/lib/highlighter/highlighter'; +/** + * Checks if a string is a valid code language. + * @param lang The string to check. + * @returns True if the string is a valid code language. + * @example + * ```typescript + * isCodeLanguage('typescript'); // true + * isCodeLanguage('foo'); // false + * ``` + */ +function isCodeLanguage(lang: string): lang is CodeLanguage { + return SUPPORTED_LANGUAGES.some((l) => l === lang); +} + /** * A Markdown pre component for code blocks. * @@ -20,9 +35,10 @@ export const MarkdownPre = memo(function MarkdownPre(props: CodeBlockProps) { const className = children.props.className; const match = /language-(\w+)/.exec(className); if (match) { - // We cast here because we know it comes from markdown, - // shiki will handle it or fallback to plaintext if unknown at runtime. - return match[1] as CodeLanguage; + const lang = match[1]; + if (isCodeLanguage(lang)) { + return lang; + } } } return undefined; diff --git a/packages/ui/src/components/Separator/Separator.stories.tsx b/packages/ui/src/components/Separator/Separator.stories.tsx index 4c33640..8f12f6a 100644 --- a/packages/ui/src/components/Separator/Separator.stories.tsx +++ b/packages/ui/src/components/Separator/Separator.stories.tsx @@ -12,7 +12,7 @@ import type { Meta, StoryObj } from '@storybook/react'; * - Multiple color options (black, outline, outline-light) with theme support * - Horizontal and vertical orientations * - Optional thinned gradient fade effect at line ends ('thinned', 'thinned-start', 'thinned-end', 'none') - * - 2px stroke width following print design standards + * - 1px stroke width following print design standards * - Semantic div elements with proper ARIA separator role for accessibility */ const meta: Meta = { @@ -64,7 +64,7 @@ type Story = StoryObj; /** * The default single-line separator with light outline color. - * Uses the newspaper-standard 2px stroke with thinned gradient ends. + * Uses the newspaper-standard 1px stroke with thinned gradient ends. */ export const Default: Story = { args: {}, diff --git a/packages/ui/src/components/Separator/Separator.tsx b/packages/ui/src/components/Separator/Separator.tsx index bca0558..36859cf 100644 --- a/packages/ui/src/components/Separator/Separator.tsx +++ b/packages/ui/src/components/Separator/Separator.tsx @@ -89,8 +89,7 @@ export const Separator = memo(function Separator({ ]; // Thinned effect using CSS mask - const thinnedClasses = - thinned !== 'none' ? [THINNED_TYPE_CLASSES[direction][thinned]] : []; + const thinnedClasses = thinned !== 'none' ? [THINNED_TYPE_CLASSES[direction][thinned]] : []; // Size classes - fill container by default const sizeClasses = diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad6ebf5..976ca07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -198,7 +198,7 @@ importers: version: 7.0.1 storybook-react-rsbuild: specifier: ^1.0.3 - version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.15(prettier@3.5.3)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.56.0)(storybook@8.6.15(prettier@3.5.3))(typescript@5.8.3)(webpack@5.105.1) + version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.15(prettier@3.5.3)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.56.0)(storybook@8.6.15(prettier@3.5.3))(typescript@5.8.3)(webpack@5.105.1) typescript: specifier: ^5.8.3 version: 5.8.3 @@ -298,7 +298,7 @@ importers: version: 4.1.18 '@tailwindcss/typography': specifier: ^0.5.19 - version: 0.5.19 + version: 0.5.19(tailwindcss@4.1.18) '@testing-library/jest-dom': specifier: ^6.9.1 version: 6.9.1 @@ -387,6 +387,9 @@ importers: '@tailwindcss/postcss': specifier: ^4.1.18 version: 4.1.18 + '@types/node': + specifier: '*' + version: 25.0.10 '@types/react': specifier: ^19.1.2 version: 19.2.9 @@ -490,6 +493,9 @@ importers: '@testing-library/user-event': specifier: ^14.6.1 version: 14.6.1(@testing-library/dom@10.4.0) + '@types/node': + specifier: '*' + version: 25.0.10 '@types/react': specifier: ^19.1.2 version: 19.2.9 @@ -11305,9 +11311,10 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.18 - '@tailwindcss/typography@0.5.19': + '@tailwindcss/typography@0.5.19(tailwindcss@4.1.18)': dependencies: postcss-selector-parser: 6.0.10 + tailwindcss: 4.1.18 '@testing-library/dom@10.4.0': dependencies: @@ -15796,7 +15803,7 @@ snapshots: - tslib - webpack - storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.15(prettier@3.5.3)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.56.0)(storybook@8.6.15(prettier@3.5.3))(typescript@5.8.3)(webpack@5.105.1): + storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.15(prettier@3.5.3)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.56.0)(storybook@8.6.15(prettier@3.5.3))(typescript@5.8.3)(webpack@5.105.1): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.56.0) '@rsbuild/core': 1.7.3 diff --git a/services/static/package.json b/services/static/package.json index 8c8b289..d4f4224 100644 --- a/services/static/package.json +++ b/services/static/package.json @@ -30,6 +30,7 @@ "postcss": "^8.5.6", "prettier": "^3.5.3", "prettier-plugin-tailwindcss": "^0.6.11", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "@types/node": "*" } } diff --git a/services/www/package.json b/services/www/package.json index 3f7b5be..1991f1a 100644 --- a/services/www/package.json +++ b/services/www/package.json @@ -70,6 +70,7 @@ "ts-node": "^10.9.2", "typescript": "^5.8.3", "vitest": "^4.0.18", - "wait-on": "^8.0.3" + "wait-on": "^8.0.3", + "@types/node": "*" } } diff --git a/services/www/src/app/articles/index.tsx b/services/www/src/app/articles/index.tsx index b6457c4..d05b249 100644 --- a/services/www/src/app/articles/index.tsx +++ b/services/www/src/app/articles/index.tsx @@ -21,10 +21,10 @@ createPage( )}
pre]:!bg-transparent [&>pre]:!p-0 [&>pre]:!m-0', @@ -131,7 +137,6 @@ export const Code = memo(function Code({ ], className )} - dangerouslySetInnerHTML={{ __html: highlightedHtml }} data-testid={testId} /> ); diff --git a/packages/ui/src/components/Code/CodeBlock/CodeBlock.tsx b/packages/ui/src/components/Code/CodeBlock/CodeBlock.tsx index 7b0c440..ab1a269 100644 --- a/packages/ui/src/components/Code/CodeBlock/CodeBlock.tsx +++ b/packages/ui/src/components/Code/CodeBlock/CodeBlock.tsx @@ -6,8 +6,8 @@ import { Button } from '../../Button/Button'; import { Container } from '../../Container/Container'; import { CodeContext, type CodeContextValue } from '../contexts/CodeContext/CodeContext'; -import type { ClassNameProps, TestIdProps } from '@ermnvldmr/stl'; import type { CodeLanguage } from '../lib/highlighter/highlighter'; +import type { ClassNameProps, TestIdProps } from '@ermnvldmr/stl'; /** * Props for the CodeBlock component. diff --git a/packages/ui/src/components/Code/lib/highlighter/highlighter.ts b/packages/ui/src/components/Code/lib/highlighter/highlighter.ts index 418a7ca..9f937ba 100644 --- a/packages/ui/src/components/Code/lib/highlighter/highlighter.ts +++ b/packages/ui/src/components/Code/lib/highlighter/highlighter.ts @@ -46,24 +46,27 @@ export const SUPPORTED_LANGUAGES: CodeLanguage[] = [ * A singleton highlighter service for code blocks. */ class CodeHighlighter { - private static instance: CodeHighlighter; + private static instance: CodeHighlighter | null = null; private highlighter: Highlighter | null = null; private initializingPromise: Promise | null = null; - private constructor() {} - /** * Gets the singleton instance of the highlighter. + * @example + * ```typescript + * const highlighter = CodeHighlighter.getInstance(); + * ``` */ public static getInstance(): CodeHighlighter { - if (!CodeHighlighter.instance) { - CodeHighlighter.instance = new CodeHighlighter(); - } - return CodeHighlighter.instance; + return (CodeHighlighter.instance ??= new CodeHighlighter()); } /** * Initializes the highlighter if it hasn't been already. + * @example + * ```typescript + * const highlighter = await this.getHighlighter(); + * ``` */ private async getHighlighter(): Promise { if (this.highlighter) { @@ -92,6 +95,10 @@ class CodeHighlighter { * @param code - The raw source code to highlight * @param lang - The language of the code * @returns A promise resolving to the highlighted HTML + * @example + * ```typescript + * const html = await highlighter.highlight('const a = 1;', 'typescript'); + * ``` */ public async highlight(code: string, lang: CodeLanguage): Promise { try { diff --git a/packages/ui/src/components/Container/Container.test.tsx b/packages/ui/src/components/Container/Container.test.tsx index beb4ab6..13e43ca 100644 --- a/packages/ui/src/components/Container/Container.test.tsx +++ b/packages/ui/src/components/Container/Container.test.tsx @@ -67,7 +67,7 @@ describe('components/Container', () => { ); expect(consoleWarnSpy).toHaveBeenCalledWith( - '[@ermnvldmr/ui] Container cannot have both `onPress` and `href` props.', + '[@ermnvldmr/ui] Container cannot have both `onPress` and `href` props.' ); consoleWarnSpy.mockRestore(); }); diff --git a/packages/ui/src/components/Container/Container.tsx b/packages/ui/src/components/Container/Container.tsx index 8b58e44..5d7882f 100644 --- a/packages/ui/src/components/Container/Container.tsx +++ b/packages/ui/src/components/Container/Container.tsx @@ -86,12 +86,12 @@ export interface ContainerProps extends ClassNameProps, TestIdProps { border?: boolean; /** * Press interaction handler. - * @note Cannot be used with `href`. + * Cannot be used with `href`. */ onPress?: PressEvents['onPress']; /** * If provided, the container will be rendered as an `` tag. - * @note Cannot be used with `onPress`. + * Cannot be used with `onPress`. */ href?: string; /** The HTML element to use for rendering. Overridden to `a` if `href` is provided. */ @@ -119,11 +119,10 @@ export const Container = memo(function Container({ 'data-testid': testId, }: ContainerProps) { if (process.env.NODE_ENV !== 'production' && onPress && href) { - // eslint-disable-next-line no-console console.warn('[@ermnvldmr/ui] Container cannot have both `onPress` and `href` props.'); } - const isLink = !!href; + const isLink = href != null; const isPressable = !!onPress && !isLink; const ref = React.useRef(null); @@ -202,7 +201,6 @@ export const Container = memo(function Container({ {children} diff --git a/packages/ui/src/components/Markdown/Markdown.stories.tsx b/packages/ui/src/components/Markdown/Markdown.stories.tsx index b4afc34..960df5a 100644 --- a/packages/ui/src/components/Markdown/Markdown.stories.tsx +++ b/packages/ui/src/components/Markdown/Markdown.stories.tsx @@ -161,3 +161,66 @@ export const ListStyles: Story = {