@eventcatalog/core 2.57.1 → 2.58.0

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.
Files changed (31) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-6PT76NQN.js → chunk-GKUPYLJL.js} +1 -1
  6. package/dist/{chunk-76HGJTBT.js → chunk-PAKFTVIV.js} +1 -1
  7. package/dist/{chunk-7BNIEBLU.js → chunk-TIETK4WY.js} +1 -1
  8. package/dist/constants.cjs +1 -1
  9. package/dist/constants.js +1 -1
  10. package/dist/eventcatalog.cjs +1 -1
  11. package/dist/eventcatalog.js +3 -3
  12. package/eventcatalog/astro.config.mjs +6 -1
  13. package/eventcatalog/public/icons/graphql.svg +1 -0
  14. package/eventcatalog/src/components/Lists/PillListFlat.tsx +108 -27
  15. package/eventcatalog/src/components/Lists/SpecificationsList.astro +15 -0
  16. package/eventcatalog/src/components/MDX/Attachments.astro +158 -0
  17. package/eventcatalog/src/components/MDX/Tiles/Tile.astro +31 -15
  18. package/eventcatalog/src/components/MDX/Tiles/Tiles.astro +3 -3
  19. package/eventcatalog/src/components/MDX/components.tsx +2 -0
  20. package/eventcatalog/src/components/SideBars/ChannelSideBar.astro +25 -0
  21. package/eventcatalog/src/components/SideBars/DomainSideBar.astro +25 -0
  22. package/eventcatalog/src/components/SideBars/EntitySideBar.astro +24 -0
  23. package/eventcatalog/src/components/SideBars/FlowSideBar.astro +25 -0
  24. package/eventcatalog/src/components/SideBars/MessageSideBar.astro +25 -0
  25. package/eventcatalog/src/components/SideBars/ServiceSideBar.astro +25 -0
  26. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/SpecificationList.tsx +20 -0
  27. package/eventcatalog/src/content.config.ts +20 -1
  28. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +177 -0
  29. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/_[filename].data.ts +98 -0
  30. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +43 -6
  31. package/package.json +2 -1
@@ -37,7 +37,7 @@ var import_axios = __toESM(require("axios"), 1);
37
37
  var import_os = __toESM(require("os"), 1);
38
38
 
39
39
  // package.json
40
- var version = "2.57.1";
40
+ var version = "2.58.0";
41
41
 
42
42
  // src/constants.ts
43
43
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-6PT76NQN.js";
4
- import "../chunk-76HGJTBT.js";
3
+ } from "../chunk-GKUPYLJL.js";
4
+ import "../chunk-PAKFTVIV.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -106,7 +106,7 @@ var import_axios = __toESM(require("axios"), 1);
106
106
  var import_os = __toESM(require("os"), 1);
107
107
 
108
108
  // package.json
109
- var version = "2.57.1";
109
+ var version = "2.58.0";
110
110
 
111
111
  // src/constants.ts
112
112
  var VERSION = version;
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-7BNIEBLU.js";
4
- import "../chunk-6PT76NQN.js";
5
- import "../chunk-76HGJTBT.js";
3
+ } from "../chunk-TIETK4WY.js";
4
+ import "../chunk-GKUPYLJL.js";
5
+ import "../chunk-PAKFTVIV.js";
6
6
  import "../chunk-UPONRQSN.js";
7
7
  export {
8
8
  log_build_default as default
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-76HGJTBT.js";
3
+ } from "./chunk-PAKFTVIV.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import axios from "axios";
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "2.57.1";
2
+ var version = "2.58.0";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-6PT76NQN.js";
3
+ } from "./chunk-GKUPYLJL.js";
4
4
  import {
5
5
  getEventCatalogConfigFile,
6
6
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "2.57.1";
28
+ var version = "2.58.0";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-76HGJTBT.js";
3
+ } from "./chunk-PAKFTVIV.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -157,7 +157,7 @@ var import_axios = __toESM(require("axios"), 1);
157
157
  var import_os = __toESM(require("os"), 1);
