From 544960936c06c2625b6a8fbc0f04e860be826a44 Mon Sep 17 00:00:00 2001 From: Alex Nault Date: Fri, 6 Oct 2023 19:26:30 -0400 Subject: [PATCH 1/4] feat: Add support for setting explicit level --- README.md | 4 +++ src/index.test.tsx | 74 +++++++++++++++++++++++++++++++++++++++++++++- src/index.tsx | 35 +++++++++++++++------- 3 files changed, 102 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a54ee99..d0397e3 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,10 @@ function Example2() { } ``` +### `` component + +TODO document advanced use case + ### `useLevel` hook Returns an object containing the current `level` and current `Component`. diff --git a/src/index.test.tsx b/src/index.test.tsx index 8b860b4..db7b26d 100644 --- a/src/index.test.tsx +++ b/src/index.test.tsx @@ -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(); @@ -121,6 +121,78 @@ describe("H component", () => { }); }); +describe("LevelProvider component", () => { + it("should render a heading and its content", () => { + const { getByText } = render( +
My H1}> +
My H2}> +
My H3}> +
My H4}> + +
My new H2}> +

My content

+
+
+
+
+
+
+ ); + + const headingEl = getByText("My new H2"); + + expect(headingEl.tagName).toBe("H2"); + }); + + it("should default to level 1", () => { + const { getByText } = render( +
My H1}> +
My H2}> +
My H3}> + +
My new H1}> +

My content

+
+
+
+
+
+ ); + + const headingEl = getByText("My new H1"); + + expect(headingEl.tagName).toBe("H1"); + }); + + it("should start a component tree to level 3", () => { + const { getByText } = render( + +
My H3}> +

My content

+
+
+ ); + + const headingEl = getByText("My H3"); + + expect(headingEl.tagName).toBe("H3"); + }); + + it("should start a component tree to level 1 by default", () => { + const { getByText } = render( + +
My H1}> +

My content

+
+
+ ); + + const headingEl = getByText("My H1"); + + expect(headingEl.tagName).toBe("H1"); + }); +}); + describe("Section component", () => { it("should be level 1 in first section", () => { const { getByText } = render(
My H1}>
); diff --git a/src/index.tsx b/src/index.tsx index 4e4d5ef..18d306d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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 ): JSX.Element { @@ -36,12 +39,29 @@ function InternalH( } return ; -} +}); + +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 ( + {children} + ); +} type SectionProps = { component: React.ReactNode; @@ -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} - {children} + {children} ); } From 3b26774787f634797d51cd4b724d37f6f3c2610b Mon Sep 17 00:00:00 2001 From: Alex Nault Date: Fri, 6 Oct 2023 20:33:47 -0400 Subject: [PATCH 2/4] Opt for Level component with level prop --- README.md | 4 +-- src/index.test.tsx | 86 ++++++++++++++-------------------------------- src/index.tsx | 22 ++++++------ 3 files changed, 40 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index d0397e3..bf4e075 100644 --- a/README.md +++ b/README.md @@ -250,9 +250,9 @@ function Example2() { } ``` -### `` component +### `` component -TODO document advanced use case +TODO documentation for low level api / advanced use case (dialogs & portals) ### `useLevel` hook diff --git a/src/index.test.tsx b/src/index.test.tsx index db7b26d..83a2207 100644 --- a/src/index.test.tsx +++ b/src/index.test.tsx @@ -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, LevelProvider } from "./index"; +import { H, Section, Level, useLevel } from "./index"; afterEach(() => { cleanup(); @@ -121,75 +121,41 @@ describe("H component", () => { }); }); -describe("LevelProvider component", () => { - it("should render a heading and its content", () => { +describe("Level component", () => { + it("should render a H at the next level", () => { const { getByText } = render( -
My H1}> -
My H2}> -
My H3}> -
My H4}> - -
My new H2}> -

My content

