@mostrom/app-shell 0.1.1 → 0.1.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # App Shell
2
2
 
3
- Shared Cloudscape-based layout shell used by all platform services. Mirrors the AWS Console layout pattern.
3
+ Shared layout shell used by all platform services. Provides a consistent layout pattern with global header, service navigation, and content areas.
4
4
 
5
5
  ## Publishing to npm
6
6
 
@@ -14,69 +14,63 @@ From `platform/shared/packages/app-shell`:
14
14
  Default scope is `@mostrom` (override with `--scope` if needed).
15
15
 
16
16
  ```bash
17
- # Dry run
18
- npm run publish:npm:dry-run -- --version 0.1.0
19
- # or: sh scripts/publish-npm.sh --version 0.1.0 --dry-run
17
+ # Dry run (auto-increment patch version)
18
+ npm run publish:npm:dry-run
19
+ # or: sh scripts/publish-npm.sh --dry-run
20
20
 
21
- # Publish to latest
21
+ # Publish to latest (auto-increment patch version)
22
+ npm run publish:npm
23
+ # or: sh scripts/publish-npm.sh
24
+
25
+ # First run or explicit override
22
26
  npm run publish:npm -- --version 0.1.0
23
27
  # or: sh scripts/publish-npm.sh --version 0.1.0
24
28
 
25
29
  # Publish to custom tag
26
- npm run publish:npm -- --version 0.1.0 --tag next
27
- # or: sh scripts/publish-npm.sh --version 0.1.0 --tag next
30
+ npm run publish:npm -- --tag next
31
+ # or: sh scripts/publish-npm.sh --tag next
28
32
  ```
29
33
 
30
- The script reads `NODE_AUTH_TOKEN` from environment variables, and also checks `app-shell/.env` for that key.
34
+ The script reads `NODE_AUTH_TOKEN` from environment variables, and also checks `app-shell/.env` for that key. It stores the last successful version in `scripts/.publish-version` and auto-increments patch versions on subsequent runs.
35
+
36
+ No init script is shipped in the published package.
31
37
 
32
38
  ## Quick Start
33
39
 
34
- ### Initial Setup (New Apps)
40
+ ### Setup (No Init Script)
35
41
 
36
- Run the init script to configure a new app to use app-shell:
42
+ App setup is file-based and does not require running an init command.
37
43
 
38
- ```bash
39
- # From your app's frontend directory, run the init script directly
40
- node ../../../shared/packages/app-shell/bin/init.js
44
+ Install using npm aliases so existing imports stay as `@platform/*`:
41
45
 
42
- # Force overwrite existing files
43
- node ../../../shared/packages/app-shell/bin/init.js --force
44
-
45
- # Alternative: If app-shell is linked in node_modules, use the bin
46
- ./node_modules/.bin/app-shell-init
46
+ ```json
47
+ {
48
+ "dependencies": {
49
+ "@platform/app-shell": "npm:@mostrom/app-shell@latest",
50
+ "@platform/service-catalog": "npm:@mostrom/service-catalog@latest"
51
+ }
52
+ }
47
53
  ```
48
54
 
49
- > **Note:** In this monorepo, prefer running the local init script directly. If you publish to npm, consumers can also use the installed bin (`app-shell-init`).
50
-
51
- The init script creates/updates:
52
-
53
- - `app/tailwind.css` - Imports app-shell styles
54
- - `tailwind.config.ts` - Configured to scan app-shell components
55
- - `components.json` - shadcn/ui configuration
56
- - `app/lib/utils.ts` - Utility functions (cn, getBasePathHref)
55
+ Required app files:
57
56
 
58
- ### When to Run Init
59
-
60
- | Scenario | Run Init? |
61
- | ------------------------------------------- | ---------------------------------------------------- |
62
- | Setting up a new app | Yes |
63
- | CSS changes in app-shell | No - changes are picked up automatically |
64
- | Adding new shadcn components | No - use `npx shadcn@latest add <component>` |
65
- | Tailwind not scanning app-shell classes | Yes, with `--force` to regenerate tailwind.config.ts |
66
- | Upgrading app-shell with new required files | Yes, with `--force` |
57
+ - `app/tailwind.css`
58
+ - `tailwind.config.ts`
59
+ - `components.json`
60
+ - `app/lib/utils.ts`
61
+ - `vite.config.ts` using `getSharedViteConfig()` from `@platform/app-shell/vite`
67
62
 
68
63
  ### Troubleshooting
69
64
 
70
65
  **Styles not applying from app-shell:**
71
66
 
72
- 1. Ensure `tailwind.config.ts` includes the app-shell path in `content`:
67
+ 1. Ensure `tailwind.config.ts` includes app-shell content:
73
68
  ```ts
74
69
  content: [
75
70
  "./app/**/*.{ts,tsx,js,jsx}",
