@hackcove/ui-react 0.10.0 → 0.10.2

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.
@@ -1,4 +1,5 @@
1
1
  import { type ComponentPropsWithoutRef, type ElementType, type ReactElement, type ReactNode } from "react";
2
+ import type { Passthrough } from "./props.js";
2
3
  import type { Size } from "./size.js";
3
4
  export type TypographyColor = "default" | "secondary" | "tertiary" | "muted" | "brand";
4
5
  /**
@@ -77,8 +78,31 @@ export declare const PageHeading: {
77
78
  <E extends ElementType = "h1">(props: TypographyProps<E>): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
78
79
  displayName: string;
79
80
  };
81
+ /** True inside a `Prose`, where the text roles take the long-form scale. Internal. */
82
+ export declare function useProse(): boolean;
80
83
  /**
81
- * Top-level section header. `h2`, text-base/6 semibold, primary text. `rule` sets
84
+ * Sets the text roles back to the app scale around what a component renders itself (a `Dialog`, a
85
+ * `Drawer`, an `EmptyState`), so one opened from a docs page looks as it does anywhere else.
86
+ * Renders no element. Internal: not exported from the package.
87
+ */
88
+ export declare function AppScale({ children }: {
89
+ children?: ReactNode;
90
+ }): ReactElement;
91
+ /**
92
+ * Long-form content, for a docs page or an article. A block (`div`) that sets text-base/7 on itself,
93
+ * so a bare `<li>` or text node inside reads at 16px, and puts the text roles inside it on the
94
+ * prose scale: `Text` text-base/7, `SectionHeading` text-2xl/8, `SubsectionHeading` `lg` text-2xl/8,
95
+ * `md` text-xl/7 and `sm` text-base/7 (a semibold line at body size), and the box of a `Callout`
96
+ * text-base/7. Every other role keeps its size. Nothing outside a `Prose` changes, and a `Dialog`,
97
+ * `Drawer` or `EmptyState` inside one is back on the app scale. An app screen never uses it: wrap the
98
+ * body of a docs page or an article, inside a `ProseLayout`, a `SidebarLayout` or anywhere else.
99
+ */
100
+ export declare function Prose({ children, ...rest }: {
101
+ children?: ReactNode;
102
+ } & Passthrough): ReactElement;
103
+ /**
104
+ * Top-level section header. `h2`, text-base/6 semibold, primary text (text-2xl/8 semibold tight
105
+ * inside a `Prose`). `rule` sets
82
106
  * a hairline under it (the `Divider` line colour) with the space either side of
83
107
  * the line built in. `description` is a muted line under the heading, outside the `<h2>`, so the
84
108
  * heading's name stays the title; the rule then sits under the description.
@@ -92,7 +116,9 @@ export declare function SectionHeading<E extends ElementType = "h2">({ rule, des
92
116
  export type SubsectionHeadingSize = Size;
93
117
  /**
94
118
  * Nested header. `h3`, text-base/6 semibold, primary text. `size` steps it down for a dense card
95
- * or up for a drawer or form-group title: `sm` (text-sm), `md` (default) or `lg`. `truncate` keeps it on
119
+ * or up for a drawer or form-group title: `sm` (text-sm), `md` (default) or `lg`. Inside a `Prose`
120
+ * `md` is text-xl/7 (the h3 of a doc), `sm` text-base/7 (the h4: body size, set apart by its weight)
121
+ * and `lg` text-2xl/8, the size of a `SectionHeading` there. `truncate` keeps it on
96
122
  * one line and cuts it with an ellipsis where its container ends, the full text as the `title`.
97
123
  */