-
-
-
-
-
-
+ + + + + My H5 + + + + ); - const headingEl = getByText("My new H2"); + const headingEl = getByText("My H5"); - expect(headingEl.tagName).toBe("H2"); + expect(headingEl.tagName).toBe("H5"); }); - it("should default to level 1", () => { + it("should render a H at a specified level", () => { const { getByText } = render( -
My H1}> -
My H2}> -
My H3}> - -
My new H1}> -

My content

-
-
-
-
-
+ + + + + My H2 + + + + ); - const headingEl = getByText("My new H1"); - - expect(headingEl.tagName).toBe("H1"); - }); - - it("should start a component tree to level 3", () => { - const { getByText } = render( - -
My H3}> -

My content

-
-
- ); - - const headingEl = getByText("My H3"); - - expect(headingEl.tagName).toBe("H3"); - }); - - it("should start a component tree to level 1 by default", () => { - const { getByText } = render( - -
My H1}> -

My content

-
-
- ); - - const headingEl = getByText("My H1"); + const headingEl = getByText("My H2"); - expect(headingEl.tagName).toBe("H1"); + expect(headingEl.tagName).toBe("H2"); }); }); diff --git a/src/index.tsx b/src/index.tsx index 18d306d..24626f8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -41,18 +41,24 @@ export const H = React.forwardRef(function H( return ; }); -type LevelProviderProps = { +type LevelProps = { level?: Level; children?: React.ReactNode; }; /** - * TODO documentation + * Renders `children` 1 level down, or at the desired level. + * @param children The children in the next level, or the desired level + * @param level The desired level */ -export function LevelProvider({ - level = 1, +export function Level({ + level: desiredLevel, children, -}: LevelProviderProps): JSX.Element { +}: LevelProps): JSX.Element { + const { level: currentLevel } = useLevel(); + + const level = desiredLevel ?? (Math.min(currentLevel + 1, 6) as Level); + const value = { level, Component: `h${level}` as Heading, @@ -74,14 +80,10 @@ type SectionProps = { * @param children The children in the next level */ export function Section({ component, children }: SectionProps): JSX.Element { - const { level } = useLevel(); - - const nextLevel = Math.min(level + 1, 6) as Level; - return ( <> {component} - {children} + {children} ); } From da85c5161bc8a3e06b76a9faab35acb6479627ff Mon Sep 17 00:00:00 2001 From: Alex Nault Date: Fri, 6 Oct 2023 20:36:22 -0400 Subject: [PATCH 3/4] Update size limit --- .size-limit.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.size-limit.json b/.size-limit.json index d366e6c..0c8aaf5 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -3,11 +3,11 @@ "name": "JS Minified", "path": "dist/*.js", "gzip": false, - "limit": "1610B" + "limit": "1715B" }, { "name": "JS Compressed", "path": "dist/*.js", - "limit": "748B" + "limit": "775B" } ] From cde9a5d0d43a79f4e84393590c424a5f917d2f71 Mon Sep 17 00:00:00 2001 From: Alex Nault Date: Fri, 6 Oct 2023 21:24:44 -0400 Subject: [PATCH 4/4] Add level property to Section component --- .size-limit.json | 4 +-- README.md | 6 ++--- src/index.test.tsx | 62 +++++++++++++++++----------------------------- src/index.tsx | 22 ++++++++++++---- 4 files changed, 44 insertions(+), 50 deletions(-) diff --git a/.size-limit.json b/.size-limit.json index 0c8aaf5..f615cbd 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -3,11 +3,11 @@ "name": "JS Minified", "path": "dist/*.js", "gzip": false, - "limit": "1715B" + "limit": "1782B" }, { "name": "JS Compressed", "path": "dist/*.js", - "limit": "775B" + "limit": "786B" } ] diff --git a/README.md b/README.md index bf4e075..9a811ee 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,8 @@ Creates a new section (a heading and its level). | `component` | `node` | Yes | The heading component. Can be anything but best used in combination with ``. | | `children` | `node` | No | The content of the new level. | + + #### Example ```jsx @@ -250,10 +252,6 @@ function Example2() { } ``` -### `` component - -TODO documentation for low level api / advanced use case (dialogs & portals) - ### `useLevel` hook Returns an object containing the current `level` and current `Component`. diff --git a/src/index.test.tsx b/src/index.test.tsx index 83a2207..a09e475 100644 --- a/src/index.test.tsx +++ b/src/index.test.tsx @@ -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, Level, useLevel } from "./index"; +import { H, Section, useLevel } from "./index"; afterEach(() => { cleanup(); @@ -121,44 +121,6 @@ describe("H component", () => { }); }); -describe("Level component", () => { - it("should render a H at the next level", () => { - const { getByText } = render( - - - - - My H5 - - - - - ); - - const headingEl = getByText("My H5"); - - expect(headingEl.tagName).toBe("H5"); - }); - - it("should render a H at a specified level", () => { - const { getByText } = render( - - - - - My H2 - - - - - ); - - const headingEl = getByText("My H2"); - - expect(headingEl.tagName).toBe("H2"); - }); -}); - describe("Section component", () => { it("should be level 1 in first section", () => { const { getByText } = render(
My H1}>
); @@ -217,4 +179,26 @@ describe("Section component", () => { expect(pEl.tagName).toBe("P"); }); + + it("should render a heading at the specified level", () => { + const { getByText } = render( +
My H1}> +
My H2}> +
My H3}> +
My H2-2} level={2}> +
My H3-2}>
+
+
+
+
+ ); + + const heading2El = getByText("My H2-2"); + + expect(heading2El.tagName).toBe("H2"); + + const heading3El = getByText("My H3-2"); + + expect(heading3El.tagName).toBe("H3"); + }); }); diff --git a/src/index.tsx b/src/index.tsx index 24626f8..5a7b5aa 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -51,10 +51,7 @@ type LevelProps = { * @param children The children in the next level, or the desired level * @param level The desired level */ -export function Level({ - level: desiredLevel, - children, -}: LevelProps): JSX.Element { +function Level({ level: desiredLevel, children }: LevelProps): JSX.Element { const { level: currentLevel } = useLevel(); const level = desiredLevel ?? (Math.min(currentLevel + 1, 6) as Level); @@ -72,14 +69,29 @@ export function Level({ type SectionProps = { component: React.ReactNode; children?: React.ReactNode; + level?: Level; }; /** * Renders `component` in the current level and `children` in the next level. * @param component A component containing a heading * @param children The children in the next level + * @param level A specific level to render instead of the current one */ -export function Section({ component, children }: SectionProps): JSX.Element { +export function Section({ + component, + children, + level, +}: SectionProps): JSX.Element { + if (level) { + return ( + + {component} + {children} + + ); + } + return ( <> {component}