diff --git a/.storybook/main.js b/.storybook/main.js index f9b532a..ab61894 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,11 +1,7 @@ export default { stories: ['../stories/**/*.stories.@(js|jsx|ts|tsx)'], - addons: [ - '@storybook/addon-links', - '@storybook/addon-essentials', - '@storybook/addon-storysource', - ], + addons: ['@storybook/addon-links', '@storybook/addon-docs'], framework: { name: '@storybook/react-vite', diff --git a/.storybook/preview.js b/.storybook/preview.js index 04c7e1f..dad30a9 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1,4 +1,7 @@ export const parameters = { controls: { expanded: true, hideNoControlsWarning: true }, + docs: { + codePanel: true, + }, }; export const tags = ['autodocs']; diff --git a/eslint.config.js b/eslint.config.js index 6f93af3..4360aaf 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,4 +1,6 @@ +import { defineConfig } from 'eslint/config'; import react from 'eslint-config-zakodium/react'; import ts from 'eslint-config-zakodium/ts'; +import storybook from 'eslint-plugin-storybook'; -export default [...react, ...ts]; +export default defineConfig(react, ts, storybook.configs['flat/recommended']); diff --git a/package.json b/package.json index 466f16f..c84d83f 100644 --- a/package.json +++ b/package.json @@ -34,24 +34,24 @@ }, "homepage": "https://github.com/zakodium-oss/react-d3-utils#readme", "devDependencies": { - "@storybook/addon-essentials": "^8.3.5", - "@storybook/addon-links": "^8.3.5", - "@storybook/addon-storysource": "^8.3.5", - "@storybook/react": "^8.3.5", - "@storybook/react-vite": "^8.3.5", - "@types/react": "^18.3.11", - "eslint": "^9.12.0", - "eslint-config-zakodium": "^13.0.0", - "prettier": "^3.3.3", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "@storybook/addon-docs": "^9.0.8", + "@storybook/addon-links": "^9.0.8", + "@storybook/react-vite": "^9.0.8", + "@types/react": "^19.1.7", + "@zakodium/tsconfig": "^1.0.1", + "eslint": "^9.28.0", + "eslint-config-zakodium": "^15.0.1", + "eslint-plugin-storybook": "^9.0.8", + "prettier": "^3.5.3", + "react": "^19.1.0", + "react-dom": "^19.1.0", "rimraf": "^6.0.1", - "typescript": "^5.6.3" + "storybook": "^9.0.8", + "typescript": "^5.8.3" }, "dependencies": { - "@types/d3-scale": "^4.0.8", - "d3-scale": "^4.0.2", - "use-resize-observer": "^9.1.0" + "@types/d3-scale": "^4.0.9", + "d3-scale": "^4.0.2" }, "volta": { "node": "22.9.0" diff --git a/src/components/AlignGroup.tsx b/src/components/AlignGroup.tsx index 9fc94b1..c5c67fc 100644 --- a/src/components/AlignGroup.tsx +++ b/src/components/AlignGroup.tsx @@ -1,4 +1,5 @@ -import { type CSSProperties, type ReactNode, useMemo } from 'react'; +import type { CSSProperties, ReactNode } from 'react'; +import { useMemo } from 'react'; import { useBBoxObserver } from '../hooks/useBBoxObserver.js'; diff --git a/src/components/ResponsiveChart.tsx b/src/components/ResponsiveChart.tsx index 2278685..c2d1c3f 100644 --- a/src/components/ResponsiveChart.tsx +++ b/src/components/ResponsiveChart.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from 'react'; -import useResizeObserver from 'use-resize-observer'; + +import { useResizeObserver } from '../hooks/use_resize_observer.js'; export interface ResponsiveChartProps { width?: number | `${number}%`; @@ -15,12 +16,11 @@ export function ResponsiveChart(props: ResponsiveChartProps) { const { width, height, minWidth, minHeight, maxWidth, maxHeight, children } = props; - // @ts-expect-error Default import is correct. - const observed = useResizeObserver(); + const [observedRef, observedSize] = useResizeObserver(); return (
- {observed.width && observed.height - ? children({ width: observed.width, height: observed.height }) + {observedSize + ? children({ width: observedSize.width, height: observedSize.height }) : null}
diff --git a/src/hooks/useBBoxObserver.ts b/src/hooks/useBBoxObserver.ts index 9b74165..9dac5bb 100644 --- a/src/hooks/useBBoxObserver.ts +++ b/src/hooks/useBBoxObserver.ts @@ -1,4 +1,5 @@ -import { type RefCallback, useCallback, useRef, useState } from 'react'; +import type { RefCallback } from 'react'; +import { useCallback, useRef, useState } from 'react'; const initialSize = { x: 0, y: 0, width: 0, height: 0 }; @@ -10,7 +11,7 @@ export function useBBoxObserver() { const previousSize = useRef(initialSize); // Contains a function to cleanup the previous observer when the observed element changes. - const cleanupPrevious = useRef<() => void>(); + const cleanupPrevious = useRef<() => void>(null); // Ref callback to do the observation. const ref: RefCallback = useCallback((element) => { @@ -19,7 +20,7 @@ export function useBBoxObserver() { } if (element !== null) { const observer = new ResizeObserver(([entry]) => { - const bbox = (entry.target as ElementType).getBBox(); + const bbox = (entry?.target as ElementType).getBBox(); const previous = previousSize.current; if ( previous.x !== bbox.x || diff --git a/src/hooks/useLinearPrimaryTicks.ts b/src/hooks/useLinearPrimaryTicks.ts index ea5ec7e..0712bd8 100644 --- a/src/hooks/useLinearPrimaryTicks.ts +++ b/src/hooks/useLinearPrimaryTicks.ts @@ -1,7 +1,9 @@ import type { ScaleContinuousNumeric } from 'd3-scale'; -import { type MutableRefObject, useState } from 'react'; +import type { RefObject } from 'react'; +import { useState } from 'react'; -import { type Tick, useTicks } from './useTicks.js'; +import type { Tick } from './useTicks.js'; +import { useTicks } from './useTicks.js'; type Directions = 'horizontal' | 'vertical'; @@ -32,7 +34,7 @@ interface Options { export function useLinearPrimaryTicks( scale: ScaleContinuousNumeric, direction: Directions, - ref: MutableRefObject, + ref: RefObject, options: Options = {}, ): UseLinearPrimaryTicksResult { const [ticks, setTicks] = useState(() => ({ diff --git a/src/hooks/useLogTicks.ts b/src/hooks/useLogTicks.ts index 1d422c1..1cb6c95 100644 --- a/src/hooks/useLogTicks.ts +++ b/src/hooks/useLogTicks.ts @@ -1,11 +1,6 @@ import type { ScaleContinuousNumeric } from 'd3-scale'; -import { - type MutableRefObject, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; +import type { RefObject } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { textDimensions } from '../utils.js'; @@ -37,7 +32,10 @@ function formatTicks( minSpace: number, ): PrimaryLogTicks[] { const scaledTicks = ticks.filter((val) => isMainTick(val) === 1).map(scale); - const mainTickSpace = Math.abs(scaledTicks[0] - scaledTicks[1]); + const mainTickSpace = Math.abs( + // TODO: `scaledTicks` might not have a length >1 + (scaledTicks[0] as number) - (scaledTicks[1] as number), + ); const mainTickRatio = (maxWordSpace + minSpace) / mainTickSpace; const mainTicksStep = mainTickRatio >= 1 ? Math.ceil(mainTickRatio) : 1; @@ -56,7 +54,7 @@ function formatTicks( export function useLogTicks( scale: ScaleContinuousNumeric, direction: Directions, - ref: MutableRefObject, + ref: RefObject, options: Options = {}, ): PrimaryLogTicks[] { const [maxStrSize, setMaxStrSize] = useState(40); diff --git a/src/hooks/useTicks.ts b/src/hooks/useTicks.ts index 340d83a..2e2637b 100644 --- a/src/hooks/useTicks.ts +++ b/src/hooks/useTicks.ts @@ -1,10 +1,6 @@ import type { ScaleContinuousNumeric, ScaleTime } from 'd3-scale'; -import { - type Dispatch, - type MutableRefObject, - type SetStateAction, - useEffect, -} from 'react'; +import type { Dispatch, RefObject, SetStateAction } from 'react'; +import { useEffect } from 'react'; import { textDimensions } from '../utils.js'; @@ -44,11 +40,13 @@ export function useTicks( ? ScaleContinuousNumeric : ScaleTime, direction: Directions, - ref: MutableRefObject, + ref: RefObject, options: Options, ) { - const range = scale.range(); - if (!range) throw new Error('Range needs to be specified'); + const range = scale.range() as [number, number]; + if (!range || range.length !== 2) { + throw new Error('Range needs to be specified'); + } const domain = scale.domain(); if (!domain) throw new Error('Domain needs to be specified'); diff --git a/src/hooks/useTimeTicks.ts b/src/hooks/useTimeTicks.ts index 5774a08..4a51a7a 100644 --- a/src/hooks/useTimeTicks.ts +++ b/src/hooks/useTimeTicks.ts @@ -1,7 +1,9 @@ import type { ScaleTime } from 'd3-scale'; -import { type MutableRefObject, useState } from 'react'; +import type { RefObject } from 'react'; +import { useState } from 'react'; -import { type Tick, useTicks } from './useTicks.js'; +import type { Tick } from './useTicks.js'; +import { useTicks } from './useTicks.js'; type Directions = 'horizontal' | 'vertical'; @@ -32,7 +34,7 @@ interface Options { export function useTimeTicks( scale: ScaleTime, direction: Directions, - ref: MutableRefObject, + ref: RefObject, options: Options, ): UseTimeTicksResult { const { tickFormat = scale.tickFormat() } = options; diff --git a/src/hooks/use_resize_observer.ts b/src/hooks/use_resize_observer.ts new file mode 100644 index 0000000..07df4f5 --- /dev/null +++ b/src/hooks/use_resize_observer.ts @@ -0,0 +1,32 @@ +import { useCallback, useRef, useState } from 'react'; + +export function useResizeObserver() { + const [size, setSize] = useState(); + const observerRef = useRef(null); + + const refCallback = useCallback((node: HTMLElement | null) => { + if (observerRef.current) { + observerRef.current.disconnect(); + observerRef.current = null; + } + + if (node) { + const updateSize = () => { + const rect = node.getBoundingClientRect(); + + setSize(rect); + }; + + updateSize(); + + const observer = new ResizeObserver(() => { + updateSize(); + }); + + observer.observe(node); + observerRef.current = observer; + } + }, []); + + return [refCallback, size] as const; +} diff --git a/src/index.ts b/src/index.ts index cb728ed..d3359dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export * from './components/AlignGroup.js'; export * from './components/ResponsiveChart.js'; export * from './hooks/useBBoxObserver.js'; +export * from './hooks/use_resize_observer.js'; export * from './hooks/useLinearPrimaryTicks.js'; export * from './hooks/useLogTicks.js'; export * from './hooks/useTimeTicks.js'; diff --git a/src/utils.ts b/src/utils.ts index d7756bd..951b022 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,8 @@ -import type { MutableRefObject } from 'react'; +import type { RefObject } from 'react'; export function textDimensions( word: string, - ref: MutableRefObject, + ref: RefObject, ) { const textContent = document.createTextNode(word); const textElement = document.createElementNS( diff --git a/stories/components/AlignGroup.stories.tsx b/stories/components/AlignGroup.stories.tsx index 75686e1..6b95d61 100644 --- a/stories/components/AlignGroup.stories.tsx +++ b/stories/components/AlignGroup.stories.tsx @@ -1,11 +1,8 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { useEffect, useState } from 'react'; -import { - AlignGroup, - type AlignGroupProps, - useBBoxObserver, -} from '../../src/index.js'; +import type { AlignGroupProps } from '../../src/index.js'; +import { AlignGroup, useBBoxObserver } from '../../src/index.js'; const DEBUG_COLOR = 'yellow'; diff --git a/stories/components/ResponsiveChart.stories.tsx b/stories/components/ResponsiveChart.stories.tsx index c07c6e2..bc4156c 100644 --- a/stories/components/ResponsiveChart.stories.tsx +++ b/stories/components/ResponsiveChart.stories.tsx @@ -1,7 +1,9 @@ -import type { Meta } from '@storybook/react'; -import { type ReactNode, useEffect, useState } from 'react'; +import type { Meta } from '@storybook/react-vite'; +import type { ReactNode } from 'react'; +import { useEffect, useState } from 'react'; -import { ResponsiveChart, type ResponsiveChartProps } from '../../src/index.js'; +import type { ResponsiveChartProps } from '../../src/index.js'; +import { ResponsiveChart } from '../../src/index.js'; export default { title: 'components/ResponsiveChart', @@ -115,7 +117,9 @@ export function SiblingVariable() { >
- {({ width, height }) => } + {({ width, height }) => ( + + )}
diff --git a/stories/hooks/LinearPrimaryExamples.stories.tsx b/stories/hooks/LinearPrimaryExamples.stories.tsx index d9bbe24..02d084c 100644 --- a/stories/hooks/LinearPrimaryExamples.stories.tsx +++ b/stories/hooks/LinearPrimaryExamples.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { useState } from 'react'; import { HorizontalExample, VerticalExample } from './TestAxis.js'; diff --git a/stories/hooks/LinearPrimaryTicks.stories.tsx b/stories/hooks/LinearPrimaryTicks.stories.tsx index 8da8414..f92412d 100644 --- a/stories/hooks/LinearPrimaryTicks.stories.tsx +++ b/stories/hooks/LinearPrimaryTicks.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { AutomaticHorizontalAxis, AutomaticVerticalAxis } from './TestAxis.js'; diff --git a/stories/hooks/LogExamples.stories.tsx b/stories/hooks/LogExamples.stories.tsx index 6de0d45..14e7142 100644 --- a/stories/hooks/LogExamples.stories.tsx +++ b/stories/hooks/LogExamples.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { HorizontalExample, VerticalExample } from './TestAxis.js'; diff --git a/stories/hooks/LogTicks.stories.tsx b/stories/hooks/LogTicks.stories.tsx index ee0930a..6b1c17c 100644 --- a/stories/hooks/LogTicks.stories.tsx +++ b/stories/hooks/LogTicks.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { AutomaticHorizontalAxis, AutomaticVerticalAxis } from './TestAxis.js'; diff --git a/stories/hooks/TestAxis.tsx b/stories/hooks/TestAxis.tsx index 133ba29..940682e 100644 --- a/stories/hooks/TestAxis.tsx +++ b/stories/hooks/TestAxis.tsx @@ -1,11 +1,5 @@ -import { - type ScaleLinear, - scaleLinear, - scaleLog, - type ScaleLogarithmic, - type ScaleTime, - scaleTime, -} from 'd3-scale'; +import type { ScaleLinear, ScaleLogarithmic, ScaleTime } from 'd3-scale'; +import { scaleLinear, scaleLog, scaleTime } from 'd3-scale'; import React, { forwardRef, useEffect, useMemo, useRef, useState } from 'react'; import { diff --git a/stories/hooks/TimeExamples.stories.tsx b/stories/hooks/TimeExamples.stories.tsx index b62e66c..e79b441 100644 --- a/stories/hooks/TimeExamples.stories.tsx +++ b/stories/hooks/TimeExamples.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { HorizontalExample, VerticalExample } from './TestAxis.js'; diff --git a/stories/hooks/TimeTicks.stories.tsx b/stories/hooks/TimeTicks.stories.tsx index 938c5e5..9e4827d 100644 --- a/stories/hooks/TimeTicks.stories.tsx +++ b/stories/hooks/TimeTicks.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { AutomaticHorizontalAxis, AutomaticVerticalAxis } from './TestAxis.js'; diff --git a/stories/hooks/advanced_ticks.stories.tsx b/stories/hooks/advanced_ticks.stories.tsx index ab2569a..2051dca 100644 --- a/stories/hooks/advanced_ticks.stories.tsx +++ b/stories/hooks/advanced_ticks.stories.tsx @@ -1,5 +1,6 @@ -import type { Meta } from '@storybook/react'; -import { type ScaleContinuousNumeric, scaleLinear } from 'd3-scale'; +import type { Meta } from '@storybook/react-vite'; +import type { ScaleContinuousNumeric } from 'd3-scale'; +import { scaleLinear } from 'd3-scale'; import React, { forwardRef, useRef, useState } from 'react'; import type { Tick } from '../../src/index.js'; @@ -9,10 +10,12 @@ export default { title: 'Hooks/Advanced Ticks', } as Meta; -const LARGE_RANGE = [0, 600]; -const SMALL_RANGE = [0, 40]; -const DOMAIN_LARGE = [0, 100]; -const DOMAIN_SMALL = [50, 50.0001]; +type Domain = [number, number]; + +const LARGE_RANGE: Domain = [0, 600]; +const SMALL_RANGE: Domain = [0, 40]; +const DOMAIN_LARGE: Domain = [0, 100]; +const DOMAIN_SMALL: Domain = [50, 50.0001]; export function AdvancedCustomTicks() { const ref = useRef(null); @@ -64,7 +67,7 @@ function computeLinearTicks( secondaryTickSize?: number; } = {}, ): AxisTick[] { - const viewport = xAccessor.domain(); + const viewport = xAccessor.domain() as Domain; const ticks: AxisTick[] = []; const { noSecondaryTicks = false, @@ -112,7 +115,7 @@ function computeLinearTicks( ticks.push({ value, position: xAccessor(value), - label: primaryTicks[primaryTickIndex]?.label ?? null, + label: primaryTicks[primaryTickIndex]?.label ?? '', size: n % nSubTicks === 0 ? primaryTickSize : secondaryTickSize, }); } diff --git a/tsconfig.json b/tsconfig.json index af1a3b0..8cfbced 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,7 @@ { + "extends": "@zakodium/tsconfig/jsx", "compilerOptions": { - "lib": ["DOM", "ES2022"], - "types": [], - "target": "ES2022", - "outDir": "lib", - "jsx": "react-jsx", - "module": "NodeNext", - "strict": true, - "skipLibCheck": true, - "resolveJsonModule": false, - "forceConsistentCasingInFileNames": true, - "allowJs": false, - "isolatedModules": true, - "verbatimModuleSyntax": true, - "sourceMap": true, - "declaration": true, - "declarationMap": true + "outDir": "lib" }, "include": ["src", "stories"] }