98
124
  export declare function SubsectionHeading<E extends ElementType = "h3">({ size, ...props }: TypographyProps<E> & {
@@ -115,7 +141,8 @@ export declare const Overline: {
115
141
  /** A fixed width for a run of text: `amount` is a money figure, right-aligned, at a width that lines a column up. */
116
142
  export type TextMinWidth = "amount";
117
143
  /**
118
- * Running text. `span` (inline; `as="p"` for a paragraph), text-sm/5 normal, secondary. A block of it
144
+ * Running text. `span` (inline; `as="p"` for a paragraph), text-sm/5 normal, secondary (text-base/7
145
+ * inside a `Prose`). A block of it
119
146
  * stops at a readable line length (`max-w-[72ch]`), wherever it sits. `strong` emphasises it:
120
147
  * font-medium in the primary ink, unless `color` or `tone` says otherwise.
121
148
  * `preserveNewlines` keeps the line breaks in the text (a user-typed note); runs of spaces still collapse.
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Children, } from "react";
2
+ import { Children, createContext, useContext, } from "react";
3
3
  import { cx } from "./cx.js";
4
4
  /** Colour token → slate/violet utility, which a theme remaps. */
5
5
  const COLOR = {
@@ -83,32 +83,72 @@ export const Lead = typography("Lead", "p", "text-lg/7 font-normal sm:text-xl/8"
83
83
  * `Lead` under it and the body text after it, so the page's title reads first.
84
84
  */
85
85
  export const PageHeading = typography("PageHeading", "h1", "text-3xl/9 font-bold tracking-tight sm:text-4xl/10", "default");
86
+ // --- Prose: the long-form scale ---------------------------------------------
86
87
  /**
87
- * Top-level section header. `h2`, text-base/6 semibold, primary text. `rule` sets
88
+ * Whether the text roles below are in a `Prose`. `Text`, `SectionHeading`, `SubsectionHeading` and
89
+ * `Callout` read it and pick their prose size; outside a `Prose` it is false and nothing changes.
90
+ * Internal: not exported from the package.
91
+ */
92
+ const ProseContext = createContext(false);
93
+ /** True inside a `Prose`, where the text roles take the long-form scale. Internal. */
94
+ export function useProse() {
95
+ return useContext(ProseContext);
96
+ }
97
+ /**
98
+ * Sets the text roles back to the app scale around what a component renders itself (a `Dialog`, a
99
+ * `Drawer`, an `EmptyState`), so one opened from a docs page looks as it does anywhere else.
100
+ * Renders no element. Internal: not exported from the package.
101
+ */
102
+ export function AppScale({ children }) {
103
+ return _jsx(ProseContext.Provider, { value: false, children: children });
104
+ }
105
+ /**
106
+ * Long-form content, for a docs page or an article. A block (`div`) that sets text-base/7 on itself,
107
+ * so a bare `<li>` or text node inside reads at 16px, and puts the text roles inside it on the
108
+ * prose scale: `Text` text-base/7, `SectionHeading` text-2xl/8, `SubsectionHeading` `lg` text-2xl/8,
109
+ * `md` text-xl/7 and `sm` text-base/7 (a semibold line at body size), and the box of a `Callout`
110
+ * text-base/7. Every other role keeps its size. Nothing outside a `Prose` changes, and a `Dialog`,
111
+ * `Drawer` or `EmptyState` inside one is back on the app scale. An app screen never uses it: wrap the
112
+ * body of a docs page or an article, inside a `ProseLayout`, a `SidebarLayout` or anywhere else.
113
+ */
114
+ export function Prose({ children, ...rest }) {
115
+ return (_jsx(ProseContext.Provider, { value: true, children: _jsx("div", { ...rest, className: "text-base/7", children: children }) }));
116
+ }
117
+ const SECTION = {
118
+ app: "text-base/6 font-semibold",
119
+ prose: "text-2xl/8 font-semibold tracking-tight",
120
+ };
121
+ /**
122
+ * Top-level section header. `h2`, text-base/6 semibold, primary text (text-2xl/8 semibold tight
123
+ * inside a `Prose`). `rule` sets
88
124
  * a hairline under it (the `Divider` line colour) with the space either side of
89
125
  * the line built in. `description` is a muted line under the heading, outside the `<h2>`, so the
90
126
  * heading's name stays the title; the rule then sits under the description.
91
127
  */
92
128
  export function SectionHeading({ rule, description, ...props }) {
93
129
  const hairline = "mb-4 border-b border-slate-200/70 pb-2";
130
+ const size = useProse() ? SECTION.prose : SECTION.app;
94
131
  const hasDescription = description != null && description !== false;
95
132
  if (!hasDescription) {
96
- return pinned("h2", cx(rule && hairline, "text-base/6 font-semibold"), "default", props);
133
+ return pinned("h2", cx(rule && hairline, size), "default", props);
97
134
  }
98
- return (_jsxs("div", { className: cx("flex flex-col gap-1", rule && hairline), children: [pinned("h2", "text-base/6 font-semibold", "default", props), _jsx(Text, { as: "p", color: "tertiary", children: description })] }));
135
+ return (_jsxs("div", { className: cx("flex flex-col gap-1", rule && hairline), children: [pinned("h2", size, "default", props), _jsx(Text, { as: "p", color: "tertiary", children: description })] }));
99
136
  }
100
137
  const SUBSECTION_SIZE = {
101
- sm: "text-sm/5 font-semibold",
102
- md: "text-base/6 font-semibold",
103
- lg: "text-lg/7 font-semibold",
138
+ sm: { app: "text-sm/5 font-semibold", prose: "text-base/7 font-semibold" },
139
+ md: { app: "text-base/6 font-semibold", prose: "text-xl/7 font-semibold" },
140
+ lg: { app: "text-lg/7 font-semibold", prose: "text-2xl/8 font-semibold tracking-tight" },
104
141
  };
105
142
  /**
106
143
  * Nested header. `h3`, text-base/6 semibold, primary text. `size` steps it down for a dense card
107
- * or up for a drawer or form-group title: `sm` (text-sm), `md` (default) or `lg`. `truncate` keeps it on
144
+ * or up for a drawer or form-group title: `sm` (text-sm), `md` (default) or `lg`. Inside a `Prose`
145
+ * `md` is text-xl/7 (the h3 of a doc), `sm` text-base/7 (the h4: body size, set apart by its weight)
146
+ * and `lg` text-2xl/8, the size of a `SectionHeading` there. `truncate` keeps it on
108
147
  * one line and cuts it with an ellipsis where its container ends, the full text as the `title`.
109
148
  */
110
149
  export function SubsectionHeading({ size = "md", ...props }) {
111
- return pinned("h3", SUBSECTION_SIZE[size], "default", props);
150
+ const scale = SUBSECTION_SIZE[size];
151
+ return pinned("h3", useProse() ? scale.prose : scale.app, "default", props);
112
152
  }
113
153
  // --- Inline roles (inline by default) --------------------------------------
114
154
  /**
@@ -117,7 +157,8 @@ export function SubsectionHeading({ size = "md", ...props }) {
117
157
  */
118
158
  export const Overline = inlineRole("Overline", "span", "text-xs font-semibold uppercase tracking-wide", "tertiary");
119
159
  /**
120
- * Running text. `span` (inline; `as="p"` for a paragraph), text-sm/5 normal, secondary. A block of it
160
+ * Running text. `span` (inline; `as="p"` for a paragraph), text-sm/5 normal, secondary (text-base/7
161
+ * inside a `Prose`). A block of it
121
162
  * stops at a readable line length (`max-w-[72ch]`), wherever it sits. `strong` emphasises it:
122
163
  * font-medium in the primary ink, unless `color` or `tone` says otherwise.
123
164
  * `preserveNewlines` keeps the line breaks in the text (a user-typed note); runs of spaces still collapse.
@@ -128,7 +169,8 @@ export const Overline = inlineRole("Overline", "span", "text-xs font-semibold up
128
169
  * fixes a width and right-aligns the figure in it (a column of amounts lines up); it does not take `align`.
129
170
  */
130
171
  export function Text({ strong, preserveNewlines, minWidth, italic, breakWords, ...props }) {
131
- return pinned("span", cx(textVariant(strong, preserveNewlines, true), italic && "italic", breakWords && "break-words", minWidth === "amount" && "inline-block min-w-24 text-end"), strong ? "default" : "secondary",
172
+ const prose = useProse();
173
+ return pinned("span", cx(textVariant(strong, preserveNewlines, true, prose), italic && "italic", breakWords && "break-words", minWidth === "amount" && "inline-block min-w-24 text-end"), strong ? "default" : "secondary",
132
174
  // An amount is always in tabular figures, so it lines up; `numeric` already emits them.
133
175
  { ...props, numeric: props.numeric || minWidth === "amount" });
134
176
  }
@@ -137,10 +179,10 @@ export function Text({ strong, preserveNewlines, minWidth, italic, breakWords, .
137
179
  * it runs the width of its dialog. Internal; not exported from the package.
138
180
  */
139
181
  export function HeaderText({ children }) {
140
- return pinned("p", textVariant(false, false, false), "secondary", { children });
182
+ return pinned("p", textVariant(false, false, false, useProse()), "secondary", { children });
141
183
  }
142
- function textVariant(strong, preserveNewlines, measure) {
143
- return cx(preserveNewlines && "whitespace-pre-line", measure && "max-w-[72ch]", "text-sm/5", strong ? "font-medium" : "font-normal");
184
+ function textVariant(strong, preserveNewlines, measure, prose) {
185
+ return cx(preserveNewlines && "whitespace-pre-line", measure && "max-w-[72ch]", prose ? "text-base/7" : "text-sm/5", strong ? "font-medium" : "font-normal");
144
186
  }
145
187
  /**
146
188
  * Fine print / metadata. `span` (inline; `as="p"` for a block), text-xs/4 normal, tertiary. Takes `tone`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hackcove/ui-react",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Hack Cove React components: primitives, typography roles and a safe Markdown renderer, written against Tailwind v4 utilities. Pair with a theme from @hackcove/themes.",
5
5
  "keywords": [
6
6
  "components",