Skip to content

Commit

Permalink
feat: Add support for setting explicit level
Browse files Browse the repository at this point in the history
  • Loading branch information
alexnault committed Oct 6, 2023
1 parent 279cdfa commit 5449609
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 11 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ function Example2() {
}
```

### `<LevelProvider>` component

TODO document advanced use case

### `useLevel` hook

Returns an object containing the current `level` and current `Component`.
Expand Down
74 changes: 73 additions & 1 deletion src/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import { render, cleanup } from "@testing-library/react";
import { describe, it, expect, afterEach } from "vitest";

import { H, Section, useLevel } from "./index";
import { H, Section, useLevel, LevelProvider } from "./index";

afterEach(() => {
cleanup();
Expand Down Expand Up @@ -121,6 +121,78 @@ describe("H component", () => {
});
});

describe("LevelProvider component", () => {
it("should render a heading and its content", () => {
const { getByText } = render(
<Section component={<H>My H1</H>}>
<Section component={<H>My H2</H>}>
<Section component={<H>My H3</H>}>
<Section component={<H>My H4</H>}>
<LevelProvider level={2}>
<Section component={<H>My new H2</H>}>
<p>My content</p>
</Section>
</LevelProvider>
</Section>
</Section>
</Section>
</Section>
);

const headingEl = getByText("My new H2");

expect(headingEl.tagName).toBe("H2");
});

it("should default to level 1", () => {
const { getByText } = render(
<Section component={<H>My H1</H>}>
<Section component={<H>My H2</H>}>
<Section component={<H>My H3</H>}>
<LevelProvider>
<Section component={<H>My new H1</H>}>
<p>My content</p>
</Section>
</LevelProvider>
</Section>
</Section>
</Section>
);

const headingEl = getByText("My new H1");

expect(headingEl.tagName).toBe("H1");
});

it("should start a component tree to level 3", () => {
const { getByText } = render(
<LevelProvider level={3}>
<Section component={<H>My H3</H>}>
<p>My content</p>
</Section>
</LevelProvider>
);

const headingEl = getByText("My H3");

expect(headingEl.tagName).toBe("H3");
});

it("should start a component tree to level 1 by default", () => {
const { getByText } = render(
<LevelProvider>
<Section component={<H>My H1</H>}>
<p>My content</p>
</Section>
</LevelProvider>
);

const headingEl = getByText("My H1");

expect(headingEl.tagName).toBe("H1");
});
});

describe("Section component", () => {
it("should be level 1 in first section", () => {
const { getByText } = render(<Section component={<H>My H1</H>}></Section>);
Expand Down
35 changes: 25 additions & 10 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ type HProps = React.DetailedHTMLProps<
render?: (context: LevelContextValue) => React.ReactElement;
};

function InternalH(
/**
* Renders a dynamic HTML heading (h1, h2, etc.) or custom component according to the current level.
*/
export const H = React.forwardRef(function H(
{ render, ...props }: HProps,
forwardedRef: React.ForwardedRef<HTMLHeadingElement>
): JSX.Element {
Expand All @@ -36,12 +39,29 @@ function InternalH(
}

return <context.Component ref={forwardedRef} {...props} />;
}
});

type LevelProviderProps = {
level?: Level;
children?: React.ReactNode;
};

/**
* Renders a dynamic HTML heading (h1, h2, etc.) or custom component according to the current level.
* TODO documentation
*/
export const H = React.forwardRef(InternalH);
export function LevelProvider({
level = 1,
children,
}: LevelProviderProps): JSX.Element {
const value = {
level,
Component: `h${level}` as Heading,
};

return (
<LevelContext.Provider value={value}>{children}</LevelContext.Provider>
);
}

type SectionProps = {
component: React.ReactNode;
Expand All @@ -58,15 +78,10 @@ export function Section({ component, children }: SectionProps): JSX.Element {

const nextLevel = Math.min(level + 1, 6) as Level;

const value = {
level: nextLevel,
Component: `h${nextLevel}` as Heading,
};

return (
<>
{component}
<LevelContext.Provider value={value}>{children}</LevelContext.Provider>
<LevelProvider level={nextLevel}>{children}</LevelProvider>
</>
);
}

0 comments on commit 5449609

Please sign in to comment.