76
- "../../../shared/packages/app-shell/src/**/*.{ts,tsx}",
71
+ "./node_modules/@platform/app-shell/src/**/*.{ts,tsx}",
77
72
  ],
78
73
  ```
79
- 2. Run `node ../../../shared/packages/app-shell/bin/init.js --force` to regenerate config files
80
74
 
81
75
  **Conflicting CSS (wrong colors, broken layout):**
82
76
 
@@ -101,72 +95,55 @@ The init script creates/updates:
101
95
  +--------+-----------------------------------------+---------------+
102
96
  ```
103
97
 
104
- ## Component Map (Cloudscape)
105
-
106
- ### Global Header (top-most, full width)
107
-
108
- Cloudscape `TopNavigation`.
109
-
110
- | Position | Element | Cloudscape Component | Behavior |
111
- | -------- | -------- | ------------------------------------------------------ | ---------------------- |
112
- | Left | Logo | `TopNavigation` `identity` | Navigate to home |
113
- | Center | Search | `TopNavigation` `search` with `Input` or `Autosuggest` | Global search |
114
- | Right | Apps | `TopNavigation` utility with `Icon` `grid-view` | Opens dropdown (empty) |
115
- | Right | Settings | `TopNavigation` utility with `Icon` `settings` | Opens dropdown (empty) |
116
- | Right | User | `TopNavigation` utility with `Icon` `user-profile` | User menu |
117
-
118
- ### Service Header (below Global Header, full width)
119
-
120
- Cloudscape `AppLayoutToolbar`.
121
-
122
- | Position | Element | Cloudscape Component | Behavior |
123
- | -------- | ------------ | ------------------------------------------------ | ------------------------ |
124
- | Left | Menu | `Button` (icon-only) with `Icon` `menu` | Toggles Left Panel |
125
- | Left | Service Name | Text | Current service name |
126
- | Center | Breadcrumbs | `BreadcrumbGroup` | Single breadcrumb source |
127
- | Right | Panel Toggle | `Button` (icon-only) with `Icon` `view-vertical` | Toggles Right Panel |
128
-
129
- ### Left Panel
98
+ ## UI Components
130
99
 
131
- - Slot: `navigation` in `AppLayoutToolbar`
132
- - Component: `SideNavigation`
133
- - Header: `Header` with an icon-only `Button` (`Icon` `angle-left`) to collapse
134
- - Content: Empty placeholder list (service provides content)
100
+ App Shell provides a comprehensive set of UI components built with Radix UI and Tailwind CSS:
135
101
 
136
- ### Right Panel
102
+ ### Layout Components
137
103
 
138
- - Slot: `tools` in `AppLayoutToolbar`
139
- - Content: Empty container (no `HelpPanel`)
104
+ | Component | Description |
105
+ | --------- | ----------- |
106
+ | `AppShell` | Main layout wrapper with header, navigation, and content areas |
107
+ | `SpaceBetween` | Flexbox container with consistent gap sizing (xxxs to xxl) |
140
108
 
141
- ### Notifications
109
+ ### Form & Input Components
142
110
 
143
- - Slot: `notifications` in `AppLayoutToolbar`
144
- - Component: `Flashbar`
111
+ | Component | Description |
112
+ | --------- | ----------- |
113
+ | `Button` | Primary, normal, outline, ghost, link, and destructive variants |
114
+ | `Input` | Text input with validation states |
115
+ | `Select` | Dropdown selection with search/filter support |
116
+ | `Toggle` | On/off switch component |
145
117
 
146
- ### Main Content Area
118
+ ### Display Components
147
119
 
148
- - Renders `children` passed to `AppShell`
120
+ | Component | Description |
121
+ | --------- | ----------- |
122
+ | `Badge` | Status indicators with color variants (blue, green, grey, red) |
123
+ | `Avatar` | User/entity avatars with fallback initials |
124
+ | `Card` | Content container with header/footer slots |
149
125
 
