radiant-docs 0.1.48 → 0.1.50

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": "radiant-docs",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
4
4
  "description": "CLI tool for previewing Radiant documentation locally",
5
5
  "type": "module",
6
6
  "bin": {
@@ -64,7 +64,7 @@ const darkLogoContainerStyle = `padding-top: ${darkLogo.paddingTop}px; padding-b
64
64
 
65
65
  <a
66
66
  href={logoHref}
67
- class="h-full flex items-center justify-center gap-2 lg:gap-3 text-xl font-bold text-neutral-800 dark:text-neutral-100 overflow-hidden"
67
+ class="h-full flex items-center justify-center gap-2 lg:gap-2.5 text-xl font-bold text-neutral-800 dark:text-neutral-100 overflow-hidden"
68
68
  >
69
69
  {
70
70
  lightLogoUrl || darkLogoUrl ? (
@@ -95,7 +95,7 @@ const darkLogoContainerStyle = `padding-top: ${darkLogo.paddingTop}px; padding-b
95
95
  logoPillText && (
96
96
  <span
97
97
  class:list={[
98
- "text-[10px] text-neutral-500 font-semibold bg-neutral-100 dark:bg-neutral-800 px-2 py-px rounded-full border border-neutral-200 dark:border-neutral-700/70 shadow-xs",
98
+ "text-[10px] text-neutral-500 dark:text-neutral-400 font-normal bg-neutral-100 dark:bg-neutral-800 px-2 py-px rounded-full border-[0.5px] border-neutral-900/8 dark:border-neutral-50/8",
99
99
  ]}
100
100
  >
101
101
  {logoPillText}
@@ -22,7 +22,7 @@ const currentPrefix = parentSlug ? `${parentSlug}/${groupSlug}` : groupSlug;
22
22
  <div class:list={["text-sm font-semibold mb-2 flex items-center gap-2 px-2"]}>
23
23
  {item.icon && <Icon name={item.icon} class="size-4 text-neutral-500" />}
24
24
  {item.group}
25
- {item.tag && <Tag>{item.tag}</Tag>}
25
+ {item.tag && <Tag tag={item.tag} />}
26
26
  </div>
27
27
 
28
28
  <ul>
@@ -3,13 +3,13 @@ import { buildMdxPageHref, deriveTitleFromEntryId } from "../lib/utils";
3
3
  import Icon from "./ui/Icon.astro";
4
4
  import Tag from "./ui/Tag.astro";
5
5
  import { getCollection } from "astro:content";
6
- import { getConfig } from "../lib/validation";
6
+ import { getConfig, type NavTag } from "../lib/validation";
7
7
 
8
8
  interface Props {
9
9
  path: string;
10
10
  groupSlug?: string;
11
11
  icon?: string | null;
12
- tag?: string;
12
+ tag?: NavTag;
13
13
  title?: string;
14
14
  }
15
15
 
@@ -61,6 +61,6 @@ const isActive = currentPath === targetPath;
61
61
  <div class="flex items-center gap-2">
62
62
  {icon && <Icon name={icon} class="size-4 opacity-75" />}
63
63
  {text}
64
- {tag && <Tag>{tag}</Tag>}
64
+ {tag && <Tag tag={tag} />}
65
65
  </div>
66
66
  </a>
@@ -82,7 +82,7 @@ const containsActivePage = item.pages.some((child) => {
82
82
  <div class="flex items-center gap-2">
83
83
  {item.icon && <Icon name={item.icon} class="size-4 opacity-75" />}
84
84
  {item.group}
85
- {item.tag && <Tag>{item.tag}</Tag>}
85
+ {item.tag && <Tag tag={item.tag} />}
86
86
  </div>
87
87
  <svg
88
88
  xmlns="http://www.w3.org/2000/svg"
@@ -325,13 +325,26 @@ const hasMultipleRequests = requestSnippetItems.length > 1;
325
325
  isTransitioning: false,
326
326
  isManagedSlot: false,
327
327
  transitionDirection: 1,
328
- transitionDurationMs: 360,
328
+ transitionDurationMs: 400,
329
329
  transitionEasing: "cubic-bezier(0.22, 1, 0.36, 1)",
330
330
  tabSyncHandler: null,
331
331
  transitionTimeoutId: null,
332
332
  copyTimeoutId: null,
333
+ readMotionTokens() {
334
+ const styles = window.getComputedStyle(document.documentElement);
335
+ const configuredDurationMs = Number.parseFloat(
336
+ styles.getPropertyValue("--rd-panel-transition-duration-ms"),
337
+ );
338
+ this.transitionDurationMs = Number.isFinite(configuredDurationMs)
339
+ ? configuredDurationMs
340
+ : this.transitionDurationMs;
341
+ this.transitionEasing =
342
+ styles.getPropertyValue("--rd-panel-transition-easing").trim() ||
343
+ this.transitionEasing;
344
+ },
333
345
  init() {
334
346
  this.isManagedSlot = !!this.$root?.closest("[data-snippet-slot]");
347
+ this.readMotionTokens();
335
348
  if (
336
349
  typeof window.matchMedia === "function" &&
337
350
  window.matchMedia("(prefers-reduced-motion: reduce)").matches
@@ -348,6 +361,8 @@ const hasMultipleRequests = requestSnippetItems.length > 1;
348
361
  if (this.$refs.snippetPanels) {
349
362
  this.$refs.snippetPanels.style.transitionDuration =
350
363
  this.transitionDurationMs + "ms";
364
+ this.$refs.snippetPanels.style.transitionTimingFunction =
365
+ this.transitionEasing;
351
366
  }
352
367
  this.syncPill();
353
368
  this.syncSnippetHeight();
@@ -492,18 +507,18 @@ const hasMultipleRequests = requestSnippetItems.length > 1;
492
507
  class="flex h-full min-h-0 w-full max-w-full min-w-0 flex-col rounded-xl"
493
508
  >
494
509
  <div
495
- class="flex items-center justify-between gap-2 bg-(--rd-code-header-surface)"
510
+ class="flex items-center justify-between gap-2"
496
511
  >
497
512
  {
498
513
  hasMultipleRequests ? (
499
514
  <div class="min-w-0 flex-1 overflow-hidden rounded-t-xl">
500
515
  <div
501
516
  x-ref="tabList"
502
- class="relative flex min-w-0 items-end gap-1 overflow-x-auto pl-1 pr-8 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
517
+ class="relative flex min-w-0 items-end gap-1 overflow-x-auto overscroll-x-contain pl-1 pr-8 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
503
518
  >
504
519
  <div
505
520
  aria-hidden="true"
506
- class="pointer-events-none absolute top-1/2 z-0 h-[28px] -translate-y-1/2 rounded-[9px] border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface) transition-[left,width,opacity] duration-200 ease-out"
521
+ class="pointer-events-none absolute top-0 z-0 h-[28px] rounded-[9px] border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface) transition-[left,width,opacity] duration-200 ease-out"
507
522
  x-bind:class="pillVisible ? 'opacity-100' : 'opacity-0'"
508
523
  x-bind:style="'left:' + pillLeft + 'px;width:' + pillWidth + 'px;'"
509
524
  />
@@ -516,15 +531,17 @@ const hasMultipleRequests = requestSnippetItems.length > 1;
516
531
  type="button"
517
532
  x-bind:data-rd-snippet-tab="index"
518
533
  x-on:click="select(index)"
519
- class="relative z-10 inline-flex h-9 items-center gap-2 border-0 bg-transparent px-3 py-1.5 text-xs font-medium transition-colors duration-150 focus:outline-none focus-visible:outline-none cursor-pointer"
534
+ class="relative z-10 inline-flex h-9 items-start border-0 bg-transparent px-3 py-0 text-xs font-medium transition-colors duration-150 focus:outline-none focus-visible:outline-none cursor-pointer"
520
535
  x-bind:class="selected === index ? 'text-foreground' : 'text-muted-foreground'"
521
536
  >
522
- <span
523
- x-show="snippet.iconSvg"
524
- x-html="snippet.iconSvg"
525
- class="pointer-events-none inline-flex size-3.5 shrink-0 items-center rounded-[4px] transition-opacity duration-150"
526
- x-bind:class="selected === index ? 'opacity-100' : 'opacity-70'"></span>
527
- <span class="whitespace-pre leading-none" x-text="snippet.label"></span>
537
+ <span class="pointer-events-none inline-flex h-[28px] items-center gap-2">
538
+ <span
539
+ x-show="snippet.iconSvg"
540
+ x-html="snippet.iconSvg"
541
+ class="inline-flex size-3.5 shrink-0 items-center rounded-[4px] transition-opacity duration-150"
542
+ x-bind:class="selected === index ? 'opacity-100' : 'opacity-70'"></span>
543
+ <span class="whitespace-pre leading-none" x-text="snippet.label"></span>
544
+ </span>
528
545
  </button>
529
546
  </template>
530
547
  </div>
@@ -579,7 +596,8 @@ const hasMultipleRequests = requestSnippetItems.length > 1;
579
596
  <div class="relative min-h-0 min-w-0 flex-1 overflow-hidden rounded-b-xl rounded-tl-xl border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface)">
580
597
  <div
581
598
  x-ref="snippetPanels"
582
- class="relative h-full overflow-auto transition-[height] duration-[360ms] ease-[cubic-bezier(0.22,1,0.36,1)] [scrollbar-width:thin] [scrollbar-color:var(--color-neutral-300)_transparent] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-neutral-300/70 hover:[&::-webkit-scrollbar-thumb]:bg-neutral-300/90 dark:[scrollbar-color:var(--color-neutral-700)_transparent] dark:[&::-webkit-scrollbar-thumb]:bg-neutral-700/70 dark:hover:[&::-webkit-scrollbar-thumb]:bg-neutral-700/90"
599
+ class="relative h-full overflow-auto transition-[height] motion-reduce:transition-none [scrollbar-width:thin] [scrollbar-color:var(--color-neutral-300)_transparent] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-neutral-300/70 hover:[&::-webkit-scrollbar-thumb]:bg-neutral-300/90 dark:[scrollbar-color:var(--color-neutral-700)_transparent] dark:[&::-webkit-scrollbar-thumb]:bg-neutral-700/70 dark:hover:[&::-webkit-scrollbar-thumb]:bg-neutral-700/90"
600
+ style="transition-duration: var(--rd-panel-transition-duration); transition-timing-function: var(--rd-panel-transition-easing);"
583
601
  >
584
602
  {
585
603
  requestSnippetItems.map((snippet, index) => (
@@ -74,9 +74,7 @@ import { Icon } from "astro-icon/components";
74
74
  <div
75
75
  class="flex h-full min-h-0 w-full max-w-full min-w-0 flex-col rounded-xl"
76
76
  >
77
- <div
78
- class="flex items-center justify-between gap-2 bg-(--rd-code-header-surface)"
79
- >
77
+ <div class="flex items-center justify-between gap-2">
80
78
  <div class="min-w-0 flex-1 overflow-hidden rounded-t-xl">
81
79
  <div
82
80
  x-ref="tabList"
@@ -84,7 +82,7 @@ import { Icon } from "astro-icon/components";
84
82
  >
85
83
  <div
86
84
  aria-hidden="true"
87
- class="pointer-events-none absolute top-1/2 z-0 h-[28px] -translate-y-1/2 rounded-[9px] border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface) transition-[left,width,opacity] duration-200 ease-out"
85
+ class="pointer-events-none absolute top-0 z-0 h-[28px] rounded-[9px] border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface) transition-[left,width,opacity] duration-200 ease-out"
88
86
  x-bind:class="pillVisible ? 'opacity-100' : 'opacity-0'"
89
87
  x-bind:style="'left:' + pillLeft + 'px;width:' + pillWidth + 'px;'"
90
88
  >
@@ -95,11 +93,12 @@ import { Icon } from "astro-icon/components";
95
93
  type="button"
96
94
  x-bind:data-rd-response-tab="tab.id"
97
95
  x-on:click="select(tab.id)"
98
- class="relative z-10 inline-flex h-9 items-center gap-2 border-0 bg-transparent px-3 py-1.5 text-xs font-medium transition-colors duration-150 focus:outline-none focus-visible:outline-none cursor-pointer"
96
+ class="relative z-10 inline-flex h-9 items-start border-0 bg-transparent px-3 py-0 text-xs font-medium transition-colors duration-150 focus:outline-none focus-visible:outline-none cursor-pointer"
99
97
  x-bind:class="selected === tab.id ? 'text-foreground' : 'text-muted-foreground'"
100
98
  >
101
- <span class="whitespace-pre leading-none" x-text="tab.label"
102
- ></span>
99
+ <span
100
+ class="pointer-events-none inline-flex h-[28px] items-center whitespace-pre leading-none"
101
+ x-text="tab.label"></span>
103
102
  </button>
104
103
  </template>
105
104
  </div>
@@ -147,7 +146,7 @@ import { Icon } from "astro-icon/components";
147
146
  x-show="!response || !response.highlightedData"
148
147
  class="text-sm px-4 py-4 xs:py-8 flex flex-col items-center justify-center h-full text-foreground"
149
148
  >
150
- <div class="bg-(--rd-code-header-surface) p-2 rounded-xl mb-1">
149
+ <div class="p-2 rounded-xl mb-1">
151
150
  <Icon
152
151
  class="size-6 text-neutral-300 dark:text-neutral-600"
153
152
  name="lucide:square-arrow-up-right"
@@ -171,7 +170,7 @@ import { Icon } from "astro-icon/components";
171
170
  x-show="!response || !response.highlightedHeaders"
172
171
  class="text-sm px-4 py-4 xs:py-8 flex flex-col items-center justify-center h-full text-foreground"
173
172
  >
174
- <div class="bg-(--rd-code-header-surface) p-2 rounded-xl mb-1">
173
+ <div class="p-2 rounded-xl mb-1">
175
174
  <Icon
176
175
  class="size-6 text-neutral-300 dark:text-neutral-600"
177
176
  name="lucide:square-arrow-up-right"
@@ -248,13 +248,26 @@ const hasMultipleResponses = responseSnippetItems.length > 1;
248
248
  isTransitioning: false,
249
249
  isManagedSlot: false,
250
250
  transitionDirection: 1,
251
- transitionDurationMs: 360,
251
+ transitionDurationMs: 400,
252
252
  transitionEasing: "cubic-bezier(0.22, 1, 0.36, 1)",
253
253
  tabSyncHandler: null,
254
254
  transitionTimeoutId: null,
255
255
  copyTimeoutId: null,
256
+ readMotionTokens() {
257
+ const styles = window.getComputedStyle(document.documentElement);
258
+ const configuredDurationMs = Number.parseFloat(
259
+ styles.getPropertyValue("--rd-panel-transition-duration-ms"),
260
+ );
261
+ this.transitionDurationMs = Number.isFinite(configuredDurationMs)
262
+ ? configuredDurationMs
263
+ : this.transitionDurationMs;
264
+ this.transitionEasing =
265
+ styles.getPropertyValue("--rd-panel-transition-easing").trim() ||
266
+ this.transitionEasing;
267
+ },
256
268
  init() {
257
269
  this.isManagedSlot = !!this.$root?.closest("[data-snippet-slot]");
270
+ this.readMotionTokens();
258
271
  if (
259
272
  typeof window.matchMedia === "function" &&
260
273
  window.matchMedia("(prefers-reduced-motion: reduce)").matches
@@ -271,6 +284,8 @@ const hasMultipleResponses = responseSnippetItems.length > 1;
271
284
  if (this.$refs.snippetPanels) {
272
285
  this.$refs.snippetPanels.style.transitionDuration =
273
286
  this.transitionDurationMs + "ms";
287
+ this.$refs.snippetPanels.style.transitionTimingFunction =
288
+ this.transitionEasing;
274
289
  }
275
290
  this.syncPill();
276
291
  this.syncSnippetHeight();
@@ -415,7 +430,7 @@ const hasMultipleResponses = responseSnippetItems.length > 1;
415
430
  class="flex h-full min-h-0 w-full max-w-full min-w-0 flex-col rounded-xl"
416
431
  >
417
432
  <div
418
- class="flex items-center justify-between gap-2 bg-(--rd-code-header-surface)"
433
+ class="flex items-center justify-between gap-2"
419
434
  >
420
435
  {
421
436
  hasMultipleResponses ? (
@@ -426,7 +441,7 @@ const hasMultipleResponses = responseSnippetItems.length > 1;
426
441
  >
427
442
  <div
428
443
  aria-hidden="true"
429
- class="pointer-events-none absolute top-1/2 z-0 h-[28px] -translate-y-1/2 rounded-[9px] border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface) transition-[left,width,opacity] duration-200 ease-out"
444
+ class="pointer-events-none absolute top-0 z-0 h-[28px] rounded-[9px] border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface) transition-[left,width,opacity] duration-200 ease-out"
430
445
  x-bind:class="pillVisible ? 'opacity-100' : 'opacity-0'"
431
446
  x-bind:style="'left:' + pillLeft + 'px;width:' + pillWidth + 'px;'"
432
447
  />
@@ -439,13 +454,15 @@ const hasMultipleResponses = responseSnippetItems.length > 1;
439
454
  type="button"
440
455
  x-bind:data-rd-snippet-tab="index"
441
456
  x-on:click="select(index)"
442
- class="relative z-10 inline-flex h-9 items-center gap-2 border-0 bg-transparent px-3 py-1.5 text-xs font-medium transition-colors duration-150 focus:outline-none focus-visible:outline-none cursor-pointer"
457
+ class="relative z-10 inline-flex h-9 items-start border-0 bg-transparent px-3 py-0 text-xs font-medium transition-colors duration-150 focus:outline-none focus-visible:outline-none cursor-pointer"
443
458
  x-bind:class="selected === index ? 'text-foreground' : 'text-muted-foreground'"
444
459
  >
445
- <span
446
- class="size-[7px] shrink-0 rounded-full transition-opacity duration-150"
447
- x-bind:class="(selected === index ? 'opacity-100 ' : 'opacity-70 ') + snippet.dotClass"></span>
448
- <span class="whitespace-pre leading-none" x-text="snippet.statusCode"></span>
460
+ <span class="pointer-events-none inline-flex h-[28px] items-center gap-2">
461
+ <span
462
+ class="size-[7px] shrink-0 rounded-full transition-opacity duration-150"
463
+ x-bind:class="(selected === index ? 'opacity-100 ' : 'opacity-70 ') + snippet.dotClass"></span>
464
+ <span class="whitespace-pre leading-none" x-text="snippet.statusCode"></span>
465
+ </span>
449
466
  </button>
450
467
  </template>
451
468
  </div>
@@ -499,7 +516,8 @@ const hasMultipleResponses = responseSnippetItems.length > 1;
499
516
  <div class="relative min-h-0 min-w-0 flex-1 overflow-hidden rounded-b-xl rounded-tl-xl border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface)">
500
517
  <div
501
518
  x-ref="snippetPanels"
502
- class="relative h-full overflow-auto transition-[height] duration-[360ms] ease-[cubic-bezier(0.22,1,0.36,1)] [scrollbar-width:thin] [scrollbar-color:var(--color-neutral-300)_transparent] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-neutral-300/70 hover:[&::-webkit-scrollbar-thumb]:bg-neutral-300/90 dark:[scrollbar-color:var(--color-neutral-700)_transparent] dark:[&::-webkit-scrollbar-thumb]:bg-neutral-700/70 dark:hover:[&::-webkit-scrollbar-thumb]:bg-neutral-700/90"
519
+ class="relative h-full overflow-auto transition-[height] motion-reduce:transition-none [scrollbar-width:thin] [scrollbar-color:var(--color-neutral-300)_transparent] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-neutral-300/70 hover:[&::-webkit-scrollbar-thumb]:bg-neutral-300/90 dark:[scrollbar-color:var(--color-neutral-700)_transparent] dark:[&::-webkit-scrollbar-thumb]:bg-neutral-700/70 dark:hover:[&::-webkit-scrollbar-thumb]:bg-neutral-700/90"
520
+ style="transition-duration: var(--rd-panel-transition-duration); transition-timing-function: var(--rd-panel-transition-easing);"
503
521
  >
504
522
  {
505
523
  responseSnippetItems.map((snippet, index) => (
@@ -2,13 +2,14 @@
2
2
  import Tag from "../ui/Tag.astro";
3
3
  import { buildOpenApiEndpointHref } from "../../lib/utils";
4
4
  import { methodColors } from "../../lib/utils";
5
+ import type { NavTag } from "../../lib/validation";
5
6
 
6
7
  interface Props {
7
8
  method: string;
8
9
  path: string;
9
10
  summary?: string;
10
11
  title?: string;
11
- tag?: string;
12
+ tag?: NavTag;
12
13
  parentSlug?: string;
13
14
  }
14
15
 
@@ -49,6 +50,6 @@ const isActive = currentPath === targetPath;
49
50
  </span>
50
51
  <div class="flex items-center gap-2 min-w-0">
51
52
  <span>{text}</span>
52
- {tag && <Tag>{tag}</Tag>}
53
+ {tag && <Tag tag={tag} />}
53
54
  </div>
54
55
  </a>
@@ -1,5 +1,67 @@
1
+ ---
2
+ import {
3
+ getConfig,
4
+ type NavTag,
5
+ type ThemeColorByMode,
6
+ } from "../../lib/validation";
7
+
8
+ interface Props {
9
+ tag?: NavTag;
10
+ color?: string | ThemeColorByMode;
11
+ }
12
+
13
+ const { tag, color } = Astro.props;
14
+ const config = await getConfig();
15
+
16
+ function getTagText(value: NavTag | undefined): string | undefined {
17
+ if (typeof value === "string") return value;
18
+ return value?.text;
19
+ }
20
+
21
+ function getTagColor(
22
+ value: NavTag | undefined,
23
+ ): string | ThemeColorByMode | undefined {
24
+ if (typeof value === "object" && value !== null) {
25
+ return value.color;
26
+ }
27
+ return undefined;
28
+ }
29
+
30
+ function resolveColorByMode(
31
+ value: string | ThemeColorByMode | undefined,
32
+ ): { light: string; dark: string } | undefined {
33
+ if (typeof value === "string") {
34
+ return { light: value, dark: value };
35
+ }
36
+
37
+ const light = value?.light ?? value?.dark;
38
+ const dark = value?.dark ?? value?.light;
39
+ if (!light || !dark) return undefined;
40
+
41
+ return { light, dark };
42
+ }
43
+
44
+ const text = getTagText(tag);
45
+ const configuredColor =
46
+ color ?? getTagColor(tag) ?? config.theme?.tag?.color ?? undefined;
47
+ const resolvedColor = resolveColorByMode(configuredColor);
48
+ const colorStyle = resolvedColor
49
+ ? `--rd-tag-color-light:${resolvedColor.light};--rd-tag-color-dark:${resolvedColor.dark};`
50
+ : undefined;
51
+ ---
52
+
1
53
  <span
2
- class="text-[9px] bg-blue-100/70 text-blue-800/90 border border-blue-800/10 px-1.5 py-px rounded-full tracking-wide font-semibold shrink-0 normal-case"
54
+ class="rd-tag text-[9px] border px-[5px] py-[2px] rounded-full tracking-wide leading-none font-medium shrink-0"
55
+ style={colorStyle}
3
56
  >
4
- <slot />
57
+ {text ? text : <slot />}
5
58
  </span>
59
+
60
+ <style>
61
+ .rd-tag {
62
+ --rd-tag-color: var(--rd-tag-color-light, var(--color-theme));
63
+ background-color: color-mix(in oklab, var(--rd-tag-color) 8%, transparent);
64
+ border-color: color-mix(in oklab, var(--rd-tag-color) 0%, transparent);
65
+ color: color-mix(in oklab, var(--rd-tag-color) 95%, transparent);
66
+ }
67
+ </style>
@@ -50,7 +50,7 @@ validateProps(
50
50
  }`
51
51
  x-init="id = (typeof register === 'function') ? register() : Math.random()"
52
52
  role="region"
53
- class="rd-accordion block border-b border-neutral-800/10 dark:border-neutral-700/50 last:border-b-0. [&:first-child>h4>button]:pt-0 [&:last-child>div>div]:pb-0"
53
+ class="rd-accordion block border-b border-neutral-800/10 dark:border-neutral-700/50 last:border-b-0. [&:first-child>h4>button]:pt-0"
54
54
  >
55
55
  <h4 class="not-prose">
56
56
  <button
@@ -70,9 +70,23 @@ validateProps(
70
70
  />
71
71
  </button>
72
72
  </h4>
73
- <div x-show="expanded" x-collapse>
74
- <div class="prose-rules max-w-none! *:max-w-none! pb-4!">
75
- <slot />
73
+ <div
74
+ class:list={[
75
+ "grid overflow-hidden transition-[grid-template-rows,opacity] motion-reduce:transition-none",
76
+ defaultOpen ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0",
77
+ ]}
78
+ :class="expanded ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'"
79
+ :aria-hidden="(!expanded).toString()"
80
+ x-bind:inert="!expanded"
81
+ style="transition-duration: var(--rd-panel-transition-duration); transition-timing-function: var(--rd-panel-transition-easing);"
82
+ >
83
+ <div class="min-h-0 overflow-hidden">
84
+ <div
85
+ data-rd-accordion-content-inner
86
+ class="prose-rules max-w-none! *:max-w-none! pb-4!"
87
+ >
88
+ <slot />
89
+ </div>
76
90
  </div>
77
91
  </div>
78
92
  </div>
@@ -16,6 +16,7 @@ interface Props {
16
16
  showLineNumbers?: boolean | string;
17
17
  hideLanguageIcon?: boolean | string;
18
18
  inCodeGroup?: boolean | string;
19
+ flushTop?: boolean | string;
19
20
  highlightedLines?: string;
20
21
  collapsedLines?: string;
21
22
  }
@@ -28,6 +29,7 @@ const {
28
29
  showLineNumbers = false,
29
30
  hideLanguageIcon = false,
30
31
  inCodeGroup = false,
32
+ flushTop = false,
31
33
  highlightedLines = "",
32
34
  collapsedLines = "",
33
35
  } = Astro.props as Props;
@@ -191,6 +193,7 @@ const parsedShowFilename = parsedInCodeGroup
191
193
  : toBoolean(showFilename, false);
192
194
  const parsedShowLineNumbers = toBoolean(showLineNumbers, false);
193
195
  const parsedHideLanguageIcon = toBoolean(hideLanguageIcon, false);
196
+ const parsedFlushTop = !parsedInCodeGroup && toBoolean(flushTop, false);
194
197
  const shouldRenderLanguageIcon = !parsedHideLanguageIcon;
195
198
 
196
199
  const trimmedFilename = filename.trim();
@@ -262,7 +265,8 @@ const renderedCodeLinesHtml = normalizedTokenLines
262
265
 
263
266
  <div
264
267
  class:list={[
265
- "group/prose-code not-prose relative w-full max-w-full min-w-0 rounded-xl",
268
+ "group/prose-code not-prose relative w-full max-w-full min-w-0",
269
+ parsedFlushTop ? "rounded-b-xl" : "rounded-xl",
266
270
  parsedInCodeGroup ? "my-0" : "rd-prose-block",
267
271
  ]}
268
272
  data-rd-code-block-root="true"
@@ -278,13 +282,17 @@ const renderedCodeLinesHtml = normalizedTokenLines
278
282
  >
279
283
  <div
280
284
  class:list={[
281
- "w-full max-w-full min-w-0 bg-(--rd-code-surface)",
282
- parsedInCodeGroup ? "rounded-tr-none rounded-xl" : "rounded-xl",
285
+ "w-full max-w-full min-w-0",
286
+ parsedInCodeGroup
287
+ ? "rounded-tr-none rounded-xl"
288
+ : parsedFlushTop
289
+ ? "rounded-b-xl"
290
+ : "rounded-xl",
283
291
  ]}
284
292
  >
285
293
  {
286
294
  !parsedInCodeGroup && parsedShowFilename ? (
287
- <div class="flex items-center justify-between gap-2 border-b-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-header-surface)">
295
+ <div class="flex items-center justify-between gap-2 border-b-[0.5px] border-(--rd-code-tab-edge-border)">
288
296
  <div class="min-w-0 flex-1">
289
297
  <div class="relative h-9 w-fit max-w-full rounded-tl-xl bg-(--rd-code-surface)">
290
298
  <div class="absolute inset-x-0 -bottom-px h-px bg-(--rd-code-surface)" />
@@ -331,7 +339,9 @@ const renderedCodeLinesHtml = normalizedTokenLines
331
339
 
332
340
  <div
333
341
  class:list={[
334
- "relative min-w-0 border-[0.5px] rounded-xl overflow-hidden border-(--rd-code-tab-edge-border)",
342
+ "relative min-w-0 border-[0.5px] overflow-hidden border-(--rd-code-tab-edge-border)",
343
+ parsedFlushTop ? "rounded-b-xl" : "rounded-xl",
344
+ parsedFlushTop && "border-t-0",
335
345
  parsedShowFilename && "border-t-0 rounded-t-none",
336
346
  parsedInCodeGroup && "rounded-tl-xl border-0!",
337
347
  ]}
@@ -7,19 +7,17 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
7
7
  class="rd-prose-block group/prose-code-group not-prose relative w-full max-w-full min-w-0 rounded-xl"
8
8
  data-rd-code-group-root="true"
9
9
  >
10
- <div
11
- class="relative z-10 overflow-visible rounded-t-xl bg-(--rd-code-header-surface)"
12
- >
10
+ <div class="relative z-10 overflow-visible rounded-t-xl">
13
11
  <div class="flex min-w-0 items-end justify-between gap-2">
14
12
  <div class="min-w-0 flex-1 overflow-hidden rounded-t-xl">
15
13
  <div
16
14
  data-rd-code-group-tabs
17
- class="relative flex min-w-0 items-end gap-1 overflow-x-auto pl-1 pr-8 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
15
+ class="relative flex min-w-0 items-end gap-1 overflow-x-auto overscroll-x-contain pl-1 pr-8 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
18
16
  >
19
17
  <div
20
18
  data-rd-code-group-pill
21
19
  aria-hidden="true"
22
- class="pointer-events-none absolute top-1/2 z-0 h-[28px] -translate-y-1/2 rounded-[9px] border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface) opacity-0 transition-[left,width,opacity] duration-200 ease-out"
20
+ class="pointer-events-none absolute top-0 z-0 h-[28px] rounded-[9px] border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface) opacity-0 transition-[left,width,opacity] duration-200 ease-out"
23
21
  >
24
22
  </div>
25
23
  </div>
@@ -58,7 +56,8 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
58
56
 
59
57
  <div
60
58
  data-rd-code-group-content
61
- class="relative min-w-0 overflow-hidden transition-[height] duration-[360ms] ease-[cubic-bezier(0.22,1,0.36,1)] bg-(--rd-code-surface) rounded-xl rounded-tr-none border-[0.5px] border-(--rd-code-tab-edge-border)"
59
+ class="relative min-w-0 overflow-hidden transition-[height] bg-(--rd-code-surface) rounded-xl rounded-tr-none border-[0.5px] border-(--rd-code-tab-edge-border)"
60
+ style="transition-duration: var(--rd-panel-transition-duration); transition-timing-function: var(--rd-panel-transition-easing);"
62
61
  >
63
62
  <slot />
64
63
  </div>
@@ -91,8 +90,20 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
91
90
  const prefersReducedMotion =
92
91
  typeof window.matchMedia === "function" &&
93
92
  window.matchMedia("(prefers-reduced-motion: reduce)").matches;
94
- const TRANSITION_DURATION_MS = prefersReducedMotion ? 0 : 360;
95
- const TRANSITION_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
93
+ const rootStyles = window.getComputedStyle(document.documentElement);
94
+ const configuredDurationMs = Number.parseFloat(
95
+ rootStyles.getPropertyValue("--rd-panel-transition-duration-ms"),
96
+ );
97
+ const configuredEasing = rootStyles
98
+ .getPropertyValue("--rd-panel-transition-easing")
99
+ .trim();
100
+ const TRANSITION_DURATION_MS = prefersReducedMotion
101
+ ? 0
102
+ : Number.isFinite(configuredDurationMs)
103
+ ? configuredDurationMs
104
+ : 400;
105
+ const TRANSITION_EASING =
106
+ configuredEasing || "cubic-bezier(0.22, 1, 0.36, 1)";
96
107
 
97
108
  let activeIndex = 0;
98
109
  let transitionTimeoutId = null;
@@ -138,10 +149,14 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
138
149
 
139
150
  const tabButton = document.createElement("button");
140
151
  tabButton.type = "button";
141
- tabButton.className = `relative inline-flex h-9 items-center border-0 bg-transparent px-3 py-1.5 text-xs font-medium transition-colors duration-150 focus:outline-none focus-visible:outline-none cursor-pointer ${hasTabIcon ? "gap-2" : ""}`;
152
+ tabButton.className =
153
+ "relative inline-flex h-9 items-start border-0 bg-transparent px-3 py-0 text-xs font-medium transition-colors duration-150 focus:outline-none focus-visible:outline-none cursor-pointer";
142
154
  tabButton.setAttribute("aria-label", filename);
143
155
  tabButton.setAttribute("data-rd-code-group-tab", String(index));
144
156
 
157
+ const tabContent = document.createElement("span");
158
+ tabContent.className = `pointer-events-none inline-flex h-[28px] items-center ${hasTabIcon ? "gap-2" : ""}`;
159
+
145
160
  let iconContainer = null;
146
161
  if (hasTabIcon) {
147
162
  iconContainer = document.createElement("span");
@@ -155,9 +170,10 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
155
170
  labelElement.textContent = filename;
156
171
 
157
172
  if (iconContainer) {
158
- tabButton.appendChild(iconContainer);
173
+ tabContent.appendChild(iconContainer);
159
174
  }
160
- tabButton.appendChild(labelElement);
175
+ tabContent.appendChild(labelElement);
176
+ tabButton.appendChild(tabContent);
161
177
  tabWrapper.appendChild(tabButton);
162
178
  tabsElement.appendChild(tabWrapper);
163
179
 
@@ -182,6 +198,7 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
182
198
  });
183
199
 
184
200
  contentElement.style.transitionDuration = `${TRANSITION_DURATION_MS}ms`;
201
+ contentElement.style.transitionTimingFunction = TRANSITION_EASING;
185
202
 
186
203
  const syncPill = () => {
187
204
  const activeTab = tabs[activeIndex]?.tabWrapper;