158
158
 
159
159
  // package.json
160
- var version = "2.57.1";
160
+ var version = "2.58.0";
161
161
 
162
162
  // src/constants.ts
163
163
  var VERSION = version;
@@ -6,8 +6,8 @@ import {
6
6
  } from "./chunk-PLNJC7NZ.js";
7
7
  import {
8
8
  log_build_default
9
- } from "./chunk-7BNIEBLU.js";
10
- import "./chunk-6PT76NQN.js";
9
+ } from "./chunk-TIETK4WY.js";
10
+ import "./chunk-GKUPYLJL.js";
11
11
  import {
12
12
  catalogToAstro,
13
13
  checkAndConvertMdToMdx
@@ -15,7 +15,7 @@ import {
15
15
  import "./chunk-55D645EH.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-76HGJTBT.js";
18
+ } from "./chunk-PAKFTVIV.js";
19
19
  import {
20
20
  getProjectOutDir,
21
21
  isAuthEnabled,
@@ -2,6 +2,7 @@ import { defineConfig } from 'astro/config';
2
2
  import tailwind from '@astrojs/tailwind';
3
3
  import mdx from '@astrojs/mdx';
4
4
  import react from '@astrojs/react';
5
+ import { searchForWorkspaceRoot } from 'vite';
5
6
  import { mermaid } from "./src/remark-plugins/mermaid"
6
7
  import { plantuml } from "./src/remark-plugins/plantuml"
7
8
  import { join } from 'node:path';
@@ -94,7 +95,11 @@ export default defineConfig({
94
95
  },
95
96
  server: {
96
97
  fs: {
97
- allow: ['..', './node_modules/@fontsource']
98
+ allow: [
99
+ '..',
100
+ './node_modules/@fontsource',
101
+ searchForWorkspaceRoot(process.cwd()),
102
+ ]
98
103
  }
99
104
  },
100
105
  worker: {
@@ -0,0 +1 @@
1
+ <svg viewBox="-16 0 288 288" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g fill-rule="evenodd"> <path d="M152.575995,32.9634453 L211.722058,67.1124427 C213.387503,65.348329 215.332859,63.7907081 217.539734,62.5088708 C229.806457,55.4573413 245.452038,59.6441869 252.577021,71.9109101 C259.62855,84.1776333 255.441705,99.8232143 243.174982,106.948197 C240.984974,108.207124 238.684929,109.10784 236.343812,109.66832 L236.343812,177.99302 C238.659582,178.554683 240.934429,179.449686 243.101528,180.695443 C255.441705,187.820426 259.62855,203.466007 252.503568,215.73273 C245.452038,227.999453 229.733004,232.186299 217.46628,225.13477 C215.024105,223.730884 212.902189,221.983449 211.125456,219.988103 L152.340162,253.928803 C153.180519,256.46808 153.635248,259.18431 153.635248,262.008393 C153.635248,276.111452 142.176512,287.64364 128,287.64364 C113.823488,287.64364 102.364752,276.184905 102.364752,262.008393 C102.364752,259.488481 102.726802,257.054441 103.40181,254.755362 L44.2714887,220.615454 C42.6238257,222.34493 40.705394,223.873378 38.5337196,225.13477 C26.193543,232.186299 10.547962,227.999453 3.49643248,215.73273 C-3.55509701,203.466007 0.631748621,187.820426 12.8984718,180.695443 C15.0673397,179.448669 17.3418356,178.553222 19.6561876,177.991646 L19.6561876,109.66832 C17.3150714,109.10784 15.0150257,108.207124 12.8250184,106.948197 C0.558295189,99.8966677 -3.62855044,84.1776333 3.42297904,71.9109101 C10.4745085,59.6441869 26.193543,55.4573413 38.4602662,62.5088708 C40.6551374,63.7837361 42.5913269,65.3313931 44.2507413,67.0836756 L103.41276,32.9254664 C102.730718,30.6154532 102.364752,28.1687503 102.364752,25.6352478 C102.364752,11.4587354 113.823488,0 128,0 C142.176512,0 153.635248,11.4587354 153.635248,25.6352478 C153.635248,28.1826393 153.265258,30.6422768 152.575995,32.9634453 Z M146.413638,43.4848713 L205.700555,77.715193 C203.867899,84.1516888 204.540515,91.2885638 208.137694,97.5461579 C211.746601,103.824151 217.625783,107.985785 224.150543,109.607654 L224.150543,178.017842 C223.818032,178.099463 223.48718,178.187674 223.158201,178.282419 L145.72529,44.1686182 C145.959017,43.9450469 146.18851,43.7170876 146.413638,43.4848713 Z M110.29093,44.1841203 L32.8593279,178.295656 C32.524559,178.19844 32.1878611,178.108015 31.8494573,178.024438 L31.8494573,109.607654 C38.3742168,107.985785 44.2533992,103.824151 47.8623055,97.5461579 C51.466599,91.276189 52.1347497,84.1234583 50.2885426,77.6770157 L109.558747,43.4563434 C109.797913,43.7038019 110.042027,43.9464469 110.29093,44.1841203 Z M135.162749,50.259763 L212.576817,184.340928 C210.844241,185.99279 209.317376,187.91755 208.064241,190.097482 C206.818484,192.264581 205.923481,194.539429 205.361818,196.855198 L50.6395564,196.855198 C50.0779798,194.540846 49.1825333,192.26635 47.9357589,190.097482 C46.6776713,187.931497 45.1539248,186.017428 43.4300297,184.372667 L120.858821,50.2659993 C123.124941,50.9200822 125.520915,51.2704957 128,51.2704957 C130.486952,51.2704957 132.890265,50.9178547 135.162749,50.259763 Z M146.958084,244.737995 L205.860107,210.729899 C205.683398,210.174924 205.525483,209.614096 205.38664,209.048468 L50.6067643,209.048468 C50.5246888,209.380792 50.4360084,209.711472 50.3407792,210.040295 L109.531782,244.215239 C114.192298,239.378545 120.739739,236.373145 128,236.373145 C135.518379,236.373145 142.272352,239.596041 146.958084,244.737995 Z" fill="#E535AB"> </path> </g> </g></svg>
@@ -1,7 +1,8 @@
1
1
  import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react';
2
2
  import { ChevronDownIcon } from '@heroicons/react/20/solid';
3
3
  import { getIconForCollection as getIconForCollectionOriginal } from '@utils/collections/icons';
4
- import { useMemo } from 'react';
4
+ import { useMemo, useState } from 'react';
5
+ import * as icons from 'lucide-react'; // Import all icons
5
6
 
6
7
  import './PillListFlat.styles.css';
7
8
 
@@ -14,17 +15,80 @@ interface Props {
14
15
  label: string;
15
16
  badge?: string;
16
17
  href?: string;
18
+ target?: '_blank' | '_self';
17
19
  tag?: string;
18
20
  color?: string;
19
21
  collection?: string;
20
22
  description?: string;
21
23
  icon?: string;
24
+ subgroup?: string | undefined;
22
25
  }[];
23
26
  emptyMessage?: string;
24
27
  }
25
28
 
26
29
  const PillList = ({ title, pills, emptyMessage, color = 'gray', limit = 10, ...props }: Props) => {
27
30
  const getIconForCollection = useMemo(() => getIconForCollectionOriginal, []);
31
+ const [collapsedSubgroups, setCollapsedSubgroups] = useState<Set<string>>(new Set());
32
+
33
+ const groupedPills = useMemo(() => {
34
+ const grouped = new Map<string, typeof pills>();
35
+ const ungrouped: typeof pills = [];
36
+
37
+ pills.forEach((pill) => {
38
+ if (pill.subgroup) {
39
+ if (!grouped.has(pill.subgroup)) {
40
+ grouped.set(pill.subgroup, []);
41
+ }
42
+ grouped.get(pill.subgroup)!.push(pill);
43
+ } else {
44
+ ungrouped.push(pill);
45
+ }
46
+ });
47
+
48
+ return { grouped, ungrouped };
49
+ }, [pills]);
50
+
51
+ const toggleSubgroup = (subgroupName: string) => {
52
+ setCollapsedSubgroups((prev) => {
53
+ const newSet = new Set(prev);
54
+ if (newSet.has(subgroupName)) {
55
+ newSet.delete(subgroupName);
56
+ } else {
57
+ newSet.add(subgroupName);
58
+ }
59
+ return newSet;
60
+ });
61
+ };
62
+
63
+ const renderPillItem = (item: (typeof pills)[0]) => {
64
+ const href = item.href ?? '#';
65
+ const Icon = item.collection ? getIconForCollection(item.collection) : null;
66
+ const PillIcon = item.icon ? (icons as any)[item.icon] : null;
67
+
68
+ return (
69
+ <li
70
+ className=" has-tooltip rounded-md text-gray-600 group px-1 w-full hover:bg-gradient-to-l hover:from-purple-500 hover:to-purple-700 hover:text-white hover:font-normal "
71
+ key={item.href}
72
+ >
73
+ <a className={`leading-3`} href={href} target={item.target ?? '_self'}>
74
+ <span className="space-x-2 flex items-center">
75
+ {Icon && !PillIcon && <Icon className="h-4 w-4 shrink-0" />}
76
+ {PillIcon && <PillIcon className="h-4 w-4 shrink-0" />}
77
+ <span className="font-light text-sm truncate">
78
+ {item.label} {item.tag && <>({item.tag})</>}
79
+ </span>
80
+ {item.label.length > 24 && (
81
+ <span className="tooltip rounded relative shadow-lg p-1 font-normal text-xs bg-white text-black ml-[30px] mt-12">
82
+ {item.label} {item.tag && <>({item.tag})</>}
83
+ </span>
84
+ )}
85
+ </span>
86
+ {item.description && <span className="text-[9px] block ml-6 mt-1 leading-0">{item.description}</span>}
87
+ </a>
88
+ </li>
89
+ );
90
+ };
91
+
28
92
  return (
29
93
  <div className="">
30
94
  <div className="mx-auto w-full max-w-lg divide-y divide-white/5 rounded-xl bg-white/5">
@@ -34,39 +98,56 @@ const PillList = ({ title, pills, emptyMessage, color = 'gray', limit = 10, ...p
34
98
  <ChevronDownIcon className="size-5 ml-2 fill-black/60 group-data-[hover]:fill-black/50 group-data-[open]:rotate-180" />
35
99
  </DisclosureButton>
36
100
  <DisclosurePanel className="mt-2 text-sm/5 text-black/50">
37
- <ul role="list" className="space-y-2">
38
- {pills.map((item) => {
39
- const href = item.href ?? '#';
40
- const Icon = item.collection ? getIconForCollection(item.collection) : null;
101
+ <div className="space-y-2">
102
+ {groupedPills.ungrouped.length > 0 && (
103
+ <ul role="list" className="space-y-2">
104
+ {groupedPills.ungrouped.map(renderPillItem)}
105
+ </ul>
106
+ )}
41
107
 
108
+ {Array.from(groupedPills.grouped.entries()).map(([subgroupName, subgroupPills]) => {
109
+ const isCollapsed = collapsedSubgroups.has(subgroupName);
42
110
  return (
43
- <li
44
- className=" has-tooltip rounded-md text-gray-600 group px-1 w-full hover:bg-gradient-to-l hover:from-purple-500 hover:to-purple-700 hover:text-white hover:font-normal "
45
- key={item.href}
46
- >
47
- <a className={`leading-3`} href={href}>
48
- <span className="space-x-2 flex items-center">
49
- {Icon && <Icon className="h-4 w-4 shrink-0" />}
50
- <span className="font-light text-sm truncate">
51
- {item.label} {item.tag && <>({item.tag})</>}
52
- </span>
53
- {item.label.length > 24 && (
54
- <span className="tooltip rounded relative shadow-lg p-1 font-normal text-xs bg-white text-black ml-[30px] mt-12">
55
- {item.label} ({item.tag})
56
- </span>
57
- )}
58
- </span>
59
- {item.description && <span className="text-[9px] block ml-6 mt-1 leading-0">{item.description}</span>}
60
- </a>
61
- </li>
111
+ <div key={subgroupName} className="space-y-">
112
+ <div className="flex items-center">
113
+ <button
114
+ onClick={(e) => {
115
+ e.stopPropagation();
116
+ toggleSubgroup(subgroupName);
117
+ }}
118
+ className="p-1 hover:bg-gray-100 rounded-md"
119
+ >
120
+ <div className={`transition-transform duration-150 ${isCollapsed ? '' : 'rotate-180'}`}>
121
+ <ChevronDownIcon className="h-3 w-3 text-gray-500" />
122
+ </div>
123
+ </button>
124
+ <button
125
+ onClick={(e) => {
126
+ e.stopPropagation();
127
+ toggleSubgroup(subgroupName);
128
+ }}
129
+ className="flex-grow flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md text-gray-900 uppercase"
130
+ >
131
+ {subgroupName} ({subgroupPills.length})
132
+ </button>
133
+ </div>
134
+ <div
135
+ className={`overflow-hidden transition-[height] duration-150 ease-out ${isCollapsed ? 'h-0' : 'h-auto'}`}
136
+ >
137
+ <ul role="list" className="space-y-2 border-l border-gray-200/80 ml-[9px] pl-4 pt-2">
138
+ {subgroupPills.map(renderPillItem)}
139
+ </ul>
140
+ </div>
141
+ </div>
62
142
  );
63
143
  })}
144
+
64
145
  {pills.length === 0 && emptyMessage && (
65
- <li className="inline mr-2 leading-tight text-xs">
146
+ <div className="inline mr-2 leading-tight text-xs">
66
147
  <span className="text-gray-400">{emptyMessage}</span>
67
- </li>
148
+ </div>
68
149
  )}
69
- </ul>
150
+ </div>
70
151
  </DisclosurePanel>
71
152
  </Disclosure>
72
153
  </div>
@@ -17,6 +17,7 @@ const specs = getSpecificationsForService(collectionItem as Service);
17
17
 
18
18
  const openAPISpecifications = specs.filter((spec) => spec.type === 'openapi');
19
19
  const asyncAPISpecifications = specs.filter((spec) => spec.type === 'asyncapi');
20
+ const graphQLSpecifications = specs.filter((spec) => spec.type === 'graphql');
20
21
  ---
21
22
 
22
23
  <div class="space-y-2 mb-8">
@@ -49,4 +50,18 @@ const asyncAPISpecifications = specs.filter((spec) => spec.type === 'asyncapi');
49
50
  </a>
50
51
  ))
51
52
  }
53
+ {
54
+ graphQLSpecifications.length > 0 &&
55
+ graphQLSpecifications.map((spec) => (
56
+ <a
57
+ href={buildUrl(
58
+ `/docs/${collectionItem.collection}/${collectionItem.data.id}/${collectionItem.data.version}/graphql/${spec.filenameWithoutExtension}`
59
+ )}
60
+ class="px-1 text-sm font-light flex items-center space-x-1 hover:underline rounded-md hover:bg-gradient-to-l hover:from-purple-500 hover:to-purple-700 hover:text-white"
61
+ >
62
+ <img src={buildUrl('/icons/graphql.svg', true)} class="h-4 w-4" />
63
+ <span>{spec.name}</span>
64
+ </a>
65
+ ))
66
+ }
52
67
  </div>
@@ -0,0 +1,158 @@
1
+ ---
2
+ import type { ComponentType } from 'react';
3
+ import * as Icons from '@heroicons/react/24/outline';
4
+ import * as SolidIcons from '@heroicons/react/24/solid';
5
+ import * as LucideIcons from 'lucide-react';
6
+
7
+ interface AttachmentObject {
8
+ url: string;
9
+ title?: string;
10
+ type?: string;
11
+ description?: string;
12
+ summary?: string;
13
+ icon?: string;
14
+ }
15
+
16
+ type Attachment = string | AttachmentObject;
17
+
18
+ interface Props {
19
+ title?: string;
20
+ description?: string;
21
+ columns?: number;
22
+ data: {
23
+ attachments: Attachment[];
24
+ };
25
+ }
26
+
27
+ const { title = 'Attachments', description, columns = 2, ...props } = Astro.props;
28
+ const { attachments } = props.data;
29
+
30
+ function getIconForAttachment(attachment: AttachmentObject): ComponentType<{ className?: string }> | null {
31
+ // If custom icon is provided, try to find it
32
+ if (attachment.icon) {
33
+ const customIcon =
34
+ (LucideIcons as any)[attachment.icon] || (Icons as any)[attachment.icon] || (SolidIcons as any)[attachment.icon];
35
+ if (customIcon) return customIcon;
36
+ }
37
+
38
+ // Default to link icon for all attachments
39
+ return Icons.LinkIcon;
40
+ }
41
+
42
+ function normalizeAttachment(attachment: Attachment): AttachmentObject {
43
+ if (typeof attachment === 'string') {
44
+ // Extract filename from URL for title
45
+ const urlParts = attachment.split('/');
46
+ const filename = urlParts[urlParts.length - 1];
47
+ const title = filename.includes('.') ? filename.split('.')[0] : filename || 'Link';
48
+
49
+ return {
50
+ url: attachment,
51
+ title: title.replace(/[-_]/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()),
52
+ };
53
+ }
54
+ return attachment;
55
+ }
56
+
57
+ function isExternalUrl(url: string): boolean {
58
+ return url.startsWith('http://') || url.startsWith('https://');
59
+ }
60
+
61
+ const normalizedAttachments = attachments.map(normalizeAttachment);
62
+
63
+ // Group attachments by type
64
+ const groupedAttachments = normalizedAttachments.reduce(
65
+ (groups, attachment) => {
66
+ const type = attachment.type || 'Other';
67
+ if (!groups[type]) {
68
+ groups[type] = [];
69
+ }
70
+ groups[type].push(attachment);
71
+ return groups;
72
+ },
73
+ {} as Record<string, AttachmentObject[]>
74
+ );
75
+
76
+ const sortedGroups = Object.entries(groupedAttachments).sort(([a], [b]) => {
77
+ if (a === 'Other') return 1;
78
+ if (b === 'Other') return -1;
79
+ return a.localeCompare(b);
80
+ });
81
+ ---
82
+
83
+ <section class="not-prose my-8">
84
+ {title && <h3 class="flex items-center gap-2 text-3xl font-bold mb-4">{title}</h3>}
85
+
86
+ {description && <p class="text-gray-600 mb-6 prose prose-md">{description}</p>}
87
+
88
+ {
89
+ normalizedAttachments.length === 0 ? (
90
+ <div class="text-gray-500 text-sm italic">No attachments available.</div>
91
+ ) : (
92
+ <div class="space-y-6">
93
+ {sortedGroups.map(([groupType, groupAttachments], index) => (
94
+ <div key={groupType + index}>
95
+ <h4 class="text-sm font-medium text-gray-900 mb-3 uppercase tracking-wider">
96
+ {groupType} ({groupAttachments.length})
97
+ </h4>
98
+ <div class={`grid grid-cols-1 ${columns === 1 ? '' : `md:grid-cols-${Math.min(columns, 3)}`} gap-4`}>
99
+ {groupAttachments.map((attachment) => {
100
+ const IconComponent = getIconForAttachment(attachment);
101
+ const isExternal = isExternalUrl(attachment.url);
102
+
103
+ return (
104
+ <a
105
+ href={attachment.url}
106
+ target={isExternal ? '_blank' : '_self'}
107
+ rel={isExternal ? 'noopener noreferrer' : undefined}
108
+ class="group block bg-white border border-gray-200 rounded-lg p-4 transition-all duration-200 hover:shadow-md hover:border-purple-300 hover:bg-purple-50/30 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
109
+ >
110
+ <div class="flex items-start gap-3">
111
+ <div class="flex-shrink-0 p-2 bg-gray-100 rounded-lg group-hover:bg-purple-100 transition-colors duration-200">
112
+ {IconComponent && <IconComponent className="w-5 h-5 text-gray-600 group-hover:text-purple-600" />}
113
+ </div>
114
+
115
+ <div class="flex-grow min-w-0">
116
+ <div class="flex items-center gap-2">
117
+ <h5 class="text-sm font-medium text-gray-900 group-hover:text-purple-900 truncate">
118
+ {attachment.title || 'Attachment'}
119
+ </h5>
120
+ {isExternal && (
121
+ <Icons.ArrowTopRightOnSquareIcon className="w-3 h-3 text-gray-400 group-hover:text-purple-500 flex-shrink-0" />
122
+ )}
123
+ </div>
124
+
125
+ {attachment.summary && (
126
+ <p class="mt-1 text-xs text-gray-600 group-hover:text-purple-700 line-clamp-2">{attachment.summary}</p>
127
+ )}
128
+
129
+ {attachment.description && (
130
+ <p class="mt-1 text-xs text-gray-600 group-hover:text-purple-700 line-clamp-2">
131
+ {attachment.description}
132
+ </p>
133
+ )}
134
+
135
+ <div class="mt-2 text-xs text-gray-400 group-hover:text-purple-500 truncate">
136
+ {attachment.url.replace(/^https?:\/\//, '')}
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </a>
141
+ );
142
+ })}
143
+ </div>
144
+ </div>
145
+ ))}
146
+ </div>
147
+ )
148
+ }
149
+ </section>
150
+
151
+ <style>
152
+ .line-clamp-2 {
153
+ display: -webkit-box;
154
+ -webkit-line-clamp: 2;
155
+ -webkit-box-orient: vertical;
156
+ overflow: hidden;
157
+ }
158
+ </style>
@@ -23,20 +23,36 @@ function startsWithProtocol(str: string) {
23
23
  }
24
24
  ---
25
25
 
26
- <a
27
- href={startsWithProtocol(href) ? href : buildUrl(href)}
28
- target={openWindow ? '_blank' : '_self'}
29
- class="block bg-white border border-gray-200 rounded-lg p-6 transition-all duration-300 ease-in-out hover:shadow-md hover:border-primary focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-primary focus:ring-white"
30
- >
31
- <div class="flex flex-col h-full space-y-8">
32
- {IconComponent && <IconComponent className={`w-6 h-6 ${iconColor}`} />}
33
- <div>
34
- <h2 class="text-gray-800 text-lg font-semibold transition-colors duration-300 ease-in-out group-hover:text-gray-300">
35
- {title}
36
- </h2>
37
- <p class="text-gray-600 transition-colors duration-300 ease-in-out group-hover:text-gray-200 m-0 font-light text-sm">
38
- {description}
39
- </p>
26
+ <div class="group">
27
+ <!-- Card with just the icon -->
28
+ <a
29
+ href={startsWithProtocol(href) ? href : buildUrl(href)}
30
+ target={openWindow ? '_blank' : '_self'}
31
+ class="block relative bg-white border border-gray-200 rounded-xl p-8 h-48 transition-all duration-300 ease-out hover:border-purple-300 hover:shadow-lg hover:shadow-purple-500/10 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 overflow-hidden"
32
+ >
33
+ <!-- Icon centered in card with grid background -->
34
+ <div class="relative h-full flex items-center justify-center">
35
+ <!-- Squared paper grid behind icon -->
36
+ <div
37
+ class="absolute inset-0 opacity-15"
38
+ style="background-image:
39
+ linear-gradient(rgba(147, 51, 234, 0.2) 1px, transparent 1px),
40
+ linear-gradient(90deg, rgba(147, 51, 234, 0.2) 1px, transparent 1px);
41
+ background-size: 16px 16px;"
42
+ >
43
+ </div>
44
+
45
+ {IconComponent && <IconComponent className="relative w-16 h-16 text-purple-600 stroke-1" />}
40
46
  </div>
47
+ </a>
48
+
49
+ <!-- Title and description outside card -->
50
+ <div class="mt-4 space-y-0">
51
+ <h2 class="text-gray-900 text-lg font-semibold">
52
+ {title}
53
+ </h2>
54
+ <p class="text-gray-600 text-sm leading-relaxed">
55
+ {description}
56
+ </p>
41
57
  </div>
42
- </a>
58
+ </div>
@@ -2,9 +2,9 @@
2
2
  const { title, columns = 2 } = Astro.props;
3
3
  ---
4
4
 
5
- <section class="not-prose">
6
- {title && <h2 class="text-2xl font-bold text-gray-800 mb-4">{title}</h2>}
7
- <div class={`grid grid-cols-1 md:grid-cols-${columns} gap-4 w-full not-prose`}>
5
+ <section class="not-prose my-8">
6
+ {title && <h2 class="text-2xl font-bold text-gray-800 mb-6">{title}</h2>}
7
+ <div class={`grid grid-cols-1 md:grid-cols-${columns} gap-6 w-full not-prose`}>
8
8
  <slot />
9
9
  </div>
10
10
  </section>
@@ -13,6 +13,7 @@ import Admonition from '@components/MDX/Admonition';
13
13
  import OpenAPI from '@components/MDX/OpenAPI/OpenAPI.astro';
14
14
  import AsyncAPI from '@components/MDX/AsyncAPI/AsyncAPI.astro';
15
15
  import ChannelInformation from '@components/MDX/ChannelInformation/ChannelInformation';
16
+ import Attachments from '@components/MDX/Attachments.astro';
16
17
  import MessageTable from '@components/MDX/MessageTable/MessageTable.astro';
17
18
  import ResourceGroupTable from '@components/MDX/ResourceGroupTable/ResourceGroupTable.astro';
18
19
  import EntityPropertiesTable from '@components/MDX/EntityPropertiesTable/EntityPropertiesTable.astro';
@@ -34,6 +35,7 @@ import RemoteSchema from '@components/MDX/RemoteSchema.astro';
34
35
 
35
36
  const components = (props: any) => {
36
37
  return {
38
+ Attachments: (mdxProp: any) => jsx(Attachments, { ...props, ...mdxProp }),
37
39
  Accordion,
38
40
  AccordionGroup,
39
41
  Admonition,
@@ -27,6 +27,19 @@ const parameters = Object.keys(channelParameters).map((key) => ({
27
27
  ...channelParameters[key],
28
28
  }));
29
29
 
30
+ const attachments = channel.data.attachments || [];
31
+
32
+ const attachmentsList = attachments.map((a) => {
33
+ const attachmentIsURL = typeof a === 'string';
34
+ return {
35
+ label: attachmentIsURL ? a : (a.title ?? a.url),
36
+ href: attachmentIsURL ? a : a.url,
37
+ icon: attachmentIsURL ? 'ExternalLinkIcon' : (a.icon ?? 'ExternalLinkIcon'),
38
+ target: '_blank' as const,
39
+ subgroup: attachmentIsURL ? undefined : (a.type ?? ''),
40
+ };
41
+ });
42
+
30
43
  const paramsList = parameters.map((param) => ({
31
44
  label: param.key,
32
45
  description: param?.description,
@@ -139,6 +152,18 @@ const shouldRenderSideBarSection = (section: string) => {
139
152
  )
140
153
  }
141
154
 
155
+ {
156
+ channel.data.attachments && shouldRenderSideBarSection('attachments') && (
157
+ <PillListFlat
158
+ title={`Attachments (${attachmentsList.length})`}
159
+ pills={attachmentsList}
160
+ emptyMessage={`This channel does not have any attachments.`}
161
+ color="pink"
162
+ client:load
163
+ />
164
+ )
165
+ }
166
+
142
167
  {
143
168
  ownersList.length > 0 && shouldRenderSideBarSection('owners') && (
144
169
  <OwnersList