150
- ## Required Cloudscape Packages
126
+ ### Data Components
151
127
 
152
- - `@cloudscape-design/components`
153
- - `@cloudscape-design/global-styles` (import once per app for base styles/fonts)
128
+ | Component | Description |
129
+ | --------- | ----------- |
130
+ | `DataTable` | Full-featured table with sorting, filtering, and pagination |
131
+ | `SectionedListBoard` | Kanban-style board with drag-and-drop |
132
+ | `SectionedListTable` | Grouped list view with collapsible sections |
154
133
 
155
134
  ## Service Catalog Dependency
156
135
 
157
- The App Shell builds the **All services** menu and global search suggestions from
158
- `@mostrom/service-catalog`. Until we move to a monorepo workspace setup, each
159
- app should explicitly depend on this package to avoid module resolution issues.
136
+ The App Shell builds the **All services** menu and global search suggestions from service-catalog.
160
137
 
161
- Add this to your app's `package.json` dependencies:
138
+ Use this alias in consuming apps:
162
139
 
163
140
  ```json
164
141
  {
165
- "@mostrom/service-catalog": "file:../../../shared/packages/service-catalog"
142
+ "@platform/service-catalog": "npm:@mostrom/service-catalog@latest"
166
143
  }
167
144
  ```
168
145
 
169
146
  ## Notes
170
147
 
171
- - Do not duplicate breadcrumbs outside the `BreadcrumbGroup`.
172
- - Use Cloudscape components as-is. No custom styling overrides required for the initial scaffold.
148
+ - Use the provided UI components for consistent styling across all services.
149
+ - Import `@platform/app-shell/styles.css` in your app's entry point.
package/bun.lock CHANGED
@@ -35,9 +35,11 @@
35
35
  "vaul": "^1.1.2",
36
36
  "zod": "^4.3.6",
37
37
  },
38
+ "devDependencies": {
39
+ "@types/react": "19.2.14",
40
+ "@types/react-dom": "19.2.3",
41
+ },
38
42
  "peerDependencies": {
39
- "@types/react": "^19.0.0",
40
- "@types/react-dom": "^19.0.0",
41
43
  "react": "^19.0.0",
42
44
  "react-dom": "^19.0.0",
43
45
  },
@@ -242,7 +244,7 @@
242
244
 
243
245
  "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
244
246
 
