@herdingbits/trailhead-cloudscape 0.0.13 → 0.0.16

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/README.md CHANGED
@@ -37,7 +37,7 @@ import '@herdingbits/trailhead-cloudscape/shell.css';
37
37
 
38
38
  const shell = new Trailhead({
39
39
  adapter: new CloudScapeAdapter(),
40
- basePath: '/app',
40
+ appBasePath: '/app',
41
41
  apiUrl: 'https://api.example.com'
42
42
  });
43
43
 
@@ -45,6 +45,15 @@ const root = createRoot(document.getElementById('app')!);
45
45
  root.render(<ShellApp shell={shell} />);
46
46
  ```
47
47
 
48
+ To load the CloudScape global styles from a CDN instead of bundling via npm import, pass `cloudscapeUrl` to the adapter (and remove the `@cloudscape-design/global-styles/index.css` import from your entry point):
49
+
50
+ ```typescript
51
+ const shell = new Trailhead({
52
+ adapter: new CloudScapeAdapter({ cloudscapeUrl: 'https://unpkg.com/@cloudscape-design/global-styles@1.0.0/index.css' }),
53
+ appBasePath: '/app',
54
+ });
55
+ ```
56
+
48
57
  ## What's Included
49
58
 
50
59
  - **CloudScapeAdapter** - Implements the Trailhead adapter interface
package/dist/adapter.d.ts CHANGED
@@ -3,11 +3,16 @@
3
3
  * Uses CloudScape Flashbar, Modal, and Spinner components
4
4
  */
5
5
  import type { DesignSystemAdapter, FeedbackAdapter } from '@herdingbits/trailhead-types/adapters';
6
+ export interface CloudScapeAdapterConfig {
7
+ /** URL for the CloudScape global-styles CSS. If provided, injected dynamically instead of requiring a hardcoded HTML link tag. */
8
+ cloudscapeUrl?: string;
9
+ }
6
10
  export declare class CloudScapeAdapter implements DesignSystemAdapter {
7
11
  name: string;
8
12
  version: string;
9
13
  feedback: FeedbackAdapter;
10
- constructor();
11
- init(basePath: string): Promise<void>;
14
+ private readonly config?;
15
+ constructor(config?: CloudScapeAdapterConfig);
16
+ init(shellUrl: string): Promise<void>;
12
17
  }
13
18
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAA4C,MAAM,uCAAuC,CAAC;AAyE5I,qBAAa,iBAAkB,YAAW,mBAAmB;IAC3D,IAAI,SAAgB;IACpB,OAAO,SAAW;IAClB,QAAQ,EAAE,eAAe,CAAC;;IAMpB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG5C"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAA4C,MAAM,uCAAuC,CAAC;AAyE5I,MAAM,WAAW,uBAAuB;IACtC,kIAAkI;IAClI,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,iBAAkB,YAAW,mBAAmB;IAC3D,IAAI,SAAgB;IACpB,OAAO,SAAW;IAClB,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAA0B;gBAEtC,MAAM,CAAC,EAAE,uBAAuB;IAKtC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAQ5C"}
package/dist/adapter.js CHANGED
@@ -52,12 +52,18 @@ class CloudScapeFeedbackAdapter {
52
52
  }
53
53
  }
54
54
  export class CloudScapeAdapter {
55
- constructor() {
55
+ constructor(config) {
56
56
  this.name = 'cloudscape';
57
57
  this.version = '3.0.0';
58
+ this.config = config;
58
59
  this.feedback = new CloudScapeFeedbackAdapter();
59
60
  }
60
- async init(basePath) {
61
- // CloudScape styles loaded via global-styles package
61
+ async init(shellUrl) {
62
+ if (this.config?.cloudscapeUrl) {
63
+ const link = document.createElement('link');
64
+ link.rel = 'stylesheet';
65
+ link.href = this.config.cloudscapeUrl;
66
+ document.head.appendChild(link);
67
+ }
62
68
  }
63
69
  }
@@ -1 +1 @@
1
- {"version":3,"file":"shell-app.d.ts","sourceRoot":"","sources":["../src/shell-app.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,UAAU,aAAa;IACrB,KAAK,EAAE,SAAS,CAAC;CAClB;AAiBD,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,EAAE,aAAa,2CAmNhD"}
1
+ {"version":3,"file":"shell-app.d.ts","sourceRoot":"","sources":["../src/shell-app.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAW,MAAM,6BAA6B,CAAC;AAEtE,UAAU,aAAa;IACrB,KAAK,EAAE,SAAS,CAAC;CAClB;AAiBD,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,EAAE,aAAa,2CAkNhD"}
package/dist/shell-app.js CHANGED
@@ -21,8 +21,8 @@ export function ShellApp({ shell }) {
21
21
  // Get current path without basePath
22
22
  const getCurrentPath = () => {
23
23
  let path = window.location.pathname;
24
- if (shell.basePath && path.startsWith(shell.basePath)) {
25
- path = path.substring(shell.basePath.length) || '/';
24
+ if (shell.appBasePath && path.startsWith(shell.appBasePath)) {
25
+ path = path.substring(shell.appBasePath.length) || '/';
26
26
  }
27
27
  return path;
28
28
  };
@@ -70,21 +70,20 @@ export function ShellApp({ shell }) {
70
70
  }
71
71
  }, [navigation, currentPath]);
72
72
  const handleRoute = (path) => {
73
- // Normalize path by removing trailing slash for matching
74
73
  const normalizedPath = path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;
75
- const route = navigation.find(item => item.path === normalizedPath);
76
- if (route && contentRef.current) {
77
- loadApp(route.app, route.path);
74
+ const app = shell.getApps().find(entry => normalizedPath.startsWith(entry.basePath));
75
+ if (app && contentRef.current) {
76
+ loadApp(app.src, app.basePath);
78
77
  }
79
78
  };
80
79
  const loadApp = async (appName, appPath) => {
81
80
  if (!contentRef.current)
82
81
  return;
83
82
  contentRef.current.innerHTML = '<div>Loading...</div>';
84
- const appBasePath = shell.basePath + appPath;
83
+ const appBasePath = shell.appBasePath + appPath;
85
84
  try {
86
- const pluginUrl = `${shell.basePath}${appPath}/app.js`;
87
- const pluginCss = `${shell.basePath}${appPath}/${appName}.css`;
85
+ const pluginUrl = `${shell.appBasePath}${appPath}/app.js`;
86
+ const pluginCss = `${shell.appBasePath}${appPath}/${appName}.css`;
88
87
  // Load CSS
89
88
  const link = document.createElement("link");
90
89
  link.rel = "stylesheet";
@@ -148,5 +147,5 @@ export function ShellApp({ shell }) {
148
147
  padding: '24px',
149
148
  borderRadius: '8px',
150
149
  textAlign: 'center',
151
- }, children: [_jsx(Spinner, { size: "large" }), _jsx("div", { style: { marginTop: '16px' }, children: busyMessage })] }) })), _jsx(Modal, { visible: dialogState.visible, onDismiss: handleDialogDismiss, header: dialogState.title, footer: _jsx(Box, { float: "right", children: _jsx(SpaceBetween, { direction: "horizontal", size: "xs", children: dialogState.buttons.map((btn, idx) => (_jsx(Button, { variant: btn.variant === 'primary' ? 'primary' : 'normal', onClick: () => handleDialogButton(btn.value), children: btn.label }, idx))) }) }), children: dialogState.message }), _jsx(ShellLayout, { navigation: navigation, currentPath: currentPath, basePath: shell.basePath, onNavigate: handleNavigate, children: _jsx("div", { id: "shell-content", ref: contentRef }) })] }));
150
+ }, children: [_jsx(Spinner, { size: "large" }), _jsx("div", { style: { marginTop: '16px' }, children: busyMessage })] }) })), _jsx(Modal, { visible: dialogState.visible, onDismiss: handleDialogDismiss, header: dialogState.title, footer: _jsx(Box, { float: "right", children: _jsx(SpaceBetween, { direction: "horizontal", size: "xs", children: dialogState.buttons.map((btn, idx) => (_jsx(Button, { variant: btn.variant === 'primary' ? 'primary' : 'normal', onClick: () => handleDialogButton(btn.value), children: btn.label }, idx))) }) }), children: dialogState.message }), _jsx(ShellLayout, { navigation: navigation, currentPath: currentPath, basePath: shell.appBasePath, onNavigate: handleNavigate, children: _jsx("div", { id: "shell-content", ref: contentRef }) })] }));
152
151
  }
@@ -1,12 +1,7 @@
1
1
  import React from 'react';
2
- interface NavigationItem {
3
- id: string;
4
- label: string;
5
- path: string;
6
- icon?: string;
7
- }
2
+ import type { NavItem } from '@herdingbits/trailhead-core';
8
3
  interface ShellLayoutProps {
9
- navigation: NavigationItem[];
4
+ navigation: NavItem[];
10
5
  currentPath: string;
11
6
  basePath: string;
12
7
  onNavigate: (path: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"shell-layout.d.ts","sourceRoot":"","sources":["../src/shell-layout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAMnD,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,UAAU,gBAAgB;IACxB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,gBAAgB,2CA2BxG"}
1
+ {"version":3,"file":"shell-layout.d.ts","sourceRoot":"","sources":["../src/shell-layout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,OAAO,KAAK,EAAE,OAAO,EAAW,MAAM,6BAA6B,CAAC;AAEpE,UAAU,gBAAgB;IACxB,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,gBAAgB,2CA+CxG"}
@@ -4,12 +4,30 @@ import AppLayout from '@cloudscape-design/components/app-layout';
4
4
  import SideNavigation from '@cloudscape-design/components/side-navigation';
5
5
  export function ShellLayout({ navigation, currentPath, basePath, onNavigate, children }) {
6
6
  const [navigationOpen, setNavigationOpen] = useState(true);
7
- const navItems = navigation.map(item => ({
7
+ const isExternal = (href) => /^https?:\/\/|^\/\//.test(href);
8
+ const resolveHref = (href) => isExternal(href) ? href : basePath + href + '/';
9
+ const mapLink = (item) => ({
8
10
  type: 'link',
9
11
  text: item.label,
10
- href: basePath + item.path + '/', // Add trailing slash for directory routing
11
- }));
12
- return (_jsx(AppLayout, { navigationOpen: navigationOpen, onNavigationChange: ({ detail }) => setNavigationOpen(detail.open), navigation: _jsx(SideNavigation, { activeHref: currentPath, items: navItems, onFollow: (event) => {
12
+ href: resolveHref(item.href),
13
+ });
14
+ const navItems = [...navigation]
15
+ .sort((a, b) => a.order - b.order)
16
+ .map((item) => {
17
+ switch (item.type) {
18
+ case 'link':
19
+ return mapLink(item);
20
+ case 'section':
21
+ return {
22
+ type: 'section',
23
+ text: item.label,
24
+ items: [...item.children].sort((a, b) => a.order - b.order).map(mapLink),
25
+ };
26
+ case 'divider':
27
+ return { type: 'divider' };
28
+ }
29
+ });
30
+ return (_jsx(AppLayout, { navigationOpen: navigationOpen, onNavigationChange: ({ detail }) => setNavigationOpen(detail.open), navigation: _jsx(SideNavigation, { activeHref: basePath + currentPath + (currentPath.endsWith('/') ? '' : '/'), items: navItems, onFollow: (event) => {
13
31
  event.preventDefault();
14
32
  onNavigate(event.detail.href);
15
33
  } }), content: children, toolsHide: true }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herdingbits/trailhead-cloudscape",
3
- "version": "0.0.13",
3
+ "version": "0.0.16",
4
4
  "description": "AWS CloudScape adapter for Trailhead. React-based shell with CloudScape components. Supports React 19.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,16 +21,16 @@
21
21
  "build": "npm run clean && tsc && cp src/shell.css dist/"
22
22
  },
23
23
  "peerDependencies": {
24
- "@herdingbits/trailhead-core": "^0.0.15",
25
24
  "@cloudscape-design/components": "^3.0.0",
26
25
  "@cloudscape-design/global-styles": "^1.0.0",
26
+ "@herdingbits/trailhead-core": "^0.0.16",
27
27
  "react": "^19.0.0",
28
28
  "react-dom": "^19.0.0"
29
29
  },
30
30
  "devDependencies": {
31
- "@herdingbits/trailhead-types": "^0.0.12",
32
31
  "@cloudscape-design/components": "^3.0.1297",
33
32
  "@cloudscape-design/global-styles": "^1.0.59",
33
+ "@herdingbits/trailhead-types": "^0.0.14",
34
34
  "@types/react": "^19.2.14",
35
35
  "@types/react-dom": "^19.2.3",
36
36
  "react": "^19.2.6",