@hraness/design-kit 0.35.1 → 0.35.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hraness/design-kit",
3
- "version": "0.35.1",
3
+ "version": "0.35.2",
4
4
  "description": "React components and CSS for Hraness apps and product sites: application shells, marketing sections, charts, themes, effects, and syntax highlighting. Built on @hraness/ui.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -6,7 +6,7 @@
6
6
  "name": "@hraness/design-kit",
7
7
  "path": ".",
8
8
  "visibility": "public",
9
- "version": "0.35.1"
9
+ "version": "0.35.2"
10
10
  }
11
11
  ],
12
12
  "contract": "hraness.portfolio-inventory/v1",
@@ -83,6 +83,8 @@ export interface ArticleHtmlInput {
83
83
  readonly id?: string;
84
84
  readonly provenance: ArticleProvenanceRecord | null;
85
85
  readonly published: ArticleIsoDate;
86
+ /** Show publication and update dates. Defaults to true; dates remain required and validated when hidden. */
87
+ readonly showDates?: boolean;
86
88
  readonly toc?: readonly ArticleTocItem[];
87
89
  readonly tocLabel?: string;
88
90
  readonly updated?: ArticleIsoDate;
@@ -91,7 +93,7 @@ export interface ArticleHtmlInput {
91
93
  /** The static equivalent of `MarketingArticle`. */
92
94
  export function renderArticleHtml(input: ArticleHtmlInput): string {
93
95
  const headingId = input.headingId ?? "article-title";
94
- const { published, updated } = input;
96
+ const { published, updated, showDates = true } = input;
95
97
  assertArticleDates(updated === undefined ? { published } : { published, updated });
96
98
  const toc = input.toc ?? [];
97
99
  for (const item of toc) {
@@ -99,16 +101,16 @@ export function renderArticleHtml(input: ArticleHtmlInput): string {
99
101
  }
100
102
  const tocId = `${headingId}-contents`;
101
103
  const meta = [
102
- input.author === undefined ? "" : renderArticleBylineHtml(input.author) + SEPARATOR,
103
- dateHtml("Published", published),
104
- updated === undefined ? "" : SEPARATOR + dateHtml("Updated", updated),
105
- ].join("");
104
+ input.author === undefined ? "" : renderArticleBylineHtml(input.author),
105
+ showDates ? dateHtml("Published", published) : "",
106
+ showDates && updated !== undefined ? dateHtml("Updated", updated) : "",
107
+ ].filter(Boolean).join(SEPARATOR);
106
108
  const header = [
107
109
  '<header class="plain-publication__article-header">',
108
110
  present(input.eyebrow) ? `<p class="plain-publication__eyebrow">${escapeArticleHtml(input.eyebrow)}</p>` : "",
109
111
  `<h1 id="${escapeArticleHtml(headingId)}">${escapeArticleHtml(input.heading)}</h1>`,
110
112
  present(input.dek) ? `<p class="plain-publication__article-dek">${escapeArticleHtml(input.dek)}</p>` : "",
111
- `<p class="plain-publication__article-meta">${meta}</p>`,
113
+ meta === "" ? "" : `<p class="plain-publication__article-meta">${meta}</p>`,
112
114
  input.provenance === null ? "" : renderArticleProvenanceHtml(input.provenance),
113
115
  "</header>",
114
116
  ].join("");
@@ -135,21 +137,35 @@ export function renderArticleHtml(input: ArticleHtmlInput): string {
135
137
  export function renderArticleSourcesHtml({
136
138
  heading = ARTICLE_SOURCES_HEADING,
137
139
  headingId = "article-sources",
140
+ showDates = true,
138
141
  sources,
139
- }: Readonly<{ heading?: string; headingId?: string; sources: readonly ArticleSourceItem[] }>): string {
142
+ }: Readonly<{
143
+ heading?: string;
144
+ headingId?: string;
145
+ /** Show check dates. Defaults to true; checkedOn remains required and validated when hidden. */
146
+ showDates?: boolean;
147
+ sources: readonly ArticleSourceItem[];
148
+ }>): string {
140
149
  if (sources.length === 0) return "";
141
- for (const source of sources) assertArticleHref(source.href);
150
+ for (const source of sources) {
151
+ assertArticleHref(source.href);
152
+ formatArticleDate(source.checkedOn);
153
+ }
142
154
  return [
143
155
  `<section aria-labelledby="${escapeArticleHtml(headingId)}" class="plain-publication__sources">`,
144
156
  `<h2 id="${escapeArticleHtml(headingId)}">${escapeArticleHtml(heading)}</h2>`,
145
157
  "<ol>",
146
- ...sources.map((source) => [
147
- `<li><a href="${escapeArticleHtml(source.href)}">${escapeArticleHtml(source.title)}</a>`,
148
- "<span>",
149
- present(source.publisher) ? escapeArticleHtml(source.publisher) + SEPARATOR : "",
150
- dateHtml("Checked", source.checkedOn),
151
- "</span></li>",
152
- ].join("")),
158
+ ...sources.map((source) => {
159
+ const meta = [
160
+ present(source.publisher) ? escapeArticleHtml(source.publisher) : "",
161
+ showDates ? dateHtml("Checked", source.checkedOn) : "",
162
+ ].filter(Boolean).join(SEPARATOR);
163
+ return [
164
+ `<li><a href="${escapeArticleHtml(source.href)}">${escapeArticleHtml(source.title)}</a>`,
165
+ meta === "" ? "" : `<span>${meta}</span>`,
166
+ "</li>",
167
+ ].join("");
168
+ }),
153
169
  "</ol></section>",
154
170
  ].join("");
155
171
  }
@@ -228,6 +244,7 @@ export function renderArticleIndexHtml({
228
244
  headingLevel = 2,
229
245
  id,
230
246
  items,
247
+ showDates = true,
231
248
  summary,
232
249
  }: Readonly<{
233
250
  className?: string;
@@ -236,6 +253,8 @@ export function renderArticleIndexHtml({
236
253
  headingLevel?: 1 | 2 | 3 | 4 | 5;
237
254
  id?: string;
238
255
  items: readonly ArticleIndexItem[];
256
+ /** Show publication and update dates. Defaults to true; item dates remain required and validated when hidden. */
257
+ showDates?: boolean;
239
258
  summary?: string;
240
259
  }>): string {
241
260
  if (![1, 2, 3, 4, 5].includes(headingLevel)) throw new RangeError("Article index heading level must be 1 to 5.");
@@ -259,10 +278,13 @@ export function renderArticleIndexHtml({
259
278
  present(item.eyebrow) ? `<p class="plain-publication__entry-label">${escapeArticleHtml(item.eyebrow)}</p>` : "",
260
279
  `<${entry} class="plain-publication__entry-title"><a href="${escapeArticleHtml(item.href)}">${escapeArticleHtml(item.title)}</a></${entry}>`,
261
280
  `<p class="plain-publication__entry-dek">${escapeArticleHtml(item.dek)}</p>`,
262
- '<p class="plain-publication__entry-meta">',
263
- dateHtml("Published", item.published),
264
- item.updated === undefined ? "" : SEPARATOR + dateHtml("Updated", item.updated),
265
- "</p></article>",
281
+ showDates ? [
282
+ '<p class="plain-publication__entry-meta">',
283
+ dateHtml("Published", item.published),
284
+ item.updated === undefined ? "" : SEPARATOR + dateHtml("Updated", item.updated),
285
+ "</p>",
286
+ ].join("") : "",
287
+ "</article>",
266
288
  ].join("")),
267
289
  "</div></section>",
268
290
  ].join("");
@@ -93,6 +93,8 @@ export interface MarketingArticleProps {
93
93
  /** Required so every article decides its drafting note. Pass null only for writing with no AI involvement and no review record. */
94
94
  readonly provenance: ArticleProvenanceRecord | null;
95
95
  readonly published: ArticleIsoDate;
96
+ /** Show publication and update dates. Defaults to true; dates remain required and validated when hidden. */
97
+ readonly showDates?: boolean;
96
98
  readonly toc?: readonly ArticleTocItem[];
97
99
  readonly tocLabel?: string;
98
100
  readonly updated?: ArticleIsoDate;
@@ -116,6 +118,7 @@ export function MarketingArticle({
116
118
  id,
117
119
  provenance,
118
120
  published,
121
+ showDates = true,
119
122
  toc,
120
123
  tocLabel = ARTICLE_TOC_LABEL,
121
124
  updated,
@@ -138,11 +141,13 @@ export function MarketingArticle({
138
141
  {eyebrow === undefined || eyebrow === "" ? null : <p className="plain-publication__eyebrow">{eyebrow}</p>}
139
142
  <h1 id={headingId}>{heading}</h1>
140
143
  {dek === undefined || dek === "" ? null : <p className="plain-publication__article-dek">{dek}</p>}
141
- <p className="plain-publication__article-meta">
142
- {author === undefined ? null : <><ArticleByline author={author} /><Separator /></>}
143
- <ArticleDate label="Published" value={published} />
144
- {updated === undefined ? null : <><Separator /><ArticleDate label="Updated" value={updated} /></>}
145
- </p>
144
+ {author === undefined && !showDates ? null : (
145
+ <p className="plain-publication__article-meta">
146
+ {author === undefined ? null : <><ArticleByline author={author} />{showDates ? <Separator /> : null}</>}
147
+ {showDates ? <ArticleDate label="Published" value={published} /> : null}
148
+ {!showDates || updated === undefined ? null : <><Separator /><ArticleDate label="Updated" value={updated} /></>}
149
+ </p>
150
+ )}
146
151
  {provenance === null ? null : <ArticleProvenance provenance={provenance} />}
147
152
  </header>
148
153
  <div className="plain-publication__article-layout">
@@ -165,14 +170,20 @@ export function MarketingArticle({
165
170
  export function ArticleSources({
166
171
  heading = ARTICLE_SOURCES_HEADING,
167
172
  headingId = "article-sources",
173
+ showDates = true,
168
174
  sources,
169
175
  }: Readonly<{
170
176
  heading?: string;
171
177
  headingId?: string;
178
+ /** Show check dates. Defaults to true; checkedOn remains required and validated when hidden. */
179
+ showDates?: boolean;
172
180
  sources: readonly ArticleSourceItem[];
173
181
  }>) {
174
182
  if (sources.length === 0) return null;
175
- for (const source of sources) assertArticleHref(source.href);
183
+ for (const source of sources) {
184
+ assertArticleHref(source.href);
185
+ formatArticleDate(source.checkedOn);
186
+ }
176
187
  return (
177
188
  <section aria-labelledby={headingId} className="plain-publication__sources">
178
189
  <h2 id={headingId}>{heading}</h2>
@@ -180,10 +191,12 @@ export function ArticleSources({
180
191
  {sources.map((source) => (
181
192
  <li key={`${source.href}-${source.title}`}>
182
193
  <a href={source.href}>{source.title}</a>
183
- <span>
184
- {source.publisher === undefined || source.publisher === "" ? null : <>{source.publisher}<Separator /></>}
185
- <ArticleDate label="Checked" value={source.checkedOn} />
186
- </span>
194
+ {!showDates && (source.publisher === undefined || source.publisher === "") ? null : (
195
+ <span>
196
+ {source.publisher === undefined || source.publisher === "" ? null : <>{source.publisher}{showDates ? <Separator /> : null}</>}
197
+ {showDates ? <ArticleDate label="Checked" value={source.checkedOn} /> : null}
198
+ </span>
199
+ )}
187
200
  </li>
188
201
  ))}
189
202
  </ol>
@@ -253,6 +266,7 @@ export function ArticleIndex({
253
266
  headingLevel = 2,
254
267
  id,
255
268
  items,
269
+ showDates = true,
256
270
  summary,
257
271
  }: Readonly<{
258
272
  className?: string;
@@ -261,6 +275,8 @@ export function ArticleIndex({
261
275
  headingLevel?: 1 | 2 | 3 | 4 | 5;
262
276
  id?: string;
263
277
  items: readonly ArticleIndexItem[];
278
+ /** Show publication and update dates. Defaults to true; item dates remain required and validated when hidden. */
279
+ showDates?: boolean;
264
280
  summary?: string;
265
281
  }>) {
266
282
  if (![1, 2, 3, 4, 5].includes(headingLevel)) throw new RangeError("Article index heading level must be 1 to 5.");
@@ -290,10 +306,12 @@ export function ArticleIndex({
290
306
  {item.eyebrow === undefined || item.eyebrow === "" ? null : <p className="plain-publication__entry-label">{item.eyebrow}</p>}
291
307
  <EntryTag className="plain-publication__entry-title"><a href={item.href}>{item.title}</a></EntryTag>
292
308
  <p className="plain-publication__entry-dek">{item.dek}</p>
293
- <p className="plain-publication__entry-meta">
294
- <ArticleDate label="Published" value={item.published} />
295
- {item.updated === undefined ? null : <><Separator /><ArticleDate label="Updated" value={item.updated} /></>}
296
- </p>
309
+ {showDates ? (
310
+ <p className="plain-publication__entry-meta">
311
+ <ArticleDate label="Published" value={item.published} />
312
+ {item.updated === undefined ? null : <><Separator /><ArticleDate label="Updated" value={item.updated} /></>}
313
+ </p>
314
+ ) : null}
297
315
  </article>
298
316
  ))}
299
317
  </div>
@@ -678,6 +678,7 @@ export function DesignSystemGallery({
678
678
  after={(
679
679
  <ArticleSources
680
680
  headingId="gallery-article-sources"
681
+ showDates={false}
681
682
  sources={[
682
683
  { checkedOn: "2026-09-20", href: "#articles", publisher: "Relay", title: "Relay 2.4 release notes" },
683
684
  ]}
@@ -693,6 +694,7 @@ export function DesignSystemGallery({
693
694
  review: { reviewer: "an independent AI editorial review", reviewerType: "ai" },
694
695
  }}
695
696
  published="2026-09-10"
697
+ showDates={false}
696
698
  toc={[
697
699
  { href: "#gallery-article-problem", label: "The problem" },
698
700
  { href: "#gallery-article-approach", label: "The approach" },