245
- "@types/react": ["@types/react@19.2.13", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ=="],
247
+ "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
246
248
 
247
249
  "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
248
250
 
package/package.json CHANGED
@@ -1,12 +1,8 @@
1
1
  {
2
2
  "name": "@mostrom/app-shell",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "private": false,
5
5
  "type": "module",
6
- "bin": {
7
- "app-shell": "./bin/init.js",
8
- "app-shell-init": "./bin/init.js"
9
- },
10
6
  "exports": {
11
7
  ".": "./src/index.ts",
12
8
  "./styles.css": "./src/styles.css",
@@ -27,14 +23,13 @@
27
23
  },
28
24
  "dependencies": {
29
25
  "@base-ui/react": "^1.1.0",
30
- "@cloudscape-design/components": "*",
31
- "@cloudscape-design/global-styles": "*",
32
26
  "@dnd-kit/core": "^6.3.1",
33
27
  "@dnd-kit/modifiers": "^9.0.0",
34
28
  "@dnd-kit/sortable": "^10.0.0",
35
29
  "@dnd-kit/utilities": "^3.2.2",
36
30
  "@floating-ui/dom": "^1.7.5",
37
31
  "@hookform/resolvers": "^5.2.2",
32
+ "@platform/service-catalog": "npm:@mostrom/service-catalog@^0.1.3",
38
33
  "@radix-ui/react-dropdown-menu": "^2.1.4",
39
34
  "@tanstack/react-table": "^8.21.3",
40
35
  "class-variance-authority": "^0.7.1",
@@ -53,8 +48,7 @@
53
48
  "tailwind-merge": "^3.4.0",
54
49
  "tw-animate-css": "^1.4.0",
55
50
  "vaul": "^1.1.2",
56
- "zod": "^4.3.6",
57
- "@mostrom/service-catalog": "^0.1.1"
51
+ "zod": "^4.3.6"
58
52
  },
59
53
  "devDependencies": {
60
54
  "@types/react": "19.2.14",
@@ -4,14 +4,15 @@ set -eu
4
4
  usage() {
5
5
  cat <<USAGE
6
6
  Usage:
7
- ./scripts/publish-npm.sh --version <version> [--scope <scope>] [--tag <tag>] [--otp <code>] [--dry-run]
8
- ./scripts/publish-npm.sh <version> [--scope <scope>] [--tag <tag>] [--otp <code>] [--dry-run]
7
+ ./scripts/publish-npm.sh [--version <version>] [--scope <scope>] [--tag <tag>] [--otp <code>] [--dry-run]
8
+ ./scripts/publish-npm.sh [<version>] [--scope <scope>] [--tag <tag>] [--otp <code>] [--dry-run]
9
9
 
10
10
  Examples:
11
+ ./scripts/publish-npm.sh
11
12
  ./scripts/publish-npm.sh 0.1.0
12
13
  ./scripts/publish-npm.sh --version 0.1.0 --scope @mostrom --tag next
13
14
  ./scripts/publish-npm.sh --version 0.1.0 --scope mostrom --otp 123456
14
- ./scripts/publish-npm.sh --version 0.1.0 --dry-run
15
+ ./scripts/publish-npm.sh --dry-run
15
16
  USAGE
16
17
  }
17
18
 
@@ -19,6 +20,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
19
20
  APP_SHELL_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
20
21
  PACKAGES_DIR="$(cd "${APP_SHELL_DIR}/.." && pwd)"
21
22
  SERVICE_CATALOG_DIR="${PACKAGES_DIR}/service-catalog"
23
+ VERSION_STATE_FILE="${SCRIPT_DIR}/.publish-version"
22
24
 
23
25
  VERSION=""
24
26
  SCOPE="@mostrom"
@@ -69,12 +71,6 @@ while [ "$#" -gt 0 ]; do
69
71
  esac
70
72
  done
71
73
 
72
- if [ -z "$VERSION" ]; then
73
- echo "Error: version is required." >&2
74
- usage
75
- exit 1
76
- fi
77
-
78
74
  case "$SCOPE" in
79
75
  @*) ;;
80
76
  *) SCOPE="@$SCOPE" ;;
@@ -89,6 +85,54 @@ fi
89
85
  SERVICE_CATALOG_PACKAGE_NAME="${SCOPE}/service-catalog"
90
86
  APP_SHELL_PACKAGE_NAME="${SCOPE}/app-shell"
91
87
 
88
+ bump_patch_version() {
89
+ current_version="$1"
90
+ CURRENT_VERSION="$current_version" node - <<'NODE'
91
+ const value = process.env.CURRENT_VERSION;
92
+ if (!/^\d+\.\d+\.\d+$/.test(value || "")) {
93
+ process.exit(1);
94
+ }
95
+ const [major, minor, patch] = value.split('.').map(Number);
96
+ process.stdout.write(`${major}.${minor}.${patch + 1}`);
97
+ NODE
98
+ }
99
+
100
+ infer_next_version() {
101
+ if [ -f "$VERSION_STATE_FILE" ]; then
102
+ last_version="$(tr -d '[:space:]' < "$VERSION_STATE_FILE")"
103
+ if [ -z "$last_version" ]; then
104
+ echo "Error: $VERSION_STATE_FILE is empty. Pass --version explicitly once." >&2
105
+ exit 1
106
+ fi
107
+ next_version="$(bump_patch_version "$last_version" || true)"
108
+ if [ -z "$next_version" ]; then
109
+ echo "Error: invalid version in $VERSION_STATE_FILE ($last_version). Pass --version explicitly once." >&2
110
+ exit 1
111
+ fi
112
+ echo "$next_version"
113
+ return 0
114
+ fi
115
+
116
+ latest_registry_version="$(npm view "$SERVICE_CATALOG_PACKAGE_NAME" version 2>/dev/null || true)"
117
+ if [ -n "$latest_registry_version" ]; then
118
+ next_version="$(bump_patch_version "$latest_registry_version" || true)"
119
+ if [ -z "$next_version" ]; then
120
+ echo "Error: failed to parse latest registry version ($latest_registry_version). Pass --version explicitly." >&2
121
+ exit 1
122
+ fi
123
+ echo "$next_version"
124
+ return 0
125
+ fi
126
+
127
+ echo "Error: Could not infer next version. Pass --version explicitly once (for example: --version 0.1.0)." >&2
128
+ exit 1
129
+ }
130
+
131
+ if [ -z "$VERSION" ]; then
132
+ VERSION="$(infer_next_version)"
133
+ echo "==> Auto-incremented version: ${VERSION}"
134
+ fi
135
+
92
136
  TMP_DIR="$(mktemp -d)"
93
137
  cleanup() {
94
138
  rm -rf "$TMP_DIR"
@@ -126,6 +170,7 @@ copy_pkg() {
126
170
  --exclude .env \
127
171
  --exclude test-results \
128
172
  --exclude .DS_Store \
173
+ --exclude scripts/.publish-version \
129
174
  "$from_dir/" "$to_dir/"
130
175
  }
131
176
 
@@ -135,12 +180,14 @@ rewrite_manifest() {
135
180
  package_name="$3"
136
181
  service_catalog_package="${4:-}"
137
182
  service_catalog_range="${5:-}"
183
+ service_catalog_alias="${6:-}"
138
184
 
139
185
  MANIFEST_PATH="$manifest_path" \
140
186
  PACKAGE_VERSION="$package_version" \
141
187
  PACKAGE_NAME="$package_name" \
142
188
  SERVICE_CATALOG_PACKAGE="$service_catalog_package" \
143
189
  SERVICE_CATALOG_RANGE="$service_catalog_range" \
190
+ SERVICE_CATALOG_ALIAS="$service_catalog_alias" \
144
191
  node - <<'NODE'
145
192
  const fs = require('fs');
146
193
  const p = process.env.MANIFEST_PATH;
@@ -148,15 +195,18 @@ const version = process.env.PACKAGE_VERSION;
148
195
  const packageName = process.env.PACKAGE_NAME;
149
196
  const serviceCatalogPackage = process.env.SERVICE_CATALOG_PACKAGE;
150
197
  const serviceCatalogRange = process.env.SERVICE_CATALOG_RANGE;
198
+ const serviceCatalogAlias = process.env.SERVICE_CATALOG_ALIAS;
151
199
  const pkg = JSON.parse(fs.readFileSync(p, 'utf8'));
152
200
  pkg.name = packageName;
153
201
  pkg.version = version;
154
202
  pkg.private = false;
155
203
  pkg.publishConfig = { ...(pkg.publishConfig || {}), access: 'public' };
156
- if (pkg.dependencies && pkg.dependencies['@platform/service-catalog']) {
157
- delete pkg.dependencies['@platform/service-catalog'];
158
- }
159
- if (serviceCatalogPackage && serviceCatalogRange) {
204
+ if (serviceCatalogAlias) {
205
+ pkg.dependencies = { ...(pkg.dependencies || {}), ['@platform/service-catalog']: serviceCatalogAlias };
206
+ if (serviceCatalogPackage && pkg.dependencies && pkg.dependencies[serviceCatalogPackage]) {
207
+ delete pkg.dependencies[serviceCatalogPackage];
208
+ }
209
+ } else if (serviceCatalogPackage && serviceCatalogRange) {
160
210
  pkg.dependencies = { ...(pkg.dependencies || {}), [serviceCatalogPackage]: serviceCatalogRange };
161
211
  }
162
212
  fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + '\n');
@@ -171,7 +221,7 @@ copy_pkg "$SERVICE_CATALOG_DIR" "$SERVICE_TMP_DIR"
171
221
  copy_pkg "$APP_SHELL_DIR" "$APP_SHELL_TMP_DIR"
172
222
 
173
223
  rewrite_manifest "${SERVICE_TMP_DIR}/package.json" "$VERSION" "$SERVICE_CATALOG_PACKAGE_NAME"
174
- rewrite_manifest "${APP_SHELL_TMP_DIR}/package.json" "$VERSION" "$APP_SHELL_PACKAGE_NAME" "$SERVICE_CATALOG_PACKAGE_NAME" "^${VERSION}"
224
+ rewrite_manifest "${APP_SHELL_TMP_DIR}/package.json" "$VERSION" "$APP_SHELL_PACKAGE_NAME" "$SERVICE_CATALOG_PACKAGE_NAME" "^${VERSION}" "npm:${SERVICE_CATALOG_PACKAGE_NAME}@^${VERSION}"
175
225
 
176
226
  publish_pkg() {
177
227
  pkg_dir="$1"
@@ -196,7 +246,14 @@ publish_pkg() {
196
246
  publish_pkg "$SERVICE_TMP_DIR" "$SERVICE_CATALOG_PACKAGE_NAME"
197
247
  publish_pkg "$APP_SHELL_TMP_DIR" "$APP_SHELL_PACKAGE_NAME"
198
248
 
249
+ if [ "$DRY_RUN" -eq 0 ]; then
250
+ printf "%s\n" "$VERSION" > "$VERSION_STATE_FILE"
251
+ fi
252
+
199
253
  echo "==> Done"
200
254
  echo "Published version: ${VERSION}"
201
255
  echo "Scope: ${SCOPE}"
202
256
  echo "Tag: ${TAG}"
257
+ if [ "$DRY_RUN" -eq 0 ]; then
258
+ echo "Version state file: ${VERSION_STATE_FILE}"
259
+ fi
package/src/AppShell.tsx CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * AppShell Component
3
3
  *
4
- * The primary layout wrapper for all platform services. Implements AWS Cloudscape
5
- * design system patterns to ensure visual and behavioral consistency.
4
+ * The primary layout wrapper for all platform services. Provides consistent
5
+ * visual and behavioral patterns across the platform.
6
6
  *
7
7
  * LAYOUT STRUCTURE:
8
8
  * ┌─────────────────────────────────────────────────────────────────────────────┐
@@ -43,7 +43,6 @@
43
43
 
44
44
  import React from "react";
45
45
 
46
- import "@cloudscape-design/global-styles/index.css";
47
46
  import "./styles.css";
48
47
 
49
48
  import { AppFlashbar, type FlashbarMessage } from "./components/layout/AppFlashbar";
@@ -72,7 +71,7 @@ import { GlobalHeaderSearch } from "./components/global-header/GlobalHeaderSearc
72
71
  import { type SearchOption } from "./components/search/GlobalSearch";
73
72
  import { HeaderUtilities } from "./components/global-header/HeaderUtilities";
74
73
 
75
- /** Local drawer type (replaces Cloudscape AppLayoutToolbarProps.Drawer) */
74
+ /** Drawer configuration for slide-in panels */
76
75
  interface Drawer {
77
76
  id: string;
78
77
  ariaLabels?: {
@@ -640,10 +639,9 @@ export function AppShell({
640
639
  * Left-side collapsible panel containing service-specific navigation.
641
640
  * Shows the service name in the header and hierarchical navigation items.
642
641
  */
643
- // Cast to internal type - both Cloudscape and custom types share the same shape
644
642
  const normalizedNavigationItems = (navigationItems ?? []) as ReadonlyArray<NavigationItem>;
645
643
 
646
- // Adapter to normalize Cloudscape CustomEvent to our NavigationFollowEvent
644
+ // Adapter to normalize NavigationFollowEvent
647
645
  const handleNavigationFollow = onNavigationFollow
648
646
  ? (event: NavigationFollowEvent) => {
649
647
  onNavigationFollow(event as CompatibleNavigationFollowEvent);
@@ -27,6 +27,6 @@ export type {
27
27
  export { CreateButtonGroup } from "../ui/create-button-group"
28
28
  export type { CreateButtonGroupProps } from "../ui/create-button-group"
29
29
 
30
- // Actions dropdown (replaces Cloudscape ButtonDropdown)
30
+ // Actions dropdown
31
31
  export { ActionsDropdown } from "../ui/actions-dropdown"
32
32
  export type { ActionsDropdownProps, ActionsDropdownItem } from "../ui/actions-dropdown"
@@ -2,7 +2,7 @@ import { useState, useMemo, type MouseEvent } from "react";
2
2
 
3
3
  import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
4
4
 
5
- // Custom types to replace Cloudscape ButtonDropdownProps
5
+ // Menu dropdown types
6
6
  export interface MenuDropdownItem {
7
7
  id: string;
8
8
  text: string;
@@ -26,7 +26,6 @@ export interface AppBreadcrumbProps {
26
26
 
27
27
  /**
28
28
  * AppBreadcrumb - A wrapper around reui Breadcrumb components
29
- * that provides a similar API to Cloudscape's BreadcrumbGroup
30
29
  */
31
30
  export function AppBreadcrumb({ items, onFollow, className }: AppBreadcrumbProps) {
32
31
  if (!items || items.length === 0) {
@@ -54,7 +54,7 @@ const HEADER_HEIGHT = 56;
54
54
  const SERVICE_BAR_HEIGHT = 56;
55
55
 
56
56
  /**
57
- * AppLayout - A custom layout component replacing Cloudscape's AppLayoutToolbar.
57
+ * AppLayout - A custom layout component for platform services.
58
58
  *
59
59
  * Layout structure:
60
60
  * ┌──────────────────────────────────────────────────────────────────────┐
@@ -100,8 +100,8 @@ export interface AppNavigationProps {
100
100
  }
101
101
 
102
102
  /**
103
- * AppNavigation - A navigation component that provides Cloudscape-compatible API
104
- * using Tailwind styling. Designed to work within AppLayout's navigation slot.
103
+ * AppNavigation - A navigation component using Tailwind styling.
104
+ * Designed to work within AppLayout's navigation slot.
105
105
  */
106
106
  export function AppNavigation({
107
107
  serviceName,
@@ -35,8 +35,7 @@ export interface AppSidebarProps {
35
35
  }
36
36
 
37
37
  /**
38
- * AppSidebar - A sidebar wrapper that provides Cloudscape-compatible API
39
- * using shadcn Sidebar components internally.
38
+ * AppSidebar - A sidebar wrapper using shadcn Sidebar components internally.
40
39
  */
41
40
  export function AppSidebar({
42
41
  serviceName,
@@ -1,8 +1,8 @@
1
1
  import * as React from "react";
2
2
  import * as Popover from "@radix-ui/react-popover";
3
- import Badge from "@cloudscape-design/components/badge";
4
- import Button from "@cloudscape-design/components/button";
5
- import SpaceBetween from "@cloudscape-design/components/space-between";
3
+ import { Badge } from "../ui/badge";
4
+ import { Button } from "../ui/button";
5
+ import { SpaceBetween } from "../ui/space-between";
6
6
  import { format, isToday, isTomorrow, isValid, parseISO } from "date-fns";
7
7
  import {
8
8
  DateSelector,
@@ -69,7 +69,7 @@ function ExampleTable({ sections }: { sections: Section<Deal>[] }) {
69
69
  - `type`:
70
70
  - `text`: plain string rendering.
71
71
  - `currency`: localized currency formatting.
72
- - `badge`: Cloudscape badge with optional `badgeColorMap`.
72
+ - `badge`: badge with optional `badgeColorMap`.
73
73
  - `avatar`: initials avatar from string field.
74
74
  - `editable`: defaults to editable for `text`; set `false` to lock.
75
75
 
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import Badge from "@cloudscape-design/components/badge";
2
+ import { Badge } from "../ui/badge";
3
3
  import type { Assignee, ColumnDefinition, TableBadgeColor } from "./types";
4
4
  import { AssigneeSelector } from "@/components/ui/assignee-selector";
5
5
 
@@ -43,7 +43,6 @@ export interface ActionsDropdownProps {
43
43
  /**
44
44
  * ActionsDropdown - A dropdown menu for action buttons.
45
45
  *
46
- * This component replaces Cloudscape's ButtonDropdown with our design system.
47
46
  * Use it for action menus like "Actions", "More options", etc.
48
47
  *
49
48
  * @example
@@ -0,0 +1,59 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { Slot } from "radix-ui"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const badgeVariants = cva(
8
+ "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
13
+ secondary:
14
+ "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
15
+ destructive:
16
+ "bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
17
+ outline:
18
+ "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
19
+ ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 [a&]:hover:underline",
21
+ },
22
+ // Color variants for status indicators
23
+ color: {
24
+ blue: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200",
25
+ green: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
26
+ grey: "bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200",
27
+ red: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: "default",
32
+ },
33
+ }
34
+ )
35
+
36
+ function Badge({
37
+ className,
38
+ variant,
39
+ color,
40
+ asChild = false,
41
+ ...props
42
+ }: React.ComponentProps<"span"> &
43
+ VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
44
+ const Comp = asChild ? Slot.Root : "span"
45
+
46
+ // If color is provided, use color variant; otherwise use variant (defaulting to "default")
47
+ const resolvedVariant = color ? undefined : (variant ?? "default")
48
+
49
+ return (
50
+ <Comp
51
+ data-slot="badge"
52
+ data-variant={color ?? variant}
53
+ className={cn(badgeVariants({ variant: resolvedVariant, color }), className)}
54
+ {...props}
55
+ />
56
+ )
57
+ }
58
+
59
+ export { Badge, badgeVariants }