Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
3 changes: 3 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export const parameters = {
controls: { expanded: true, hideNoControlsWarning: true },
docs: {
codePanel: true,
},
};
export const tags = ['autodocs'];
4 changes: 3 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
@@ -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']);
30 changes: 15 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion src/components/AlignGroup.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
12 changes: 6 additions & 6 deletions src/components/ResponsiveChart.tsx
Original file line number Diff line number Diff line change
@@ -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}%`;
Expand All @@ -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<HTMLDivElement>();
const [observedRef, observedSize] = useResizeObserver();

return (
<div
ref={observed.ref}
ref={observedRef}
style={{
position: 'relative',
flex: 1,
Expand All @@ -35,8 +35,8 @@ export function ResponsiveChart(props: ResponsiveChartProps) {
<div
style={{ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 }}
>
{observed.width && observed.height
? children({ width: observed.width, height: observed.height })
{observedSize
? children({ width: observedSize.width, height: observedSize.height })
: null}
</div>
</div>
Expand Down
7 changes: 4 additions & 3 deletions src/hooks/useBBoxObserver.ts
Original file line number Diff line number Diff line change
@@ -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 };

Expand All @@ -10,7 +11,7 @@ export function useBBoxObserver<ElementType extends SVGGraphicsElement>() {
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<ElementType> = useCallback((element) => {
Expand All @@ -19,7 +20,7 @@ export function useBBoxObserver<ElementType extends SVGGraphicsElement>() {
}
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 ||
Expand Down
8 changes: 5 additions & 3 deletions src/hooks/useLinearPrimaryTicks.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -32,7 +34,7 @@ interface Options {
export function useLinearPrimaryTicks(
scale: ScaleContinuousNumeric<number, number>,
direction: Directions,
ref: MutableRefObject<SVGGElement | null>,
ref: RefObject<SVGGElement | null>,
options: Options = {},
): UseLinearPrimaryTicksResult {
const [ticks, setTicks] = useState<UseLinearPrimaryTicksResult>(() => ({
Expand Down
16 changes: 7 additions & 9 deletions src/hooks/useLogTicks.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -37,7 +32,10 @@
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

Check warning on line 36 in src/hooks/useLogTicks.ts

View workflow job for this annotation

GitHub Actions / nodejs / lint-eslint

Unexpected 'todo' comment: 'TODO: `scaledTicks` might not have a...'
(scaledTicks[0] as number) - (scaledTicks[1] as number),
);
const mainTickRatio = (maxWordSpace + minSpace) / mainTickSpace;
const mainTicksStep = mainTickRatio >= 1 ? Math.ceil(mainTickRatio) : 1;

Expand All @@ -56,7 +54,7 @@
export function useLogTicks(
scale: ScaleContinuousNumeric<number, number>,
direction: Directions,
ref: MutableRefObject<SVGGElement | null>,
ref: RefObject<SVGGElement | null>,
options: Options = {},
): PrimaryLogTicks[] {
const [maxStrSize, setMaxStrSize] = useState(40);
Expand Down
16 changes: 7 additions & 9 deletions src/hooks/useTicks.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -44,11 +40,13 @@ export function useTicks<T extends number | Date>(
? ScaleContinuousNumeric<number, number>
: ScaleTime<number, number>,
direction: Directions,
ref: MutableRefObject<SVGGElement | null>,
ref: RefObject<SVGGElement | null>,
options: Options<T>,
) {
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');
Expand Down
8 changes: 5 additions & 3 deletions src/hooks/useTimeTicks.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -32,7 +34,7 @@ interface Options {
export function useTimeTicks(
scale: ScaleTime<number, number>,
direction: Directions,
ref: MutableRefObject<SVGGElement | null>,
ref: RefObject<SVGGElement | null>,
options: Options,
): UseTimeTicksResult {
const { tickFormat = scale.tickFormat() } = options;
Expand Down
32 changes: 32 additions & 0 deletions src/hooks/use_resize_observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useCallback, useRef, useState } from 'react';

export function useResizeObserver() {
const [size, setSize] = useState<DOMRect>();
const observerRef = useRef<ResizeObserver>(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;
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { MutableRefObject } from 'react';
import type { RefObject } from 'react';

export function textDimensions(
word: string,
ref: MutableRefObject<SVGGElement | null>,
ref: RefObject<SVGGElement | null>,
) {
const textContent = document.createTextNode(word);
const textElement = document.createElementNS(
Expand Down
9 changes: 3 additions & 6 deletions stories/components/AlignGroup.stories.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
12 changes: 8 additions & 4 deletions stories/components/ResponsiveChart.stories.tsx
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -115,7 +117,9 @@ export function SiblingVariable() {
>
<div style={{ flex: 2, height: 100, backgroundColor: 'yellow' }} />
<ResponsiveChart>
{({ width, height }) => <TestChart width={width} height={height} />}
{({ width, height }) => (
<TestChart width={Math.round(width)} height={Math.round(height)} />
)}
</ResponsiveChart>
<VariableDiv>
<div style={{ backgroundColor: 'red', height: '100%', color: 'white' }}>
Expand Down
2 changes: 1 addition & 1 deletion stories/hooks/LinearPrimaryExamples.stories.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion stories/hooks/LinearPrimaryTicks.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Meta } from '@storybook/react';
import type { Meta } from '@storybook/react-vite';

import { AutomaticHorizontalAxis, AutomaticVerticalAxis } from './TestAxis.js';

Expand Down
2 changes: 1 addition & 1 deletion stories/hooks/LogExamples.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Meta } from '@storybook/react';
import type { Meta } from '@storybook/react-vite';

import { HorizontalExample, VerticalExample } from './TestAxis.js';

Expand Down
2 changes: 1 addition & 1 deletion stories/hooks/LogTicks.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Meta } from '@storybook/react';
import type { Meta } from '@storybook/react-vite';

import { AutomaticHorizontalAxis, AutomaticVerticalAxis } from './TestAxis.js';

Expand Down
10 changes: 2 additions & 8 deletions stories/hooks/TestAxis.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion stories/hooks/TimeExamples.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Meta } from '@storybook/react';
import type { Meta } from '@storybook/react-vite';

import { HorizontalExample, VerticalExample } from './TestAxis.js';

Expand Down
2 changes: 1 addition & 1 deletion stories/hooks/TimeTicks.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Meta } from '@storybook/react';
import type { Meta } from '@storybook/react-vite';

import { AutomaticHorizontalAxis, AutomaticVerticalAxis } from './TestAxis.js';

Expand Down
Loading