@hraness/design-kit 0.35.1 → 0.35.3

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.3",
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.3"
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("");
@@ -229,73 +229,84 @@ export function ModeShowcase<S extends string, M extends string, O extends strin
229
229
  return (
230
230
  <figure
231
231
  aria-label={label?.(surface) ?? `Illustration of ${surface.label.toLowerCase()}`}
232
- className={joinMockupClasses("hkm-showcase", className)}
232
+ className={joinMockupClasses("hkm-showcase", "hkm-modes", className)}
233
233
  data-hkm-theme={theme}
234
234
  data-nosnippet=""
235
235
  >
236
- <div className="hkm-showcase-controls">
237
- {surfaces.length > 1 ? (
238
- <div aria-label="Surface" className="hkm-tabs" role="tablist">
239
- {surfaces.map((entry) => (
240
- <button
241
- aria-controls={`${id}-panel`}
242
- aria-selected={entry.id === surface.id}
243
- className="hkm-tab"
244
- id={`${id}-tab-${entry.id}`}
245
- key={entry.id}
246
- onClick={() => setSurfaceId(entry.id)}
247
- onKeyDown={onTabKey}
248
- ref={(element) => {
249
- if (element === null) tabs.current.delete(entry.id);
250
- else tabs.current.set(entry.id, element);
251
- }}
252
- role="tab"
253
- tabIndex={entry.id === surface.id ? 0 : -1}
254
- type="button"
255
- >
256
- {entry.label}
257
- </button>
258
- ))}
259
- </div>
260
- ) : null}
261
- <div className="hkm-showcase-settings">
262
- <div className="hkm-showcase-row">
263
- <span className="hkm-showcase-label" id={`${id}-mode`}>{modeLabel}</span>
264
- <div aria-labelledby={`${id}-mode`} className="hkm-segmented" role="group">
265
- {modes.map((entry) => (
266
- <button aria-pressed={entry.id === mode} key={entry.id} onClick={() => chooseMode(entry.id)} type="button">
267
- {entry.label}
268
- </button>
269
- ))}
270
- </div>
271
- </div>
272
- {options === undefined ? null : (
273
- <div className="hkm-showcase-row">
274
- <span className="hkm-showcase-label" id={`${id}-option`}>{optionLabel}</span>
275
- <div aria-labelledby={`${id}-option`} className="hkm-segmented" role="group">
276
- {options.map((entry) => (
277
- <button aria-pressed={entry.id === option} disabled={optionInactive} key={entry.id} onClick={() => chooseOption(entry.id)} type="button">
278
- {entry.label}
279
- </button>
280
- ))}
281
- </div>
282
- </div>
283
- )}
236
+ {surfaces.length > 1 ? (
237
+ <div aria-label="Surface" className="hkm-tabs hkm-folder-tabs" role="tablist">
238
+ {surfaces.map((entry) => (
239
+ <button
240
+ aria-controls={`${id}-panel`}
241
+ aria-selected={entry.id === surface.id}
242
+ className="hkm-tab"
243
+ id={`${id}-tab-${entry.id}`}
244
+ key={entry.id}
245
+ onClick={() => setSurfaceId(entry.id)}
246
+ onKeyDown={onTabKey}
247
+ ref={(element) => {
248
+ if (element === null) tabs.current.delete(entry.id);
249
+ else tabs.current.set(entry.id, element);
250
+ }}
251
+ role="tab"
252
+ tabIndex={entry.id === surface.id ? 0 : -1}
253
+ type="button"
254
+ >
255
+ {entry.label}
256
+ </button>
257
+ ))}
284
258
  </div>
285
- {hint === "" ? null : <p className="hkm-showcase-hint">{hint}</p>}
286
- </div>
259
+ ) : null}
287
260
  <div
288
261
  aria-labelledby={surfaces.length > 1 ? `${id}-tab-${surface.id}` : undefined}
289
- className="hkm-showcase-stage"
290
- data-hkm-animated={animated ? "" : undefined}
291
- data-hkm-from={animated ? previousMode : undefined}
262
+ className="hkm-showcase-stage hkm-mode-panel"
292
263
  id={`${id}-panel`}
293
264
  role={surfaces.length > 1 ? "tabpanel" : undefined}
294
265
  style={{ "--hkm-page-height": `${height}px` } as CSSProperties}
295
266
  >
296
- <FitToWidth minWidth={minWidth}>
297
- {surface.render({ animated, mode, option, previousMode, theme })}
298
- </FitToWidth>
267
+ <div className="hkm-showcase-controls">
268
+ <div className="hkm-showcase-settings">
269
+ <div className="hkm-showcase-row">
270
+ <span className="hkm-showcase-label" id={`${id}-mode`}>{modeLabel}</span>
271
+ <div aria-labelledby={`${id}-mode`} className="hkm-segmented" role="group">
272
+ {modes.map((entry) => (
273
+ <button aria-pressed={entry.id === mode} key={entry.id} onClick={() => chooseMode(entry.id)} type="button">
274
+ {entry.label}
275
+ </button>
276
+ ))}
277
+ </div>
278
+ </div>
279
+ {options === undefined ? null : (
280
+ <div className="hkm-showcase-row">
281
+ <span className="hkm-showcase-label" id={`${id}-option`}>{optionLabel}</span>
282
+ <div aria-labelledby={`${id}-option`} className="hkm-segmented" role="group">
283
+ {options.map((entry) => (
284
+ <button aria-pressed={entry.id === option} disabled={optionInactive} key={entry.id} onClick={() => chooseOption(entry.id)} type="button">
285
+ {entry.label}
286
+ </button>
287
+ ))}
288
+ </div>
289
+ </div>
290
+ )}
291
+ </div>
292
+ {hint === "" ? null : <p className="hkm-showcase-hint">{hint}</p>}
293
+ </div>
294
+ <div className="hkm-mode-stage">
295
+ {surfaces.map((entry) => (
296
+ <div
297
+ aria-hidden={entry.id !== surface.id}
298
+ className="hkm-mode-surface"
299
+ data-hkm-animated={entry.id === surface.id && animated ? "" : undefined}
300
+ data-hkm-from={entry.id === surface.id && animated ? previousMode : undefined}
301
+ inert={entry.id !== surface.id}
302
+ key={entry.id}
303
+ >
304
+ <FitToWidth minWidth={minWidth}>
305
+ {entry.render({ animated: entry.id === surface.id && animated, mode, option, previousMode, theme })}
306
+ </FitToWidth>
307
+ </div>
308
+ ))}
309
+ </div>
299
310
  </div>
300
311
  {!hasStatus && captionText === undefined ? null : (
301
312
  <figcaption className="hkm-showcase-caption">
@@ -367,7 +378,7 @@ export function StepThrough({
367
378
  return (
368
379
  <figure aria-label={`Illustration: ${label.toLowerCase()}`} className={joinMockupClasses("hkm-showcase", "hkm-steps", className)} data-hkm-theme={theme} data-nosnippet="">
369
380
  <div className="hkm-showcase-controls">
370
- <div aria-label={label} className="hkm-tabs hkm-step-tabs" role="tablist">
381
+ <div aria-label={label} className="hkm-tabs hkm-folder-tabs hkm-step-tabs" role="tablist">
371
382
  {steps.map((entry, position) => (
372
383
  <button
373
384
  aria-controls={`${id}-panel-${entry.id}`}
package/src/mockups.css CHANGED
@@ -2503,14 +2503,22 @@
2503
2503
  }
2504
2504
  }
2505
2505
 
2506
- /* Folder tabs share an edge with the stable-height walkthrough stage. */
2507
- .hkm-showcase.hkm-steps { gap: 0; }
2506
+ /* Folder tabs share an edge with each showcase panel. */
2507
+ .hkm-showcase:is(.hkm-steps, .hkm-modes) { gap: 0; }
2508
2508
  .hkm-showcase.hkm-steps .hkm-showcase-controls { padding: 0; border: 0; border-radius: 0; background: transparent; }
2509
- .hkm-showcase.hkm-steps .hkm-step-tabs { gap: 6px; margin: 0; padding: 0; border: 0; position: relative; z-index: 1; }
2510
- .hkm-showcase.hkm-steps .hkm-tab { min-block-size: 48px; padding: 12px 18px; border: 1px solid var(--hkm-sc-line); border-block-end: 0; border-radius: 12px 12px 0 0; background: var(--hkm-sc-field); color: var(--hkm-sc-muted); }
2511
- .hkm-showcase.hkm-steps .hkm-tab[aria-selected="true"] { background: var(--hkm-sc-bg); color: var(--hkm-sc-fg); }
2512
- .hkm-showcase.hkm-steps .hkm-tab:hover { color: var(--hkm-sc-fg); }
2509
+ .hkm-showcase .hkm-folder-tabs { gap: 6px; margin: 0; padding: 0; border: 0; position: relative; z-index: 1; }
2510
+ .hkm-showcase .hkm-folder-tabs .hkm-tab { min-block-size: 48px; padding: 12px 18px; border: 1px solid var(--hkm-sc-line); border-block-end: 0; border-radius: 12px 12px 0 0; background: var(--hkm-sc-field); color: var(--hkm-sc-muted); }
2511
+ .hkm-showcase .hkm-folder-tabs .hkm-tab[aria-selected="true"] { background: var(--hkm-sc-bg); color: var(--hkm-sc-fg); }
2512
+ .hkm-showcase .hkm-folder-tabs .hkm-tab:hover { color: var(--hkm-sc-fg); }
2513
2513
  .hkm-showcase.hkm-steps .hkm-step-stage { display: grid; border: 1px solid var(--hkm-sc-line); border-radius: 0 12px 12px 12px; background: var(--hkm-sc-bg); margin-block-start: -1px; overflow: clip; }
2514
+ .hkm-showcase.hkm-modes .hkm-mode-panel { border: 1px solid var(--hkm-sc-line); border-radius: 12px; background: var(--hkm-sc-bg); overflow: clip; }
2515
+ .hkm-showcase.hkm-modes .hkm-folder-tabs + .hkm-mode-panel { border-start-start-radius: 0; margin-block-start: -1px; }
2516
+ .hkm-showcase.hkm-modes .hkm-showcase-controls { border: 0; border-block-end: 1px solid var(--hkm-sc-line); border-radius: 0; }
2517
+ .hkm-mode-stage { display: grid; min-inline-size: 0; }
2518
+ .hkm-mode-surface { grid-area: 1 / 1; min-inline-size: 0; align-self: start; }
2519
+ .hkm-mode-surface[aria-hidden="true"] { visibility: hidden; pointer-events: none; }
2520
+ .hkm-showcase.hkm-modes .hkm-mode-surface > .hkm-fit > .hkm-fit-inner > .hkm-root,
2521
+ .hkm-showcase.hkm-modes .hkm-mode-surface > .hkm-fit > .hkm-fit-inner > .hkm-root > .hkm-window { border: 0; border-radius: 0; box-shadow: none; }
2514
2522
  .hkm-step-panel { grid-area: 1 / 1; min-inline-size: 0; align-self: start; }
2515
2523
  .hkm-step-panel[aria-hidden="true"] { visibility: hidden; pointer-events: none; }
2516
2524
  .hkm-showcase.hkm-steps .hkm-step-panel .hkm-root { border-start-start-radius: 0; border-start-end-radius: 0; box-shadow: none; }
@@ -2520,7 +2528,7 @@
2520
2528
  .hkm-showcase.hkm-steps .hkm-step-button:disabled { color: var(--hkm-sc-muted); background: transparent; }
2521
2529
  .hkm-showcase.hkm-steps .hkm-step-button[data-hkm-primary]:disabled { background: var(--hkm-sc-field); }
2522
2530
  .hkm-showcase.hkm-steps .hkm-step-announcement { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
2523
- .hkm-showcase.hkm-steps .hkm-showcase-caption { margin-block-start: 1rem; }
2531
+ .hkm-showcase:is(.hkm-steps, .hkm-modes) .hkm-showcase-caption { margin-block-start: 1rem; }
2524
2532
 
2525
2533
  /* ---------------------------------------------------------------- */
2526
2534
  /* Motion */
@@ -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" },