@nationaldesignstudio/react 0.0.15 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/App.css +0 -0
- package/src/App.tsx +7 -0
- package/src/assets/fonts/PPNeueMontreal-Variable.woff2 +0 -0
- package/src/assets/react.svg +1 -0
- package/src/components/atoms/accordion/accordion.stories.tsx +228 -0
- package/src/components/atoms/accordion/accordion.tsx +219 -0
- package/src/components/atoms/accordion/index.ts +6 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-quiet-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-charcoal-outline-quiet-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-disabled-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-disabled-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-quiet-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-ivory-outline-quiet-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-large-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-large-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-medium-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-medium-chromium-linux.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-small-chromium-darwin.png +0 -0
- package/src/components/atoms/button/__screenshots__/button.visual.test.tsx/button-size-small-chromium-linux.png +0 -0
- package/src/components/atoms/button/button.stories.tsx +102 -0
- package/src/components/atoms/button/button.test.tsx +135 -0
- package/src/components/atoms/button/button.tsx +139 -0
- package/src/components/atoms/button/button.visual.test.tsx +102 -0
- package/src/components/atoms/button/icon-button.stories.tsx +166 -0
- package/src/components/atoms/button/icon-button.tsx +120 -0
- package/src/components/atoms/button/index.ts +6 -0
- package/src/components/atoms/ndstudio-footer/index.ts +1 -0
- package/src/components/atoms/ndstudio-footer/ndstudio-footer.tsx +55 -0
- package/src/components/atoms/pager-control/index.ts +5 -0
- package/src/components/atoms/pager-control/pager-control.stories.tsx +209 -0
- package/src/components/atoms/pager-control/pager-control.test.tsx +130 -0
- package/src/components/atoms/pager-control/pager-control.tsx +329 -0
- package/src/components/dev-tools/dev-toolbar/dev-toolbar.stories.tsx +82 -0
- package/src/components/dev-tools/dev-toolbar/dev-toolbar.tsx +196 -0
- package/src/components/dev-tools/dev-toolbar/index.ts +1 -0
- package/src/components/dev-tools/grid-overlay/grid-overlay.tsx +41 -0
- package/src/components/dev-tools/grid-overlay/index.ts +1 -0
- package/src/components/dev-tools/index.ts +2 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-default-vertical-chromium-darwin.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-default-vertical-chromium-linux.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-horizontal-chromium-darwin.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-horizontal-chromium-linux.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-minimal-chromium-darwin.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-minimal-chromium-linux.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-actions-chromium-darwin.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-actions-chromium-linux.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-eyebrow-chromium-darwin.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-eyebrow-chromium-linux.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-image-chromium-darwin.png +0 -0
- package/src/components/organisms/card/__screenshots__/card.visual.test.tsx/card-without-image-chromium-linux.png +0 -0
- package/src/components/organisms/card/card.stories.tsx +293 -0
- package/src/components/organisms/card/card.test.tsx +245 -0
- package/src/components/organisms/card/card.tsx +225 -0
- package/src/components/organisms/card/card.visual.test.tsx +197 -0
- package/src/components/organisms/card/index.ts +19 -0
- package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-active-link-chromium-darwin.png +0 -0
- package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-active-link-chromium-linux.png +0 -0
- package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-brand-only-chromium-darwin.png +0 -0
- package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-brand-only-chromium-linux.png +0 -0
- package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-default-chromium-darwin.png +0 -0
- package/src/components/organisms/navbar/__screenshots__/navbar.visual.test.tsx/navbar-default-chromium-linux.png +0 -0
- package/src/components/organisms/navbar/index.ts +18 -0
- package/src/components/organisms/navbar/navbar.stories.tsx +313 -0
- package/src/components/organisms/navbar/navbar.test.tsx +190 -0
- package/src/components/organisms/navbar/navbar.tsx +323 -0
- package/src/components/organisms/navbar/navbar.visual.test.tsx +85 -0
- package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-icon-chromium-darwin.png +0 -0
- package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-icon-chromium-linux.png +0 -0
- package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-text-chromium-darwin.png +0 -0
- package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-custom-text-chromium-linux.png +0 -0
- package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-default-chromium-darwin.png +0 -0
- package/src/components/organisms/us-gov-banner/__screenshots__/us-gov-banner.visual.test.tsx/us-gov-banner-default-chromium-linux.png +0 -0
- package/src/components/organisms/us-gov-banner/index.ts +1 -0
- package/src/components/organisms/us-gov-banner/us-gov-banner.stories.tsx +35 -0
- package/src/components/organisms/us-gov-banner/us-gov-banner.test.tsx +107 -0
- package/src/components/organisms/us-gov-banner/us-gov-banner.tsx +73 -0
- package/src/components/organisms/us-gov-banner/us-gov-banner.visual.test.tsx +46 -0
- package/src/components/sections/banner/banner.stories.tsx +150 -0
- package/src/components/sections/banner/banner.test.tsx +185 -0
- package/src/components/sections/banner/banner.tsx +130 -0
- package/src/components/sections/banner/index.ts +2 -0
- package/src/components/sections/card-grid/card-grid.stories.tsx +351 -0
- package/src/components/sections/card-grid/card-grid.tsx +116 -0
- package/src/components/sections/card-grid/index.ts +1 -0
- package/src/components/sections/faq-section/faq-section.stories.tsx +453 -0
- package/src/components/sections/faq-section/faq-section.tsx +84 -0
- package/src/components/sections/faq-section/index.ts +2 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-desktop-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-desktop-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-mobile-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-mobile-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-tablet-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a1-tablet-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-desktop-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-desktop-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-mobile-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-mobile-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-tablet-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a2-tablet-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-desktop-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-desktop-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-mobile-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-mobile-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-tablet-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-a3-tablet-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-custom-class-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-custom-class-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-default-chromium-linux.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-long-title-chromium-darwin.png +0 -0
- package/src/components/sections/hero/__screenshots__/hero.visual.test.tsx/hero-long-title-chromium-linux.png +0 -0
- package/src/components/sections/hero/hero.stories.tsx +274 -0
- package/src/components/sections/hero/hero.test.tsx +135 -0
- package/src/components/sections/hero/hero.tsx +453 -0
- package/src/components/sections/hero/hero.visual.test.tsx +140 -0
- package/src/components/sections/hero/index.ts +10 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-h3-heading-chromium-darwin.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-h3-heading-chromium-linux.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-paragraphs-chromium-darwin.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-paragraphs-chromium-linux.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-sections-chromium-darwin.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-multiple-sections-chromium-linux.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-single-section-chromium-darwin.png +0 -0
- package/src/components/sections/prose/__screenshots__/prose.visual.test.tsx/prose-single-section-chromium-linux.png +0 -0
- package/src/components/sections/prose/index.ts +6 -0
- package/src/components/sections/prose/prose.stories.tsx +144 -0
- package/src/components/sections/prose/prose.test.tsx +178 -0
- package/src/components/sections/prose/prose.tsx +88 -0
- package/src/components/sections/prose/prose.visual.test.tsx +105 -0
- package/src/components/sections/river/index.ts +1 -0
- package/src/components/sections/river/river.stories.tsx +237 -0
- package/src/components/sections/river/river.test.tsx +268 -0
- package/src/components/sections/river/river.tsx +173 -0
- package/src/components/sections/tout/index.ts +1 -0
- package/src/components/sections/tout/tout.stories.tsx +171 -0
- package/src/components/sections/tout/tout.test.tsx +242 -0
- package/src/components/sections/tout/tout.tsx +270 -0
- package/src/components/sections/two-column-section/index.ts +5 -0
- package/src/components/sections/two-column-section/two-column-section.stories.tsx +285 -0
- package/src/components/sections/two-column-section/two-column-section.tsx +162 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-event-listener.ts +73 -0
- package/src/index.ts +155 -0
- package/src/lib/theme.ts +1000 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +13 -0
- package/src/stories/GridSystem.stories.tsx +84 -0
- package/src/stories/Introduction.mdx +114 -0
- package/src/stories/ThemeProvider.stories.tsx +357 -0
- package/src/stories/TokenShowcase.stories.tsx +92 -0
- package/src/stories/TokenShowcase.tsx +1429 -0
- package/src/styles.css +11 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/hooks.ts +40 -0
- package/src/theme/index.ts +43 -0
- package/src/theme/utils.ts +104 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { Prose, ProseSection } from ".";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Prose> = {
|
|
5
|
+
title: "Sections/Prose",
|
|
6
|
+
component: Prose,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: "centered",
|
|
9
|
+
},
|
|
10
|
+
} as Meta<typeof Prose>;
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof Prose>;
|
|
14
|
+
|
|
15
|
+
const sampleIntro =
|
|
16
|
+
"On the modern web, typography is not decoration; it's the cognitive interface. Before a user understands your product, policy, or program, they're unconsciously reading the structure of your type: the hierarchy of headings, the discipline of your spacing, the confidence or hesitation in your line lengths.";
|
|
17
|
+
|
|
18
|
+
const sampleParagraph1 =
|
|
19
|
+
"Style here isn't about clever fonts, it's about creating a system of visual cues that makes thinking easier. A consistent scale, clear contrast between roles, and careful use of weight and size become a kind of mental exoskeleton for the reader. Done well, typography lowers the friction of understanding to the point where attention can be spent on the content itself rather than on decoding the layout.";
|
|
20
|
+
|
|
21
|
+
const sampleParagraph2 =
|
|
22
|
+
'Rhythm is where that structure turns into a voice. Online, people experience your text as a sequence of micro-moments: the way a headline sits in space, how paragraphs breathe, where the eye can rest. Tight, compressed leading and dense blocks say, "this is serious, pay attention." Generous white space and measured line lengths say, "you have room to think." Over time, those choices form a recognizable persona, just as surely as a writing style does. When the rhythm is intentional, your site speaks with a calm, authoritative cadence that users begin to trust. When it\'s sloppy or inconsistent, the voice fractures, and so does the sense that there\'s a clear, competent mind behind the interface.';
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Default
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
export const Default: Story = {
|
|
29
|
+
render: () => (
|
|
30
|
+
<Prose>
|
|
31
|
+
<ProseSection heading="Headline - Medium" as="h2">
|
|
32
|
+
<p>{sampleIntro}</p>
|
|
33
|
+
</ProseSection>
|
|
34
|
+
<ProseSection heading="Headline - Small" as="h3">
|
|
35
|
+
<p>{sampleParagraph1}</p>
|
|
36
|
+
<p>{sampleParagraph2}</p>
|
|
37
|
+
</ProseSection>
|
|
38
|
+
</Prose>
|
|
39
|
+
),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// Responsive Variants
|
|
44
|
+
// =============================================================================
|
|
45
|
+
|
|
46
|
+
export const Desktop: Story = {
|
|
47
|
+
render: () => (
|
|
48
|
+
<Prose>
|
|
49
|
+
<ProseSection heading="Headline - Medium" as="h2">
|
|
50
|
+
<p>{sampleIntro}</p>
|
|
51
|
+
</ProseSection>
|
|
52
|
+
<ProseSection heading="Headline - Small" as="h3">
|
|
53
|
+
<p>{sampleParagraph1}</p>
|
|
54
|
+
<p>{sampleParagraph2}</p>
|
|
55
|
+
</ProseSection>
|
|
56
|
+
</Prose>
|
|
57
|
+
),
|
|
58
|
+
globals: {
|
|
59
|
+
viewport: { value: "lg", isRotated: false },
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Tablet: Story = {
|
|
64
|
+
render: () => (
|
|
65
|
+
<Prose>
|
|
66
|
+
<ProseSection heading="Headline - Medium" as="h2">
|
|
67
|
+
<p>{sampleIntro}</p>
|
|
68
|
+
</ProseSection>
|
|
69
|
+
<ProseSection heading="Headline - Small" as="h3">
|
|
70
|
+
<p>{sampleParagraph1}</p>
|
|
71
|
+
<p>{sampleParagraph2}</p>
|
|
72
|
+
</ProseSection>
|
|
73
|
+
</Prose>
|
|
74
|
+
),
|
|
75
|
+
globals: {
|
|
76
|
+
viewport: { value: "md", isRotated: false },
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const Mobile: Story = {
|
|
81
|
+
render: () => (
|
|
82
|
+
<Prose>
|
|
83
|
+
<ProseSection heading="Headline - Medium" as="h2">
|
|
84
|
+
<p>{sampleIntro}</p>
|
|
85
|
+
</ProseSection>
|
|
86
|
+
<ProseSection heading="Headline - Small" as="h3">
|
|
87
|
+
<p>{sampleParagraph1}</p>
|
|
88
|
+
<p>{sampleParagraph2}</p>
|
|
89
|
+
</ProseSection>
|
|
90
|
+
</Prose>
|
|
91
|
+
),
|
|
92
|
+
globals: {
|
|
93
|
+
viewport: { value: "sm", isRotated: false },
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// Playground
|
|
99
|
+
// =============================================================================
|
|
100
|
+
|
|
101
|
+
export const Playground: Story = {
|
|
102
|
+
render: (args) => (
|
|
103
|
+
<Prose {...args}>
|
|
104
|
+
<ProseSection heading="Main Heading" as="h2">
|
|
105
|
+
<p>{sampleIntro}</p>
|
|
106
|
+
</ProseSection>
|
|
107
|
+
<ProseSection heading="Subheading" as="h3">
|
|
108
|
+
<p>{sampleParagraph1}</p>
|
|
109
|
+
<p>{sampleParagraph2}</p>
|
|
110
|
+
</ProseSection>
|
|
111
|
+
</Prose>
|
|
112
|
+
),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// =============================================================================
|
|
116
|
+
// Examples
|
|
117
|
+
// =============================================================================
|
|
118
|
+
|
|
119
|
+
export const SingleSection: Story = {
|
|
120
|
+
render: () => (
|
|
121
|
+
<Prose>
|
|
122
|
+
<ProseSection heading="About Our Mission" as="h2">
|
|
123
|
+
<p>{sampleIntro}</p>
|
|
124
|
+
<p>{sampleParagraph1}</p>
|
|
125
|
+
</ProseSection>
|
|
126
|
+
</Prose>
|
|
127
|
+
),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const MultipleSections: Story = {
|
|
131
|
+
render: () => (
|
|
132
|
+
<Prose>
|
|
133
|
+
<ProseSection heading="Introduction" as="h2">
|
|
134
|
+
<p>{sampleIntro}</p>
|
|
135
|
+
</ProseSection>
|
|
136
|
+
<ProseSection heading="The Importance of Style" as="h3">
|
|
137
|
+
<p>{sampleParagraph1}</p>
|
|
138
|
+
</ProseSection>
|
|
139
|
+
<ProseSection heading="Finding Your Rhythm" as="h3">
|
|
140
|
+
<p>{sampleParagraph2}</p>
|
|
141
|
+
</ProseSection>
|
|
142
|
+
</Prose>
|
|
143
|
+
),
|
|
144
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { page } from "vitest/browser";
|
|
3
|
+
import { render } from "vitest-browser-react";
|
|
4
|
+
import { Prose, ProseSection } from "./prose";
|
|
5
|
+
|
|
6
|
+
describe("Prose", () => {
|
|
7
|
+
describe("Accessibility", () => {
|
|
8
|
+
test("ProseSection renders as section landmark", async () => {
|
|
9
|
+
render(
|
|
10
|
+
<Prose>
|
|
11
|
+
<ProseSection heading="Test Section" data-testid="section">
|
|
12
|
+
<p>Content</p>
|
|
13
|
+
</ProseSection>
|
|
14
|
+
</Prose>,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const section = page.getByTestId("section");
|
|
18
|
+
await expect.element(section).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("ProseSection heading renders as h2 by default", async () => {
|
|
22
|
+
render(
|
|
23
|
+
<Prose>
|
|
24
|
+
<ProseSection heading="Default Heading">
|
|
25
|
+
<p>Content</p>
|
|
26
|
+
</ProseSection>
|
|
27
|
+
</Prose>,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
await expect
|
|
31
|
+
.element(
|
|
32
|
+
page.getByRole("heading", { level: 2, name: "Default Heading" }),
|
|
33
|
+
)
|
|
34
|
+
.toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("ProseSection heading renders as h3 when specified", async () => {
|
|
38
|
+
render(
|
|
39
|
+
<Prose>
|
|
40
|
+
<ProseSection heading="H3 Heading" as="h3">
|
|
41
|
+
<p>Content</p>
|
|
42
|
+
</ProseSection>
|
|
43
|
+
</Prose>,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
await expect
|
|
47
|
+
.element(page.getByRole("heading", { level: 3, name: "H3 Heading" }))
|
|
48
|
+
.toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("prose content is readable", async () => {
|
|
52
|
+
render(
|
|
53
|
+
<Prose>
|
|
54
|
+
<ProseSection heading="Readable Content">
|
|
55
|
+
<p>This is some readable body text.</p>
|
|
56
|
+
</ProseSection>
|
|
57
|
+
</Prose>,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
await expect
|
|
61
|
+
.element(page.getByText("This is some readable body text."))
|
|
62
|
+
.toBeInTheDocument();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("Props", () => {
|
|
67
|
+
test("Prose renders children", async () => {
|
|
68
|
+
render(
|
|
69
|
+
<Prose data-testid="prose">
|
|
70
|
+
<ProseSection heading="Child Section">
|
|
71
|
+
<p>Child content</p>
|
|
72
|
+
</ProseSection>
|
|
73
|
+
</Prose>,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
await expect.element(page.getByText("Child content")).toBeInTheDocument();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("Prose supports custom className", async () => {
|
|
80
|
+
render(
|
|
81
|
+
<Prose className="custom-prose" data-testid="prose">
|
|
82
|
+
<ProseSection heading="Test">
|
|
83
|
+
<p>Content</p>
|
|
84
|
+
</ProseSection>
|
|
85
|
+
</Prose>,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const prose = page.getByTestId("prose");
|
|
89
|
+
await expect.element(prose).toHaveClass(/custom-prose/);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("ProseSection supports custom className", async () => {
|
|
93
|
+
render(
|
|
94
|
+
<Prose>
|
|
95
|
+
<ProseSection
|
|
96
|
+
heading="Test"
|
|
97
|
+
className="custom-section"
|
|
98
|
+
data-testid="section"
|
|
99
|
+
>
|
|
100
|
+
<p>Content</p>
|
|
101
|
+
</ProseSection>
|
|
102
|
+
</Prose>,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const section = page.getByTestId("section");
|
|
106
|
+
await expect.element(section).toHaveClass(/custom-section/);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("Styling", () => {
|
|
111
|
+
test("Prose has max-width constraint", async () => {
|
|
112
|
+
render(
|
|
113
|
+
<Prose data-testid="prose">
|
|
114
|
+
<ProseSection heading="Test">
|
|
115
|
+
<p>Content</p>
|
|
116
|
+
</ProseSection>
|
|
117
|
+
</Prose>,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const prose = page.getByTestId("prose");
|
|
121
|
+
await expect.element(prose).toHaveClass(/max-w-\[700px\]/);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("h2 heading has correct typography class", async () => {
|
|
125
|
+
render(
|
|
126
|
+
<Prose>
|
|
127
|
+
<ProseSection heading="H2 Test">
|
|
128
|
+
<p>Content</p>
|
|
129
|
+
</ProseSection>
|
|
130
|
+
</Prose>,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const heading = page.getByRole("heading", { name: "H2 Test" });
|
|
134
|
+
await expect.element(heading).toHaveClass(/typography-headline-medium/);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("h3 heading has correct typography class", async () => {
|
|
138
|
+
render(
|
|
139
|
+
<Prose>
|
|
140
|
+
<ProseSection heading="H3 Test" as="h3">
|
|
141
|
+
<p>Content</p>
|
|
142
|
+
</ProseSection>
|
|
143
|
+
</Prose>,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const heading = page.getByRole("heading", { name: "H3 Test" });
|
|
147
|
+
await expect.element(heading).toHaveClass(/typography-headline-small/);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("Composition", () => {
|
|
152
|
+
test("renders multiple sections", async () => {
|
|
153
|
+
render(
|
|
154
|
+
<Prose>
|
|
155
|
+
<ProseSection heading="First">
|
|
156
|
+
<p>First content</p>
|
|
157
|
+
</ProseSection>
|
|
158
|
+
<ProseSection heading="Second">
|
|
159
|
+
<p>Second content</p>
|
|
160
|
+
</ProseSection>
|
|
161
|
+
<ProseSection heading="Third">
|
|
162
|
+
<p>Third content</p>
|
|
163
|
+
</ProseSection>
|
|
164
|
+
</Prose>,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
await expect
|
|
168
|
+
.element(page.getByRole("heading", { name: "First" }))
|
|
169
|
+
.toBeInTheDocument();
|
|
170
|
+
await expect
|
|
171
|
+
.element(page.getByRole("heading", { name: "Second" }))
|
|
172
|
+
.toBeInTheDocument();
|
|
173
|
+
await expect
|
|
174
|
+
.element(page.getByRole("heading", { name: "Third" }))
|
|
175
|
+
.toBeInTheDocument();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
export interface ProseProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Prose container for long-form content with proper typography and spacing.
|
|
10
|
+
*
|
|
11
|
+
* Provides responsive spacing between content blocks:
|
|
12
|
+
* - Desktop (1440px+): 96px gap
|
|
13
|
+
* - Tablet (768px+): 72px gap
|
|
14
|
+
* - Mobile: 56px gap
|
|
15
|
+
*
|
|
16
|
+
* Use with ProseSection components for proper content structure.
|
|
17
|
+
*/
|
|
18
|
+
const Prose = React.forwardRef<HTMLDivElement, ProseProps>(
|
|
19
|
+
({ className, children, ...props }, ref) => {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
ref={ref}
|
|
23
|
+
className={cn(
|
|
24
|
+
"flex w-full max-w-[700px] flex-col overflow-hidden",
|
|
25
|
+
// Responsive gap between sections
|
|
26
|
+
"gap-spacing-56 md:gap-spacing-72 xl:gap-spacing-96",
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
Prose.displayName = "Prose";
|
|
37
|
+
|
|
38
|
+
export interface ProseSectionProps extends React.HTMLAttributes<HTMLElement> {
|
|
39
|
+
/**
|
|
40
|
+
* The heading text for this section
|
|
41
|
+
*/
|
|
42
|
+
heading: string;
|
|
43
|
+
/**
|
|
44
|
+
* The heading level to render (h2 or h3)
|
|
45
|
+
* @default "h2"
|
|
46
|
+
*/
|
|
47
|
+
as?: "h2" | "h3";
|
|
48
|
+
children: React.ReactNode;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A section within Prose content, containing a heading and body text.
|
|
53
|
+
*
|
|
54
|
+
* Responsive typography:
|
|
55
|
+
* - h2: Uses typography-headline-medium (42px mobile → 56px tablet → 72px desktop)
|
|
56
|
+
* - h3: Uses typography-headline-small (32px mobile → 42px tablet → 56px desktop)
|
|
57
|
+
* - Body: Uses typography-body-medium (16px mobile/tablet → 18px desktop)
|
|
58
|
+
*/
|
|
59
|
+
const ProseSection = React.forwardRef<HTMLElement, ProseSectionProps>(
|
|
60
|
+
({ className, heading, as = "h2", children, ...props }, ref) => {
|
|
61
|
+
const Heading = as;
|
|
62
|
+
const headingClass =
|
|
63
|
+
as === "h2" ? "typography-headline-medium" : "typography-headline-small";
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<section
|
|
67
|
+
ref={ref}
|
|
68
|
+
className={cn(
|
|
69
|
+
"flex w-full flex-col",
|
|
70
|
+
// Responsive gap between heading and body
|
|
71
|
+
"gap-spacing-24 md:gap-spacing-40",
|
|
72
|
+
className,
|
|
73
|
+
)}
|
|
74
|
+
{...props}
|
|
75
|
+
>
|
|
76
|
+
<Heading className={cn(headingClass, "text-gray-900")}>
|
|
77
|
+
{heading}
|
|
78
|
+
</Heading>
|
|
79
|
+
<div className="typography-body-medium flex flex-col gap-[1em] text-gray-800 break-words">
|
|
80
|
+
{children}
|
|
81
|
+
</div>
|
|
82
|
+
</section>
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
ProseSection.displayName = "ProseSection";
|
|
87
|
+
|
|
88
|
+
export { Prose, ProseSection };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { render } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { page } from "vitest/browser";
|
|
4
|
+
import { Prose, ProseSection } from "./prose";
|
|
5
|
+
|
|
6
|
+
describe("Prose Visual Regression", () => {
|
|
7
|
+
test("prose with single section renders correctly", async () => {
|
|
8
|
+
render(
|
|
9
|
+
<div
|
|
10
|
+
style={{ width: "800px", backgroundColor: "#ffffff", padding: "40px" }}
|
|
11
|
+
>
|
|
12
|
+
<Prose data-testid="prose">
|
|
13
|
+
<ProseSection heading="Section Heading">
|
|
14
|
+
<p>
|
|
15
|
+
This is a paragraph of body text that demonstrates the prose
|
|
16
|
+
component's typography and spacing. It should be easy to read and
|
|
17
|
+
properly formatted.
|
|
18
|
+
</p>
|
|
19
|
+
</ProseSection>
|
|
20
|
+
</Prose>
|
|
21
|
+
</div>,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
await expect(page.getByTestId("prose")).toMatchScreenshot(
|
|
25
|
+
"prose-single-section",
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("prose with multiple sections renders correctly", async () => {
|
|
30
|
+
render(
|
|
31
|
+
<div
|
|
32
|
+
style={{ width: "800px", backgroundColor: "#ffffff", padding: "40px" }}
|
|
33
|
+
>
|
|
34
|
+
<Prose data-testid="prose">
|
|
35
|
+
<ProseSection heading="First Section">
|
|
36
|
+
<p>
|
|
37
|
+
This is the first section of content with some body text to
|
|
38
|
+
demonstrate spacing between sections.
|
|
39
|
+
</p>
|
|
40
|
+
</ProseSection>
|
|
41
|
+
<ProseSection heading="Second Section">
|
|
42
|
+
<p>
|
|
43
|
+
This is the second section showing how multiple sections stack
|
|
44
|
+
with proper gaps between them.
|
|
45
|
+
</p>
|
|
46
|
+
</ProseSection>
|
|
47
|
+
</Prose>
|
|
48
|
+
</div>,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
await expect(page.getByTestId("prose")).toMatchScreenshot(
|
|
52
|
+
"prose-multiple-sections",
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("prose with h3 heading renders correctly", async () => {
|
|
57
|
+
render(
|
|
58
|
+
<div
|
|
59
|
+
style={{ width: "800px", backgroundColor: "#ffffff", padding: "40px" }}
|
|
60
|
+
>
|
|
61
|
+
<Prose data-testid="prose">
|
|
62
|
+
<ProseSection heading="H3 Heading" as="h3">
|
|
63
|
+
<p>
|
|
64
|
+
This section uses an h3 heading which has smaller typography than
|
|
65
|
+
the default h2.
|
|
66
|
+
</p>
|
|
67
|
+
</ProseSection>
|
|
68
|
+
</Prose>
|
|
69
|
+
</div>,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
await expect(page.getByTestId("prose")).toMatchScreenshot(
|
|
73
|
+
"prose-h3-heading",
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("prose with multiple paragraphs renders correctly", async () => {
|
|
78
|
+
render(
|
|
79
|
+
<div
|
|
80
|
+
style={{ width: "800px", backgroundColor: "#ffffff", padding: "40px" }}
|
|
81
|
+
>
|
|
82
|
+
<Prose data-testid="prose">
|
|
83
|
+
<ProseSection heading="Long Content">
|
|
84
|
+
<p>
|
|
85
|
+
First paragraph with some content to show how multiple paragraphs
|
|
86
|
+
are spaced within a prose section.
|
|
87
|
+
</p>
|
|
88
|
+
<p>
|
|
89
|
+
Second paragraph demonstrating the gap between paragraphs in the
|
|
90
|
+
prose component.
|
|
91
|
+
</p>
|
|
92
|
+
<p>
|
|
93
|
+
Third paragraph to show consistent spacing throughout the content
|
|
94
|
+
area.
|
|
95
|
+
</p>
|
|
96
|
+
</ProseSection>
|
|
97
|
+
</Prose>
|
|
98
|
+
</div>,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
await expect(page.getByTestId("prose")).toMatchScreenshot(
|
|
102
|
+
"prose-multiple-paragraphs",
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { River, type RiverProps, riverVariants } from "./river";
|