This is an automated email from the ASF dual-hosted git repository.

spacemonkd pushed a commit to branch HDDS-11541
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/HDDS-11541 by this push:
     new d07c8accdb9 HDDS-14520. Add shared components for service UIs (#10787)
d07c8accdb9 is described below

commit d07c8accdb93512cab350cd6f0eb4ccd6848e275
Author: Abhishek Pal <[email protected]>
AuthorDate: Mon Jul 20 21:28:36 2026 +0530

    HDDS-14520. Add shared components for service UIs (#10787)
---
 hadoop-ozone/dev-support/checks/lint.sh            |   6 +-
 ozone-ui/{src => }/.prettierignore                 |   0
 ozone-ui/README.md                                 | 206 ++++++++++++------
 ozone-ui/{src => }/eslint.config.js                |   2 +-
 ozone-ui/{src => }/package.json                    |  18 +-
 ozone-ui/{src => }/packages/om/index.html          |   0
 ozone-ui/{src => }/packages/om/package.json        |   4 +-
 ozone-ui/{src => }/packages/om/src/App.css         |   0
 ozone-ui/{src => }/packages/om/src/App.tsx         |   0
 ozone-ui/{src => }/packages/om/src/index.css       |   0
 .../{src/packages/scm => packages/om}/src/main.tsx |   8 +-
 ozone-ui/{src => }/packages/om/tsconfig.json       |   0
 ozone-ui/{src => }/packages/om/vite.config.ts      |   2 +-
 ozone-ui/{src => }/packages/recon/index.html       |   0
 .../packages/scm => packages/recon}/package.json   |   4 +-
 ozone-ui/{src => }/packages/recon/src/App.css      |   0
 ozone-ui/{src => }/packages/recon/src/App.tsx      |  22 +-
 ozone-ui/{src => }/packages/recon/src/index.css    |   0
 ozone-ui/{src => }/packages/recon/src/main.tsx     |   8 +-
 .../{src => }/packages/recon/src/vite-env.d.ts     |   0
 ozone-ui/{src => }/packages/recon/tsconfig.json    |   0
 ozone-ui/{src => }/packages/recon/vite.config.ts   |   2 +-
 ozone-ui/{src => }/packages/scm/index.html         |   0
 .../packages/recon => packages/scm}/package.json   |   4 +-
 ozone-ui/{src => }/packages/scm/src/App.css        |   0
 ozone-ui/{src => }/packages/scm/src/App.tsx        |   0
 ozone-ui/{src => }/packages/scm/src/index.css      |   0
 .../{src/packages/om => packages/scm}/src/main.tsx |   8 +-
 ozone-ui/{src => }/packages/scm/tsconfig.json      |   0
 ozone-ui/{src => }/packages/scm/vite.config.ts     |   2 +-
 .../{src => }/packages/shared/icons/favicon.ico    | Bin
 ozone-ui/{src => }/packages/shared/package.json    |   6 +-
 .../shared/src/components/Alert/Alert.tsx}         |  24 ++-
 .../shared/src/components/AppLayout/AppLayout.tsx  | 109 ++++++++++
 .../packages/shared/src/components/Card/Card.tsx   | 108 ++++++++++
 .../packages/shared/src/components/Chip/Chip.tsx   | 143 +++++++++++++
 .../packages/shared/src/components/Icon/Icon.tsx   | 230 +++++++++++++++++++++
 .../src/components/IconButton/IconButton.tsx       |  70 +++++++
 .../src/components/KeyValuePair/KeyValuePair.tsx   | 109 ++++++++++
 .../src/components/PageHeader/PageHeader.tsx       |  99 +++++++++
 .../shared/src/components/Sidebar/Sidebar.tsx      | 119 +++++++++++
 .../shared/src/components/TextLink/TextLink.tsx    |  66 ++++++
 .../src/components/UtilityBar/UtilityBar.tsx       |  93 +++++++++
 ozone-ui/packages/shared/src/index.ts              |  47 +++++
 .../packages/shared/src/theme/ThemeProvider.tsx    |  50 +++++
 ozone-ui/packages/shared/src/theme/antdTheme.ts    | 128 ++++++++++++
 .../src => packages/shared/src/theme}/index.ts     |   8 +-
 ozone-ui/packages/shared/src/theme/tokens.ts       | 211 +++++++++++++++++++
 .../packages/shared/src/utils/menuUtils.ts         |  16 ++
 ozone-ui/{src => }/packages/shared/tsconfig.json   |   0
 ozone-ui/{src => }/pnpm-lock.yaml                  |  12 +-
 ozone-ui/{src => }/pnpm-workspace.yaml             |   0
 ozone-ui/src/.gitignore                            |   2 -
 .../shared/src/components/Sidebar/Sidebar.tsx      | 127 ------------
 ozone-ui/src/packages/shared/utils/menuUtils.tsx   |  17 --
 ozone-ui/{src => }/tsconfig.json                   |   0
 ozone-ui/{src => }/vite.config.shared.ts           |   0
 57 files changed, 1820 insertions(+), 270 deletions(-)

diff --git a/hadoop-ozone/dev-support/checks/lint.sh 
b/hadoop-ozone/dev-support/checks/lint.sh
index 7902058452d..a573bc2d082 100755
--- a/hadoop-ozone/dev-support/checks/lint.sh
+++ b/hadoop-ozone/dev-support/checks/lint.sh
@@ -24,9 +24,9 @@ REPORT_FILE="$REPORT_DIR/summary.txt"
 
 declare -i rc
 
-if [[ -d "${BASE_DIR}/ozone-ui/src" ]]; then
+if [[ -d "${BASE_DIR}/ozone-ui" ]]; then
   (
-    cd "${BASE_DIR}/ozone-ui/src" || exit 1
+    cd "${BASE_DIR}/ozone-ui" || exit 1
     # Install dependencies if node_modules doesn't exist
     if [[ ! -d "node_modules" ]]; then
       pnpm install --frozen-lockfile
@@ -57,7 +57,7 @@ if [[ -d "${BASE_DIR}/ozone-ui/src" ]]; then
   fi
 else
   rc=0
-  echo "ozone-ui/src not found. Skipping UI lint."
+  echo "ozone-ui not found. Skipping UI lint."
 fi
 
 ERROR_PATTERN="error"
diff --git a/ozone-ui/src/.prettierignore b/ozone-ui/.prettierignore
similarity index 100%
rename from ozone-ui/src/.prettierignore
rename to ozone-ui/.prettierignore
diff --git a/ozone-ui/README.md b/ozone-ui/README.md
index 54b071673e4..33f2e9e9169 100644
--- a/ozone-ui/README.md
+++ b/ozone-ui/README.md
@@ -17,99 +17,177 @@
 
 # Ozone UI Monorepo
 
-This monorepo contains three React applications for Apache Ozone UI components:
+A Vite + React 18 + TypeScript monorepo (managed with **pnpm workspaces**) that
+hosts the Apache Ozone web applications and a shared component library. Styling
+is built on **Ant Design v5** themed with the Ozone UI design tokens.
 
-## Applications
+## Applications & packages
 
-- **Recon** - Data node management and monitoring
-- **SCM** - Storage Container Manager interface  
-- **OM** - Ozone Manager interface
+| Package                 | Directory         | Description                    
                |
+| ----------------------- | ----------------- | 
---------------------------------------------- |
+| `@ozone-ui/shared`      | `packages/shared` | Design system: theme + 
reusable components     |
+| `@ozone-ui/ozone-recon` | `packages/recon`  | Recon – datanode management 
and monitoring     |
+| `@ozone-ui/ozone-scm`   | `packages/scm`    | SCM – Storage Container 
Manager interface      |
+| `@ozone-ui/ozone-om`    | `packages/om`     | OM – Ozone Manager interface   
                |
 
-## Structure
+## Folder layout
 
 ```
-hadoop-ui/src/
-├── packages/
-│   ├── shared/           # Shared components and utilities
-│   ├── recon/           # Recon application
-│   ├── scm/             # SCM application
-│   └── om/              # OM application
-├── package.json         # Root package.json with workspace configuration
-└── pnpm-workspace.yaml  # PNPM workspace configuration
+ozone-ui/                         # pnpm workspace root
+├── README.md
+├── package.json                  # Root scripts + shared dev dependencies
+├── pnpm-workspace.yaml           # Workspace globs (./packages/**)
+├── pnpm-lock.yaml
+├── tsconfig.json                 # Base TS config, extended by each package
+├── vite.config.shared.ts         # Shared Vite config helpers for the apps
+├── eslint.config.js              # Flat ESLint config
+└── packages/
+    ├── shared/                   # @ozone-ui/shared (design system)
+    │   └── src/
+    │       ├── theme/            # tokens, Ant Design theme, ThemeProvider
+    │       ├── components/       # Sidebar, UtilityBar, PageHeader, Card, ...
+    │       ├── utils/            # menuUtils, ...
+    │       └── index.ts          # Public entry point (barrel)
+    ├── recon/                    # @ozone-ui/ozone-recon (Vite app)
+    ├── scm/                      # @ozone-ui/ozone-scm   (Vite app)
+    └── om/                       # @ozone-ui/ozone-om    (Vite app)
 ```
 
-## Development
+## Prerequisites
 
-### Prerequisites
+- Node.js `>= 20` (Node 20 LTS recommended)
+- pnpm `>= 8.15.7` (`corepack enable` provides the pinned version)
 
-- Node.js >= 20.0.0 (Node 20 LTS recommended)
-- PNPM >= 8.0.0
-
-### Installation
+## Install
 
 ```bash
-# Install all dependencies
-pnpm install
+cd ozone-ui
+pnpm install          # installs all workspace dependencies
 ```
 
-### Development
+## Develop
+
+The `shared` package is consumed as a built artifact, so build it once (and
+after any change to it) before/while running an app:
 
 ```bash
-# Start development server for a specific app
-pnpm dev:recon
-pnpm dev:scm
-pnpm dev:om
+cd ozone-ui
+
+pnpm build:shared      # compile @ozone-ui/shared -> packages/shared/dist
 
-# Build shared components (run this first if you make changes to shared)
-pnpm build:shared
+pnpm dev:recon         # start the Recon app dev server
+pnpm dev:scm           # start the SCM app dev server
+pnpm dev:om            # start the OM app dev server
 ```
 
-### Building
+## Build
 
 ```bash
-# Build all applications
-pnpm build
+cd ozone-ui
 
-# Build specific application
-pnpm build:recon
+pnpm build             # build shared, then all three apps
+pnpm build:recon       # build a single app
 pnpm build:scm
 pnpm build:om
-
-# Build only shared components
-pnpm build:shared
+pnpm build:shared      # build only the shared library
 ```
 
-Build outputs are placed in:
-- `build/recon/` - Recon application build
-- `build/scm/` - SCM application build  
-- `build/om/` - OM application build
+Application build output is written to `build/{recon,scm,om}/`.
 
-### Clean
+## Lint & clean
 
 ```bash
-# Clean all build artifacts and node_modules
-pnpm clean
+pnpm lint              # ESLint across the workspace
+pnpm clean             # remove build/ and all dist/ + node_modules
+pnpm clean:cache       # clear Vite caches
+pnpm clean:all         # clean + clean:cache
 ```
 
-## Architecture
-
-### Shared Components
-
-The `@hadoop-ui/shared` package contains:
-
-- **Components**: Reusable React components (e.g., Sidebar)
-- **Utils**: Shared utility functions (e.g., menu utilities)
-- **Types**: TypeScript type definitions
-
-### Individual Applications
-
-Each application (`recon`, `scm`, `om`) is a standalone Vite + React + 
TypeScript application that can import from the shared package.
-
-## Technology Stack
+## Using the design system
+
+Each app mounts the theme once near its root (already wired in
+`packages/{om,scm,recon}/src/main.tsx`), then consumes shared components and
+tokens from `@ozone-ui/shared`:
+
+```tsx
+import { BrowserRouter } from 'react-router-dom';
+import {
+  ThemeProvider,
+  AppLayout,
+  Sidebar,
+  PageHeader,
+  Card,
+  KeyValuePair,
+  Chip,
+} from '@ozone-ui/shared';
+import '@fontsource/roboto/400.css';
+import '@fontsource/roboto/500.css';
+import '@fontsource/roboto/700.css';
+
+export default function App() {
+  return (
+    <BrowserRouter>
+      <ThemeProvider>
+        <AppLayout
+          sider={
+            <Sidebar
+              logo={<span style={{ color: '#fff', padding: 12 }}>Ozone</span>}
+              items={[{ key: 'overview', label: 'Overview', path: '/' }]}
+            />
+          }
+        >
+          <PageHeader title="Datanodes" subtitle="12 healthy" />
+          <Card
+            title="Instance details"
+            emphasis="elevated"
+            collapsible
+            extra={<Chip color="green" variant="dot">Healthy</Chip>}
+          >
+            <KeyValuePair label="Hostname" value="dn-01.ozone.local" />
+            <KeyValuePair label="UUID" value="a1b2c3" copyable />
+          </Card>
+        </AppLayout>
+      </ThemeProvider>
+    </BrowserRouter>
+  );
+}
+```
 
-- **Build Tool**: Vite
+> `Sidebar` is router-aware, so render it within a `react-router-dom` context
+> (e.g. `BrowserRouter`); it highlights the active item and navigates on 
select.
+
+### What's in `@ozone-ui/shared`
+
+- **`theme/`**
+  - `colors`, `semanticColors`, `textStyles`, `fontFamilies`, `spacing`,
+    `radius` — design tokens (source of truth for colour and typography).
+  - `ozoneTheme` — an Ant Design v5 `ThemeConfig` derived from the tokens.
+  - `ThemeProvider` — wraps `ConfigProvider` with the theme and accepts 
optional
+    per-app `themeOverrides`.
+- **`components/`** (derived from the components recurring across the mockups)
+  - `UtilityBar` — global top bar (leading/title, centre, actions).
+  - `Sidebar` — collapsible, router-aware navigation rail driven by `items`
+    (with `path`s) and `logo` props; integrates with `react-router-dom`.
+  - `AppLayout` — page shell (sider + header + content).
+  - `PageHeader` — page title with breadcrumb, subtitle and actions.
+  - `Card` — surface with `outlined`/`elevated`/`filled` emphasis and an
+    optional `collapsible` header.
+  - `KeyValuePair` — label/value pair (vertical or horizontal, optional 
link/copy).
+  - `Chip` — pill: `full`/`dot` variant, `standard`/`small` size, colour and
+    `selected`/`closable` states.
+  - `Alert` — inline status banner (info/success/warning/error).
+  - `TextLink` — themed inline link with optional external affordance.
+  - `IconButton` — square icon-only button with accessible label + tooltip.
+  - `Icon` — inline-SVG icon set (`currentColor`, tree-shakeable).
+
+Prefer the tokens/theme over hard-coded colours or font sizes so re-theming
+stays centralised.
+
+## Technology stack
+
+- **Build**: Vite 5 (apps), `tsc` (shared library)
 - **Framework**: React 18
-- **Language**: TypeScript
-- **UI Library**: Ant Design v5
-- **Package Manager**: PNPM (with workspaces)
-- **Monorepo**: PNPM Workspaces 
\ No newline at end of file
+- **Language**: TypeScript 5.6
+- **UI**: Ant Design v5, themed with the Ozone UI design tokens
+- **Fonts**: Roboto / Roboto Mono (`@fontsource/roboto`), Plus Jakarta Sans 
(app titles)
+- **Package manager / monorepo**: pnpm workspaces
diff --git a/ozone-ui/src/eslint.config.js b/ozone-ui/eslint.config.js
similarity index 98%
rename from ozone-ui/src/eslint.config.js
rename to ozone-ui/eslint.config.js
index 295c32b520a..195fd5d4eee 100644
--- a/ozone-ui/src/eslint.config.js
+++ b/ozone-ui/eslint.config.js
@@ -24,7 +24,7 @@ const compat = new FlatCompat({
 
 module.exports = [
   {
-    ignores: ['dist', 'build', 'node_modules'],
+    ignores: ['**/dist/**', '**/build/**', '**/node_modules/**'],
   },
   ...compat.config({
     env: {
diff --git a/ozone-ui/src/package.json b/ozone-ui/package.json
similarity index 75%
rename from ozone-ui/src/package.json
rename to ozone-ui/package.json
index 7c0aec7a04d..673eccd9db7 100644
--- a/ozone-ui/src/package.json
+++ b/ozone-ui/package.json
@@ -1,18 +1,18 @@
 {
   "packageManager": "[email protected]",
-  "name": "hadoop-ui",
+  "name": "ozone-ui",
   "version": "1.0.0",
   "description": "The home of HDDS UI code.",
   "scripts": {
     "build": "pnpm run build:shared && pnpm run build:all",
-    "build:shared": "pnpm --filter @hadoop-ui/shared run build",
-    "build:all": "pnpm --filter @hadoop-ui/ozone-recon --filter 
@hadoop-ui/ozone-scm --filter @hadoop-ui/ozone-om run build",
-    "build:recon": "pnpm --filter @hadoop-ui/ozone-recon run build",
-    "build:scm": "pnpm --filter @hadoop-ui/ozone-scm run build",
-    "build:om": "pnpm --filter @hadoop-ui/ozone-om run build",
-    "dev:recon": "pnpm --filter @hadoop-ui/ozone-recon run dev",
-    "dev:scm": "pnpm --filter @hadoop-ui/ozone-scm run dev",
-    "dev:om": "pnpm --filter @hadoop-ui/ozone-om run dev",
+    "build:shared": "pnpm --filter @ozone-ui/shared run build",
+    "build:all": "pnpm --filter @ozone-ui/ozone-recon --filter 
@ozone-ui/ozone-scm --filter @ozone-ui/ozone-om run build",
+    "build:recon": "pnpm --filter @ozone-ui/ozone-recon run build",
+    "build:scm": "pnpm --filter @ozone-ui/ozone-scm run build",
+    "build:om": "pnpm --filter @ozone-ui/ozone-om run build",
+    "dev:recon": "pnpm --filter @ozone-ui/ozone-recon run dev",
+    "dev:scm": "pnpm --filter @ozone-ui/ozone-scm run dev",
+    "dev:om": "pnpm --filter @ozone-ui/ozone-om run dev",
     "clean": "rm -rf build && pnpm -r exec rm -rf dist node_modules",
     "clean:cache": "rm -rf node_modules/.vite && pnpm -r exec rm -rf 
node_modules/.vite",
     "clean:all": "pnpm run clean && pnpm run clean:cache",
diff --git a/ozone-ui/src/packages/om/index.html 
b/ozone-ui/packages/om/index.html
similarity index 100%
rename from ozone-ui/src/packages/om/index.html
rename to ozone-ui/packages/om/index.html
diff --git a/ozone-ui/src/packages/om/package.json 
b/ozone-ui/packages/om/package.json
similarity index 94%
rename from ozone-ui/src/packages/om/package.json
rename to ozone-ui/packages/om/package.json
index 03028162343..06f5991be53 100644
--- a/ozone-ui/src/packages/om/package.json
+++ b/ozone-ui/packages/om/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "@hadoop-ui/ozone-om",
+  "name": "@ozone-ui/ozone-om",
   "private": true,
   "version": "1.0.0",
   "scripts": {
@@ -8,7 +8,7 @@
     "lint": "eslint ."
   },
   "dependencies": {
-    "@hadoop-ui/shared": "workspace:*",
+    "@ozone-ui/shared": "workspace:*",
     "@ant-design/icons": "^5.6.1",
     "@fontsource/roboto": "^4.5.8",
     "ag-charts-community": "^7.3.0",
diff --git a/ozone-ui/src/packages/om/src/App.css 
b/ozone-ui/packages/om/src/App.css
similarity index 100%
rename from ozone-ui/src/packages/om/src/App.css
rename to ozone-ui/packages/om/src/App.css
diff --git a/ozone-ui/src/packages/om/src/App.tsx 
b/ozone-ui/packages/om/src/App.tsx
similarity index 100%
rename from ozone-ui/src/packages/om/src/App.tsx
rename to ozone-ui/packages/om/src/App.tsx
diff --git a/ozone-ui/src/packages/om/src/index.css 
b/ozone-ui/packages/om/src/index.css
similarity index 100%
rename from ozone-ui/src/packages/om/src/index.css
rename to ozone-ui/packages/om/src/index.css
diff --git a/ozone-ui/src/packages/scm/src/main.tsx 
b/ozone-ui/packages/om/src/main.tsx
similarity index 82%
rename from ozone-ui/src/packages/scm/src/main.tsx
rename to ozone-ui/packages/om/src/main.tsx
index e5de33f5d7b..e4b310bf134 100644
--- a/ozone-ui/src/packages/scm/src/main.tsx
+++ b/ozone-ui/packages/om/src/main.tsx
@@ -17,11 +17,17 @@
  */
 import { StrictMode } from 'react';
 import { createRoot } from 'react-dom/client';
+import { ThemeProvider } from '@ozone-ui/shared';
+import '@fontsource/roboto/400.css';
+import '@fontsource/roboto/500.css';
+import '@fontsource/roboto/700.css';
 import App from './App';
 import './index.css';
 
 createRoot(document.getElementById('root')!).render(
   <StrictMode>
-    <App />
+    <ThemeProvider>
+      <App />
+    </ThemeProvider>
   </StrictMode>
 );
diff --git a/ozone-ui/src/packages/om/tsconfig.json 
b/ozone-ui/packages/om/tsconfig.json
similarity index 100%
rename from ozone-ui/src/packages/om/tsconfig.json
rename to ozone-ui/packages/om/tsconfig.json
diff --git a/ozone-ui/src/packages/om/vite.config.ts 
b/ozone-ui/packages/om/vite.config.ts
similarity index 98%
rename from ozone-ui/src/packages/om/vite.config.ts
rename to ozone-ui/packages/om/vite.config.ts
index 3130d4398cc..43accb31800 100644
--- a/ozone-ui/src/packages/om/vite.config.ts
+++ b/ozone-ui/packages/om/vite.config.ts
@@ -35,7 +35,7 @@ export default defineConfig({
   ],
   build: {
     target: 'es2015',
-    outDir: '../../../build/om',
+    outDir: '../../build/om',
     rollupOptions: {
       output: {
         chunkFileNames: 'static/js/[name]-[hash].js',
diff --git a/ozone-ui/src/packages/recon/index.html 
b/ozone-ui/packages/recon/index.html
similarity index 100%
rename from ozone-ui/src/packages/recon/index.html
rename to ozone-ui/packages/recon/index.html
diff --git a/ozone-ui/src/packages/scm/package.json 
b/ozone-ui/packages/recon/package.json
similarity index 95%
rename from ozone-ui/src/packages/scm/package.json
rename to ozone-ui/packages/recon/package.json
index 5a720d64d44..560bf596b09 100644
--- a/ozone-ui/src/packages/scm/package.json
+++ b/ozone-ui/packages/recon/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "@hadoop-ui/ozone-scm",
+  "name": "@ozone-ui/ozone-recon",
   "private": true,
   "version": "1.0.0",
   "scripts": {
@@ -8,7 +8,7 @@
     "lint": "eslint ."
   },
   "dependencies": {
-    "@hadoop-ui/shared": "workspace:*",
+    "@ozone-ui/shared": "workspace:*",
     "@ant-design/icons": "^5.6.1",
     "@fontsource/roboto": "^4.5.8",
     "ag-charts-community": "^7.3.0",
diff --git a/ozone-ui/src/packages/recon/src/App.css 
b/ozone-ui/packages/recon/src/App.css
similarity index 100%
rename from ozone-ui/src/packages/recon/src/App.css
rename to ozone-ui/packages/recon/src/App.css
diff --git a/ozone-ui/src/packages/recon/src/App.tsx 
b/ozone-ui/packages/recon/src/App.tsx
similarity index 63%
rename from ozone-ui/src/packages/recon/src/App.tsx
rename to ozone-ui/packages/recon/src/App.tsx
index c107a41b64b..e26483204c0 100644
--- a/ozone-ui/src/packages/recon/src/App.tsx
+++ b/ozone-ui/packages/recon/src/App.tsx
@@ -16,32 +16,24 @@
  * limitations under the License.
  */
 import { useState } from 'react';
-import reactLogo from './assets/react.svg';
-import viteLogo from '/vite.svg';
+import { Button } from 'antd';
 import './App.css';
 
 function App() {
   const [count, setCount] = useState(0);
 
   return (
-    <>
-      <div>
-        <a href="https://vite.dev"; target="_blank" rel="noreferrer">
-          <img src={viteLogo} className="logo" alt="Vite logo" />
-        </a>
-        <a href="https://react.dev"; target="_blank" rel="noreferrer">
-          <img src={reactLogo} className="logo react" alt="React logo" />
-        </a>
-      </div>
-      <h1>Vite + React</h1>
+    <div className="App">
+      <h1>Ozone Recon</h1>
       <div className="card">
-        <button onClick={() => setCount((count) => count + 1)}>count is 
{count}</button>
+        <Button type="primary" onClick={() => setCount((count) => count + 1)}>
+          Count is {count}
+        </Button>
         <p>
           Edit <code>src/App.tsx</code> and save to test HMR
         </p>
       </div>
-      <p className="read-the-docs">Click on the Vite and React logos to learn 
more</p>
-    </>
+    </div>
   );
 }
 
diff --git a/ozone-ui/src/packages/recon/src/index.css 
b/ozone-ui/packages/recon/src/index.css
similarity index 100%
rename from ozone-ui/src/packages/recon/src/index.css
rename to ozone-ui/packages/recon/src/index.css
diff --git a/ozone-ui/src/packages/recon/src/main.tsx 
b/ozone-ui/packages/recon/src/main.tsx
similarity index 82%
rename from ozone-ui/src/packages/recon/src/main.tsx
rename to ozone-ui/packages/recon/src/main.tsx
index 1c6bbc3b61e..814e0415e53 100644
--- a/ozone-ui/src/packages/recon/src/main.tsx
+++ b/ozone-ui/packages/recon/src/main.tsx
@@ -17,11 +17,17 @@
  */
 import { StrictMode } from 'react';
 import { createRoot } from 'react-dom/client';
+import { ThemeProvider } from '@ozone-ui/shared';
+import '@fontsource/roboto/400.css';
+import '@fontsource/roboto/500.css';
+import '@fontsource/roboto/700.css';
 import './index.css';
 import App from './App';
 
 createRoot(document.getElementById('root')!).render(
   <StrictMode>
-    <App />
+    <ThemeProvider>
+      <App />
+    </ThemeProvider>
   </StrictMode>
 );
diff --git a/ozone-ui/src/packages/recon/src/vite-env.d.ts 
b/ozone-ui/packages/recon/src/vite-env.d.ts
similarity index 100%
rename from ozone-ui/src/packages/recon/src/vite-env.d.ts
rename to ozone-ui/packages/recon/src/vite-env.d.ts
diff --git a/ozone-ui/src/packages/recon/tsconfig.json 
b/ozone-ui/packages/recon/tsconfig.json
similarity index 100%
rename from ozone-ui/src/packages/recon/tsconfig.json
rename to ozone-ui/packages/recon/tsconfig.json
diff --git a/ozone-ui/src/packages/recon/vite.config.ts 
b/ozone-ui/packages/recon/vite.config.ts
similarity index 98%
rename from ozone-ui/src/packages/recon/vite.config.ts
rename to ozone-ui/packages/recon/vite.config.ts
index 9726123e879..d0ed72b742f 100644
--- a/ozone-ui/src/packages/recon/vite.config.ts
+++ b/ozone-ui/packages/recon/vite.config.ts
@@ -35,7 +35,7 @@ export default defineConfig({
   ],
   build: {
     target: 'es2015',
-    outDir: '../../../build/recon',
+    outDir: '../../build/recon',
     rollupOptions: {
       output: {
         chunkFileNames: 'static/js/[name]-[hash].js',
diff --git a/ozone-ui/src/packages/scm/index.html 
b/ozone-ui/packages/scm/index.html
similarity index 100%
rename from ozone-ui/src/packages/scm/index.html
rename to ozone-ui/packages/scm/index.html
diff --git a/ozone-ui/src/packages/recon/package.json 
b/ozone-ui/packages/scm/package.json
similarity index 94%
rename from ozone-ui/src/packages/recon/package.json
rename to ozone-ui/packages/scm/package.json
index 653da3d0245..d07ab0bbb4c 100644
--- a/ozone-ui/src/packages/recon/package.json
+++ b/ozone-ui/packages/scm/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "@hadoop-ui/ozone-recon",
+  "name": "@ozone-ui/ozone-scm",
   "private": true,
   "version": "1.0.0",
   "scripts": {
@@ -8,7 +8,7 @@
     "lint": "eslint ."
   },
   "dependencies": {
-    "@hadoop-ui/shared": "workspace:*",
+    "@ozone-ui/shared": "workspace:*",
     "@ant-design/icons": "^5.6.1",
     "@fontsource/roboto": "^4.5.8",
     "ag-charts-community": "^7.3.0",
diff --git a/ozone-ui/src/packages/scm/src/App.css 
b/ozone-ui/packages/scm/src/App.css
similarity index 100%
rename from ozone-ui/src/packages/scm/src/App.css
rename to ozone-ui/packages/scm/src/App.css
diff --git a/ozone-ui/src/packages/scm/src/App.tsx 
b/ozone-ui/packages/scm/src/App.tsx
similarity index 100%
rename from ozone-ui/src/packages/scm/src/App.tsx
rename to ozone-ui/packages/scm/src/App.tsx
diff --git a/ozone-ui/src/packages/scm/src/index.css 
b/ozone-ui/packages/scm/src/index.css
similarity index 100%
rename from ozone-ui/src/packages/scm/src/index.css
rename to ozone-ui/packages/scm/src/index.css
diff --git a/ozone-ui/src/packages/om/src/main.tsx 
b/ozone-ui/packages/scm/src/main.tsx
similarity index 82%
copy from ozone-ui/src/packages/om/src/main.tsx
copy to ozone-ui/packages/scm/src/main.tsx
index e5de33f5d7b..e4b310bf134 100644
--- a/ozone-ui/src/packages/om/src/main.tsx
+++ b/ozone-ui/packages/scm/src/main.tsx
@@ -17,11 +17,17 @@
  */
 import { StrictMode } from 'react';
 import { createRoot } from 'react-dom/client';
+import { ThemeProvider } from '@ozone-ui/shared';
+import '@fontsource/roboto/400.css';
+import '@fontsource/roboto/500.css';
+import '@fontsource/roboto/700.css';
 import App from './App';
 import './index.css';
 
 createRoot(document.getElementById('root')!).render(
   <StrictMode>
-    <App />
+    <ThemeProvider>
+      <App />
+    </ThemeProvider>
   </StrictMode>
 );
diff --git a/ozone-ui/src/packages/scm/tsconfig.json 
b/ozone-ui/packages/scm/tsconfig.json
similarity index 100%
rename from ozone-ui/src/packages/scm/tsconfig.json
rename to ozone-ui/packages/scm/tsconfig.json
diff --git a/ozone-ui/src/packages/scm/vite.config.ts 
b/ozone-ui/packages/scm/vite.config.ts
similarity index 98%
rename from ozone-ui/src/packages/scm/vite.config.ts
rename to ozone-ui/packages/scm/vite.config.ts
index b817af1296f..a1b7859a7d2 100644
--- a/ozone-ui/src/packages/scm/vite.config.ts
+++ b/ozone-ui/packages/scm/vite.config.ts
@@ -35,7 +35,7 @@ export default defineConfig({
   ],
   build: {
     target: 'es2015',
-    outDir: '../../../build/scm',
+    outDir: '../../build/scm',
     rollupOptions: {
       output: {
         chunkFileNames: 'static/js/[name]-[hash].js',
diff --git a/ozone-ui/src/packages/shared/icons/favicon.ico 
b/ozone-ui/packages/shared/icons/favicon.ico
similarity index 100%
rename from ozone-ui/src/packages/shared/icons/favicon.ico
rename to ozone-ui/packages/shared/icons/favicon.ico
diff --git a/ozone-ui/src/packages/shared/package.json 
b/ozone-ui/packages/shared/package.json
similarity index 82%
rename from ozone-ui/src/packages/shared/package.json
rename to ozone-ui/packages/shared/package.json
index a8f720c1f29..2d400d66f30 100644
--- a/ozone-ui/src/packages/shared/package.json
+++ b/ozone-ui/packages/shared/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "@hadoop-ui/shared",
+  "name": "@ozone-ui/shared",
   "private": true,
   "version": "1.0.0",
   "type": "module",
@@ -20,7 +20,8 @@
   },
   "peerDependencies": {
     "react": "^18.3.1",
-    "react-dom": "^18.3.1"
+    "react-dom": "^18.3.1",
+    "react-router-dom": "^7.3.0"
   },
   "dependencies": {
     "@ant-design/icons": "^5.6.1",
@@ -30,6 +31,7 @@
   "devDependencies": {
     "@types/react": "^18.3.1",
     "@types/react-dom": "^18.3.1",
+    "react-router-dom": "^7.3.0",
     "typescript": "~5.6.2"
   }
 } 
\ No newline at end of file
diff --git a/ozone-ui/src/packages/om/src/main.tsx 
b/ozone-ui/packages/shared/src/components/Alert/Alert.tsx
similarity index 54%
rename from ozone-ui/src/packages/om/src/main.tsx
rename to ozone-ui/packages/shared/src/components/Alert/Alert.tsx
index e5de33f5d7b..67e1b86cd26 100644
--- a/ozone-ui/src/packages/om/src/main.tsx
+++ b/ozone-ui/packages/shared/src/components/Alert/Alert.tsx
@@ -15,13 +15,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
-import App from './App';
-import './index.css';
 
-createRoot(document.getElementById('root')!).render(
-  <StrictMode>
-    <App />
-  </StrictMode>
+import React from 'react';
+import { Alert as AntAlert, type AlertProps as AntAlertProps } from 'antd';
+import { radius } from '../../theme/tokens';
+
+export type AlertProps = AntAlertProps;
+
+/**
+ * Inline status banner. Thin wrapper over Ant Design's `Alert` applying the
+ * design-system radius; use the standard `type` (`info` / `success` /
+ * `warning` / `error`), `message`, `description`, `showIcon` and `closable`
+ * props. Matches the "Alert / Standard" component in the mockups.
+ */
+export const Alert: React.FC<AlertProps> = ({ showIcon = true, style, ...rest 
}) => (
+  <AntAlert showIcon={showIcon} style={{ borderRadius: radius.md, ...style }} 
{...rest} />
 );
+
+export default Alert;
diff --git a/ozone-ui/packages/shared/src/components/AppLayout/AppLayout.tsx 
b/ozone-ui/packages/shared/src/components/AppLayout/AppLayout.tsx
new file mode 100644
index 00000000000..8933c48e3ba
--- /dev/null
+++ b/ozone-ui/packages/shared/src/components/AppLayout/AppLayout.tsx
@@ -0,0 +1,109 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Layout, Typography } from 'antd';
+import { semanticColors, spacing, textStyles } from '../../theme/tokens';
+
+const { Header, Content } = Layout;
+
+export interface AppLayoutProps {
+  /** Navigation rail, typically the shared `Sidebar`. */
+  sider?: React.ReactNode;
+  /** Page/section title rendered in the header. */
+  title?: React.ReactNode;
+  /** Right-aligned header content (actions, user menu, breadcrumbs, ...). */
+  headerExtra?: React.ReactNode;
+  /** Main page content. */
+  children?: React.ReactNode;
+  /** Constrain content width and centre it (useful for form/detail pages). */
+  maxContentWidth?: number;
+}
+
+/**
+ * Application shell: a fixed navigation rail on the left, a top header with a
+ * title and optional actions, and a scrollable content area on the 
design-system
+ * layout background. Compose with the shared `Sidebar` for the `sider` slot.
+ */
+export const AppLayout: React.FC<AppLayoutProps> = ({
+  sider,
+  title,
+  headerExtra,
+  children,
+  maxContentWidth,
+}) => {
+  return (
+    <Layout style={{ minHeight: '100vh' }}>
+      {sider}
+      <Layout>
+        {(title || headerExtra) && (
+          <Header
+            style={{
+              display: 'flex',
+              alignItems: 'center',
+              justifyContent: 'space-between',
+              background: semanticColors.bgContainer,
+              borderBottom: `1px solid ${semanticColors.border}`,
+              paddingInline: spacing.xl,
+            }}
+          >
+            {typeof title === 'string' ? (
+              <Typography.Title
+                level={4}
+                style={{
+                  margin: 0,
+                  fontSize: textStyles.h2.fontSize,
+                  lineHeight: `${textStyles.h2.lineHeight}px`,
+                  color: semanticColors.textPrimary,
+                }}
+              >
+                {title}
+              </Typography.Title>
+            ) : (
+              title
+            )}
+            {headerExtra && (
+              <div style={{ display: 'flex', alignItems: 'center', gap: 
spacing.md }}>
+                {headerExtra}
+              </div>
+            )}
+          </Header>
+        )}
+        <Content
+          style={{
+            padding: spacing.xl,
+            background: semanticColors.bgLayout,
+            overflow: 'auto',
+          }}
+        >
+          <div
+            style={{
+              maxWidth: maxContentWidth,
+              marginInline: maxContentWidth ? 'auto' : undefined,
+              width: '100%',
+            }}
+          >
+            {children}
+          </div>
+        </Content>
+      </Layout>
+    </Layout>
+  );
+};
+
+export default AppLayout;
diff --git a/ozone-ui/packages/shared/src/components/Card/Card.tsx 
b/ozone-ui/packages/shared/src/components/Card/Card.tsx
new file mode 100644
index 00000000000..a9f9a01b597
--- /dev/null
+++ b/ozone-ui/packages/shared/src/components/Card/Card.tsx
@@ -0,0 +1,108 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState } from 'react';
+import { Button, Card as AntCard, type CardProps as AntCardProps } from 'antd';
+import { DownOutlined, UpOutlined } from '@ant-design/icons';
+import { radius, semanticColors } from '../../theme/tokens';
+
+export interface CardProps extends AntCardProps {
+  /**
+   * Visual emphasis of the card surface.
+   * - `outlined` (default): white surface with a subtle border.
+   * - `elevated`: white surface with a soft shadow and no border.
+   * - `filled`: muted pewter surface, useful for nested/secondary panels.
+   */
+  emphasis?: 'outlined' | 'elevated' | 'filled';
+  /**
+   * Enable the "Card With Header" collapse affordance: a caret toggle in the
+   * header hides/shows the card body. Requires a `title`.
+   */
+  collapsible?: boolean;
+  /** Initial collapsed state when `collapsible` (uncontrolled). */
+  defaultCollapsed?: boolean;
+}
+
+const emphasisStyles: Record<NonNullable<CardProps['emphasis']>, 
React.CSSProperties> = {
+  outlined: {
+    border: `1px solid ${semanticColors.border}`,
+    boxShadow: 'none',
+    background: semanticColors.bgContainer,
+  },
+  elevated: {
+    border: 'none',
+    boxShadow: '0 1px 2px rgba(35, 43, 48, 0.06), 0 4px 12px rgba(35, 43, 48, 
0.08)',
+    background: semanticColors.bgElevated,
+  },
+  filled: {
+    border: `1px solid ${semanticColors.border}`,
+    boxShadow: 'none',
+    background: semanticColors.bgLayout,
+  },
+};
+
+/**
+ * Surface container. Thin wrapper over Ant Design's `Card` that applies the
+ * design-system radius, borders and elevation presets, plus an optional
+ * collapsible header. All standard `Card` props are supported.
+ */
+export const Card: React.FC<CardProps> = ({
+  emphasis = 'outlined',
+  collapsible = false,
+  defaultCollapsed = false,
+  extra,
+  children,
+  style,
+  styles,
+  ...rest
+}) => {
+  const [collapsed, setCollapsed] = useState(defaultCollapsed);
+
+  const collapseToggle = collapsible ? (
+    <Button
+      type="text"
+      size="small"
+      aria-label={collapsed ? 'Expand' : 'Collapse'}
+      icon={collapsed ? <DownOutlined /> : <UpOutlined />}
+      onClick={() => setCollapsed((c) => !c)}
+    />
+  ) : null;
+
+  const mergedExtra = collapsible ? (
+    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
+      {extra}
+      {collapseToggle}
+    </span>
+  ) : (
+    extra
+  );
+
+  return (
+    <AntCard
+      variant="borderless"
+      extra={mergedExtra}
+      style={{ borderRadius: radius.lg, ...emphasisStyles[emphasis], ...style 
}}
+      styles={{ header: { border: 'none' }, ...styles }}
+      {...rest}
+    >
+      {collapsible && collapsed ? null : children}
+    </AntCard>
+  );
+};
+
+export default Card;
diff --git a/ozone-ui/packages/shared/src/components/Chip/Chip.tsx 
b/ozone-ui/packages/shared/src/components/Chip/Chip.tsx
new file mode 100644
index 00000000000..d6308608de9
--- /dev/null
+++ b/ozone-ui/packages/shared/src/components/Chip/Chip.tsx
@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Tag, type TagProps } from 'antd';
+import { colors, radius } from '../../theme/tokens';
+
+/**
+ * Chip colours. Neutral, Blue, Green and Orange are the ones used across the
+ * Ozone mockups; the rest complete the palette.
+ */
+export type ChipColor = 'neutral' | 'blue' | 'green' | 'orange' | 'red' | 
'amber';
+
+/** `full` = solid pill fill; `dot` = pill with a leading status dot. */
+export type ChipVariant = 'full' | 'dot';
+
+/** Chip sizes. */
+export type ChipSize = 'standard' | 'small';
+
+interface ChipPalette {
+  bg: string;
+  fg: string;
+  border: string;
+  dot: string;
+}
+
+const palettes: Record<ChipColor, ChipPalette> = {
+  neutral: {
+    bg: colors.pewter[50],
+    fg: colors.pewter[700],
+    border: colors.pewter[100],
+    dot: colors.pewter[500],
+  },
+  blue: {
+    bg: colors.blueNova[50],
+    fg: colors.blueNova[700],
+    border: colors.blueNova[100],
+    dot: colors.blueNova[600],
+  },
+  green: {
+    bg: colors.green[50],
+    fg: colors.green[700],
+    border: colors.green[100],
+    dot: colors.green[500],
+  },
+  orange: {
+    bg: colors.orange[50],
+    fg: colors.orange[600],
+    border: colors.orange[100],
+    dot: colors.orange[400],
+  },
+  red: { bg: colors.red[50], fg: colors.red[700], border: colors.red[100], 
dot: colors.red[500] },
+  amber: {
+    bg: colors.amber[100],
+    fg: colors.amber[700],
+    border: colors.amber[200],
+    dot: colors.amber[300],
+  },
+};
+
+const sizeStyles: Record<ChipSize, React.CSSProperties> = {
+  standard: { height: 22, paddingInline: 10, fontSize: 12, lineHeight: '20px' 
},
+  small: { height: 18, paddingInline: 8, fontSize: 11, lineHeight: '16px' },
+};
+
+export interface ChipProps extends Omit<TagProps, 'color'> {
+  /** Colour family. Defaults to `neutral`. */
+  color?: ChipColor;
+  /** `full` (solid pill) or `dot` (with a leading status dot). Defaults to 
`full`. */
+  variant?: ChipVariant;
+  /** Chip size. Defaults to `standard`. */
+  size?: ChipSize;
+  /** Selected state renders a stronger fill. */
+  selected?: boolean;
+}
+
+/**
+ * Chip / status label. A pill-shaped `Tag` mapped to the design-system colours
+ * and sizes, supporting the `full`/`dot` variants, `standard`/`small` sizes, a
+ * `selected` state and Ant Design's `closable` (removable filter chips).
+ */
+export const Chip: React.FC<ChipProps> = ({
+  color = 'neutral',
+  variant = 'full',
+  size = 'standard',
+  selected = false,
+  style,
+  children,
+  ...rest
+}) => {
+  const palette = palettes[color];
+  const dims = sizeStyles[size];
+
+  return (
+    <Tag
+      style={{
+        display: 'inline-flex',
+        alignItems: 'center',
+        gap: 6,
+        margin: 0,
+        borderRadius: radius.pill,
+        fontWeight: 500,
+        backgroundColor: selected ? palette.border : palette.bg,
+        color: palette.fg,
+        border: `1px solid ${selected ? palette.fg : palette.border}`,
+        ...dims,
+        ...style,
+      }}
+      {...rest}
+    >
+      {variant === 'dot' && (
+        <span
+          aria-hidden
+          style={{
+            width: 6,
+            height: 6,
+            borderRadius: radius.pill,
+            backgroundColor: palette.dot,
+            display: 'inline-block',
+          }}
+        />
+      )}
+      {children}
+    </Tag>
+  );
+};
+
+export default Chip;
diff --git a/ozone-ui/packages/shared/src/components/Icon/Icon.tsx 
b/ozone-ui/packages/shared/src/components/Icon/Icon.tsx
new file mode 100644
index 00000000000..4a4de97c39c
--- /dev/null
+++ b/ozone-ui/packages/shared/src/components/Icon/Icon.tsx
@@ -0,0 +1,230 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+
+/**
+ * Names of the icons bundled with the design system. These are generic
+ * line-style glyphs (24x24, `currentColor`) covering the recurring icons in 
the
+ * Ozone screens. Rendered as inline SVG (no network requests, tree-shakeable).
+ */
+export type IconName =
+  | 'dashboard'
+  | 'health'
+  | 'settings'
+  | 'docs'
+  | 'service'
+  | 'reports'
+  | 'notifications'
+  | 'user'
+  | 'search'
+  | 'close'
+  | 'menu'
+  | 'chevron-right'
+  | 'chevron-left'
+  | 'chevron-down'
+  | 'chevron-up'
+  | 'external-link'
+  | 'copy';
+
+/** SVG path data for each icon, drawn on a 24x24 viewBox with `currentColor`. 
*/
+const paths: Record<IconName, React.ReactNode> = {
+  dashboard: <path d="M4 13h6V4H4v9Zm0 7h6v-5H4v5Zm10 
0h6v-9h-6v9Zm0-16v5h6V4h-6Z" />,
+  health: (
+    <path
+      d="M3 12h4l2 5 4-12 2 7h6"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+    />
+  ),
+  settings: (
+    <path
+      d="M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Zm7.4-2.5a5.6 5.6 0 0 0 
0-2l2-1.6-2-3.4-2.4 1a5.7 5.7 0 0 0-1.7-1L14 3h-4l-.3 2.5a5.7 5.7 0 0 0-1.7 
1l-2.4-1-2 3.4L3.6 11a5.6 5.6 0 0 0 0 2l-2 1.6 2 3.4 2.4-1c.5.4 1.1.7 1.7 1L10 
21h4l.3-2.5c.6-.3 1.2-.6 1.7-1l2.4 1 2-3.4-2-1.6Z"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="1.6"
+      strokeLinejoin="round"
+    />
+  ),
+  docs: (
+    <path
+      d="M6 2h8l4 4v16H6V2Zm8 1.5V7h3.5M8 12h8M8 16h8M8 8h3"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="1.6"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+    />
+  ),
+  service: (
+    <path
+      d="M4 7h16M4 12h16M4 17h16"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+    />
+  ),
+  reports: (
+    <path
+      d="M5 21V9m7 12V3m7 18v-7"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+    />
+  ),
+  notifications: (
+    <path
+      d="M12 22a2.5 2.5 0 0 0 2.5-2.5h-5A2.5 2.5 0 0 0 12 22Zm7-6-2-2v-4a5 5 0 
0 0-10 0v4l-2 2v1h14v-1Z"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="1.6"
+      strokeLinejoin="round"
+    />
+  ),
+  user: (
+    <path
+      d="M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Zm-7 8a7 7 0 0 1 14 0"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="1.6"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+    />
+  ),
+  search: (
+    <path
+      d="m21 21-4.3-4.3M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14Z"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="1.8"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+    />
+  ),
+  close: (
+    <path
+      d="M6 6l12 12M18 6 6 18"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+    />
+  ),
+  menu: (
+    <path
+      d="M4 6h16M4 12h16M4 18h16"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+    />
+  ),
+  'chevron-right': (
+    <path
+      d="m9 6 6 6-6 6"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+    />
+  ),
+  'chevron-left': (
+    <path
+      d="m15 6-6 6 6 6"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+    />
+  ),
+  'chevron-down': (
+    <path
+      d="m6 9 6 6 6-6"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+    />
+  ),
+  'chevron-up': (
+    <path
+      d="m6 15 6-6 6 6"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="2"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+    />
+  ),
+  'external-link': (
+    <path
+      d="M14 5h5v5M19 5l-7 7M12 5H6a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 
0 1-1v-6"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="1.6"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+    />
+  ),
+  copy: (
+    <path
+      d="M9 9V5a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1h-4M4 10a1 1 0 0 
1 1-1h9a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-9Z"
+      fill="none"
+      stroke="currentColor"
+      strokeWidth="1.6"
+      strokeLinecap="round"
+      strokeLinejoin="round"
+    />
+  ),
+};
+
+export interface IconProps extends Omit<React.SVGProps<SVGSVGElement>, 'name'> 
{
+  /** Which bundled icon to render. */
+  name: IconName;
+  /** Pixel size (width and height). Defaults to 16. */
+  size?: number;
+}
+
+/**
+ * Renders a design-system icon as inline SVG using `currentColor`, so it
+ * inherits the surrounding text colour and can be sized via the `size` prop.
+ */
+export const Icon: React.FC<IconProps> = ({ name, size = 16, style, ...rest }) 
=> (
+  <svg
+    width={size}
+    height={size}
+    viewBox="0 0 24 24"
+    fill="currentColor"
+    aria-hidden={rest['aria-label'] ? undefined : true}
+    role={rest['aria-label'] ? 'img' : undefined}
+    style={{ display: 'inline-block', verticalAlign: 'middle', flexShrink: 0, 
...style }}
+    {...rest}
+  >
+    {paths[name]}
+  </svg>
+);
+
+export default Icon;
diff --git a/ozone-ui/packages/shared/src/components/IconButton/IconButton.tsx 
b/ozone-ui/packages/shared/src/components/IconButton/IconButton.tsx
new file mode 100644
index 00000000000..078683f207c
--- /dev/null
+++ b/ozone-ui/packages/shared/src/components/IconButton/IconButton.tsx
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Button, Tooltip, type ButtonProps } from 'antd';
+
+export interface IconButtonProps extends Omit<ButtonProps, 'icon' | 'shape' | 
'children' | 'size'> {
+  /** The icon to render (e.g. an `<Icon />` or an Ant Design icon). */
+  icon: React.ReactNode;
+  /** Accessible label; also used as the tooltip when `tooltip` is not set. */
+  label: string;
+  /** Optional tooltip text (defaults to `label`). Pass `null` to disable. */
+  tooltip?: string | null;
+  /** Button footprint. `large` = 40px, `standard` (default) = 32px. */
+  size?: 'large' | 'standard';
+}
+
+/**
+ * Square icon-only button. Wraps Ant Design's `Button` with a consistent
+ * footprint, an accessible label and an optional tooltip. Matches the
+ * "Icon Button" component used in the top bar, tables and toolbars.
+ */
+export const IconButton: React.FC<IconButtonProps> = ({
+  icon,
+  label,
+  tooltip,
+  size = 'standard',
+  type = 'text',
+  style,
+  ...rest
+}) => {
+  const dimension = size === 'large' ? 40 : 32;
+  const button = (
+    <Button
+      type={type}
+      aria-label={label}
+      icon={icon}
+      style={{
+        width: dimension,
+        height: dimension,
+        display: 'inline-flex',
+        alignItems: 'center',
+        justifyContent: 'center',
+        padding: 0,
+        ...style,
+      }}
+      {...rest}
+    />
+  );
+
+  const tip = tooltip === undefined ? label : tooltip;
+  return tip ? <Tooltip title={tip}>{button}</Tooltip> : button;
+};
+
+export default IconButton;
diff --git 
a/ozone-ui/packages/shared/src/components/KeyValuePair/KeyValuePair.tsx 
b/ozone-ui/packages/shared/src/components/KeyValuePair/KeyValuePair.tsx
new file mode 100644
index 00000000000..dba2a175be8
--- /dev/null
+++ b/ozone-ui/packages/shared/src/components/KeyValuePair/KeyValuePair.tsx
@@ -0,0 +1,109 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Typography } from 'antd';
+import { semanticColors, spacing, textStyles } from '../../theme/tokens';
+
+export interface KeyValuePairProps {
+  /** The label (key) text. */
+  label: React.ReactNode;
+  /** The value. If `href` is set, it renders as a text link. */
+  value: React.ReactNode;
+  /** Render the value as a link to this destination ("Standard Link" 
variant). */
+  href?: string;
+  /** Layout direction. `vertical` (default) stacks label over value. */
+  layout?: 'vertical' | 'horizontal';
+  /** Width of the label column when `layout="horizontal"`. */
+  labelWidth?: number | string;
+  /** Allow the value to be copied (adds an inline copy affordance). */
+  copyable?: boolean;
+  style?: React.CSSProperties;
+}
+
+/**
+ * Key-value pair. Displays a small secondary label with its value, matching
+ * the "Key-Value Pair / Standard" and "Standard Link" components used across 
the
+ * Ozone detail cards.
+ */
+export const KeyValuePair: React.FC<KeyValuePairProps> = ({
+  label,
+  value,
+  href,
+  layout = 'vertical',
+  labelWidth = 160,
+  copyable = false,
+  style,
+}) => {
+  const isHorizontal = layout === 'horizontal';
+
+  const labelNode = (
+    <span
+      style={{
+        color: semanticColors.textSecondary,
+        fontSize: textStyles.bodySmall.fontSize,
+        lineHeight: `${textStyles.bodySmall.lineHeight}px`,
+        flex: isHorizontal
+          ? `0 0 ${typeof labelWidth === 'number' ? `${labelWidth}px` : 
labelWidth}`
+          : undefined,
+      }}
+    >
+      {label}
+    </span>
+  );
+
+  const valueNode = href ? (
+    <Typography.Link
+      href={href}
+      style={{
+        fontSize: textStyles.bodyStandard.fontSize,
+        lineHeight: `${textStyles.bodyStandard.lineHeight}px`,
+      }}
+    >
+      {value}
+    </Typography.Link>
+  ) : (
+    <Typography.Text
+      copyable={copyable}
+      style={{
+        color: semanticColors.textPrimary,
+        fontSize: textStyles.bodyStandard.fontSize,
+        lineHeight: `${textStyles.bodyStandard.lineHeight}px`,
+      }}
+    >
+      {value}
+    </Typography.Text>
+  );
+
+  return (
+    <div
+      style={{
+        display: 'flex',
+        flexDirection: isHorizontal ? 'row' : 'column',
+        alignItems: isHorizontal ? 'baseline' : 'flex-start',
+        gap: isHorizontal ? spacing.md : spacing.xxs,
+        ...style,
+      }}
+    >
+      {labelNode}
+      {valueNode}
+    </div>
+  );
+};
+
+export default KeyValuePair;
diff --git a/ozone-ui/packages/shared/src/components/PageHeader/PageHeader.tsx 
b/ozone-ui/packages/shared/src/components/PageHeader/PageHeader.tsx
new file mode 100644
index 00000000000..7c54c463671
--- /dev/null
+++ b/ozone-ui/packages/shared/src/components/PageHeader/PageHeader.tsx
@@ -0,0 +1,99 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Typography } from 'antd';
+import { semanticColors, spacing, textStyles } from '../../theme/tokens';
+
+export interface PageHeaderProps {
+  /** Page title. */
+  title: React.ReactNode;
+  /** Optional supporting text under the title. */
+  subtitle?: React.ReactNode;
+  /** Optional content rendered above the title (e.g. breadcrumbs). */
+  breadcrumb?: React.ReactNode;
+  /** Right-aligned actions (buttons, filters, ...). */
+  actions?: React.ReactNode;
+  style?: React.CSSProperties;
+}
+
+/**
+ * Page header. Renders the page title with optional breadcrumb, subtitle and
+ * right-aligned actions, matching the "Page Header" component used at the top 
of
+ * the Ozone content area.
+ */
+export const PageHeader: React.FC<PageHeaderProps> = ({
+  title,
+  subtitle,
+  breadcrumb,
+  actions,
+  style,
+}) => {
+  return (
+    <div
+      style={{
+        display: 'flex',
+        flexDirection: 'column',
+        gap: spacing.xs,
+        marginBottom: spacing.xl,
+        ...style,
+      }}
+    >
+      {breadcrumb}
+      <div
+        style={{
+          display: 'flex',
+          alignItems: 'flex-start',
+          justifyContent: 'space-between',
+          gap: spacing.lg,
+        }}
+      >
+        <div style={{ display: 'flex', flexDirection: 'column', gap: 
spacing.xxs }}>
+          <Typography.Title
+            level={1}
+            style={{
+              margin: 0,
+              fontSize: textStyles.h1.fontSize,
+              lineHeight: `${textStyles.h1.lineHeight}px`,
+              fontWeight: textStyles.h1.fontWeight,
+              color: semanticColors.textPrimary,
+            }}
+          >
+            {title}
+          </Typography.Title>
+          {subtitle && (
+            <Typography.Text
+              style={{
+                color: semanticColors.textSecondary,
+                fontSize: textStyles.bodyStandard.fontSize,
+                lineHeight: `${textStyles.bodyStandard.lineHeight}px`,
+              }}
+            >
+              {subtitle}
+            </Typography.Text>
+          )}
+        </div>
+        {actions && (
+          <div style={{ display: 'flex', alignItems: 'center', gap: spacing.sm 
}}>{actions}</div>
+        )}
+      </div>
+    </div>
+  );
+};
+
+export default PageHeader;
diff --git a/ozone-ui/packages/shared/src/components/Sidebar/Sidebar.tsx 
b/ozone-ui/packages/shared/src/components/Sidebar/Sidebar.tsx
new file mode 100644
index 00000000000..39b8815c769
--- /dev/null
+++ b/ozone-ui/packages/shared/src/components/Sidebar/Sidebar.tsx
@@ -0,0 +1,119 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { Layout, Menu, type MenuProps } from 'antd';
+import { DoubleLeftOutlined } from '@ant-design/icons';
+import { useLocation, useNavigate } from 'react-router-dom';
+import { MenuItem, findSelectedKey, getMenuItemPath } from 
'../../utils/menuUtils';
+
+export interface SidebarProps {
+  /** Navigation items to render in the rail. Each item may carry a `path`. */
+  items: MenuItem[];
+  /** Branding shown when the rail is expanded (e.g. a logo + product name). */
+  logo?: React.ReactNode;
+  /** Branding shown when the rail is collapsed (defaults to `logo`). */
+  collapsedLogo?: React.ReactNode;
+  /** Called with the active item's label whenever the active route changes. */
+  onHeaderChange?: (header: string) => void;
+  /** Controlled collapsed state. When omitted the component manages its own. 
*/
+  collapsed?: boolean;
+  /** Initial collapsed state when uncontrolled. */
+  defaultCollapsed?: boolean;
+  /** Called whenever the collapsed state changes. */
+  onCollapse?: (collapsed: boolean) => void;
+  /** Expanded rail width. Defaults to `'15%'`. */
+  width?: string | number;
+  /** Collapsed rail width in px. Defaults to `56`. */
+  collapsedWidth?: number;
+}
+
+/**
+ * Application navigation rail.
+ *
+ * The rail is router-aware (via `react-router-dom`): it highlights the item 
that
+ * matches the current location and navigates on selection, so applications do
+ * not have to wire selection/navigation themselves. It is still fully
+ * configurable — the consuming application supplies the menu `items` (with
+ * `path`s) and its own `logo` — so no app-specific routes or branding are 
baked
+ * in. Must be rendered within a react-router context (e.g. `BrowserRouter`).
+ */
+export const Sidebar: React.FC<SidebarProps> = ({
+  items,
+  logo,
+  collapsedLogo,
+  onHeaderChange,
+  collapsed: collapsedProp,
+  defaultCollapsed = false,
+  onCollapse,
+  width = '15%',
+  collapsedWidth = 56,
+}) => {
+  const location = useLocation();
+  const navigate = useNavigate();
+  const [internalCollapsed, setInternalCollapsed] = 
useState<boolean>(defaultCollapsed);
+  const isControlled = collapsedProp !== undefined;
+  const collapsed = isControlled ? collapsedProp : internalCollapsed;
+
+  const { selectedKey, header } = findSelectedKey(items, location.pathname);
+
+  useEffect(() => {
+    if (header) {
+      onHeaderChange?.(header);
+    }
+  }, [header, onHeaderChange]);
+
+  const handleCollapse = (value: boolean) => {
+    if (!isControlled) {
+      setInternalCollapsed(value);
+    }
+    onCollapse?.(value);
+  };
+
+  const handleSelect: MenuProps['onSelect'] = ({ key }) => {
+    const path = getMenuItemPath(items, key);
+    if (path) {
+      navigate(path);
+    }
+  };
+
+  const branding = collapsed ? (collapsedLogo ?? logo) : logo;
+
+  return (
+    <Layout.Sider
+      prefixCls="navbar"
+      collapsible
+      collapsed={collapsed}
+      collapsedWidth={collapsedWidth}
+      onCollapse={handleCollapse}
+      width={width}
+      trigger={<DoubleLeftOutlined />}
+    >
+      {branding}
+      <Menu
+        theme="dark"
+        mode="inline"
+        items={items}
+        selectedKeys={selectedKey ? [selectedKey] : []}
+        onSelect={handleSelect}
+      />
+    </Layout.Sider>
+  );
+};
+
+export default Sidebar;
diff --git a/ozone-ui/packages/shared/src/components/TextLink/TextLink.tsx 
b/ozone-ui/packages/shared/src/components/TextLink/TextLink.tsx
new file mode 100644
index 00000000000..cfaa21b50a6
--- /dev/null
+++ b/ozone-ui/packages/shared/src/components/TextLink/TextLink.tsx
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Typography } from 'antd';
+import type { LinkProps } from 'antd/es/typography/Link';
+import { textStyles } from '../../theme/tokens';
+import Icon from '../Icon/Icon';
+
+export interface TextLinkProps extends LinkProps {
+  /** Render as an external link (opens in a new tab, shows an external icon). 
*/
+  external?: boolean;
+  /** Text size. Defaults to `standard`. */
+  size?: 'standard' | 'small';
+}
+
+/**
+ * Inline text link built on Ant Design's `Typography.Link`, themed with the
+ * design-system link colour and type scale. Set `external` for links that open
+ * in a new tab (adds a trailing external-link glyph).
+ */
+export const TextLink: React.FC<TextLinkProps> = ({
+  external = false,
+  size = 'standard',
+  style,
+  children,
+  ...rest
+}) => {
+  const scale = size === 'small' ? textStyles.bodySmall : 
textStyles.bodyStandard;
+  const externalProps = external ? { target: '_blank', rel: 'noopener 
noreferrer' } : {};
+
+  return (
+    <Typography.Link
+      {...rest}
+      style={{
+        fontSize: scale.fontSize,
+        lineHeight: `${scale.lineHeight}px`,
+        display: 'inline-flex',
+        alignItems: 'center',
+        gap: 4,
+        ...style,
+      }}
+      {...externalProps}
+    >
+      {children}
+      {external && <Icon name="external-link" size={size === 'small' ? 12 : 
14} />}
+    </Typography.Link>
+  );
+};
+
+export default TextLink;
diff --git a/ozone-ui/packages/shared/src/components/UtilityBar/UtilityBar.tsx 
b/ozone-ui/packages/shared/src/components/UtilityBar/UtilityBar.tsx
new file mode 100644
index 00000000000..ebdce680bf4
--- /dev/null
+++ b/ozone-ui/packages/shared/src/components/UtilityBar/UtilityBar.tsx
@@ -0,0 +1,93 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { colors, fontFamilies, semanticColors, spacing, textStyles } from 
'../../theme/tokens';
+
+export interface UtilityBarProps {
+  /** Left slot, e.g. an app switcher or menu button. */
+  leading?: React.ReactNode;
+  /** Product / app title shown next to the leading slot. */
+  title?: React.ReactNode;
+  /** Optional centre slot (e.g. global search). */
+  center?: React.ReactNode;
+  /** Right slot, e.g. notification/user icon buttons. */
+  actions?: React.ReactNode;
+  /** Height in px. Defaults to 48. */
+  height?: number;
+  style?: React.CSSProperties;
+}
+
+/**
+ * Global top utility bar (the dark chrome at the very top of every screen).
+ * Provides leading/title, an optional centre slot and right-aligned actions.
+ */
+export const UtilityBar: React.FC<UtilityBarProps> = ({
+  leading,
+  title,
+  center,
+  actions,
+  height = 48,
+  style,
+}) => (
+  <div
+    style={{
+      display: 'flex',
+      alignItems: 'center',
+      gap: spacing.md,
+      height,
+      paddingInline: spacing.md,
+      background: colors.pewter[950],
+      color: 'rgb(255, 255, 255)',
+      ...style,
+    }}
+  >
+    <div style={{ display: 'flex', alignItems: 'center', gap: spacing.sm }}>
+      {leading}
+      {title && (
+        <span
+          style={{
+            fontFamily: fontFamilies.appTitle,
+            fontSize: textStyles.appTitle.fontSize,
+            fontWeight: textStyles.appTitle.fontWeight,
+            lineHeight: `${textStyles.appTitle.lineHeight}px`,
+            color: 'rgb(255, 255, 255)',
+          }}
+        >
+          {title}
+        </span>
+      )}
+    </div>
+
+    {center && <div style={{ flex: 1, display: 'flex', justifyContent: 
'center' }}>{center}</div>}
+
+    <div
+      style={{
+        marginLeft: center ? 0 : 'auto',
+        display: 'flex',
+        alignItems: 'center',
+        gap: spacing.xs,
+        color: semanticColors.textDisabled,
+      }}
+    >
+      {actions}
+    </div>
+  </div>
+);
+
+export default UtilityBar;
diff --git a/ozone-ui/packages/shared/src/index.ts 
b/ozone-ui/packages/shared/src/index.ts
new file mode 100644
index 00000000000..87e7994659c
--- /dev/null
+++ b/ozone-ui/packages/shared/src/index.ts
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Theme (design tokens + Ant Design theme + provider)
+export * from './theme';
+
+// Components
+export { default as Sidebar } from './components/Sidebar/Sidebar';
+export type { SidebarProps } from './components/Sidebar/Sidebar';
+export { default as AppLayout } from './components/AppLayout/AppLayout';
+export type { AppLayoutProps } from './components/AppLayout/AppLayout';
+export { default as UtilityBar } from './components/UtilityBar/UtilityBar';
+export type { UtilityBarProps } from './components/UtilityBar/UtilityBar';
+export { default as PageHeader } from './components/PageHeader/PageHeader';
+export type { PageHeaderProps } from './components/PageHeader/PageHeader';
+export { default as Card } from './components/Card/Card';
+export type { CardProps } from './components/Card/Card';
+export { default as KeyValuePair } from 
'./components/KeyValuePair/KeyValuePair';
+export type { KeyValuePairProps } from 
'./components/KeyValuePair/KeyValuePair';
+export { default as Chip } from './components/Chip/Chip';
+export type { ChipProps, ChipColor, ChipVariant, ChipSize } from 
'./components/Chip/Chip';
+export { default as Alert } from './components/Alert/Alert';
+export type { AlertProps } from './components/Alert/Alert';
+export { default as TextLink } from './components/TextLink/TextLink';
+export type { TextLinkProps } from './components/TextLink/TextLink';
+export { default as IconButton } from './components/IconButton/IconButton';
+export type { IconButtonProps } from './components/IconButton/IconButton';
+export { default as Icon } from './components/Icon/Icon';
+export type { IconProps, IconName } from './components/Icon/Icon';
+
+// Utils
+export * from './utils/menuUtils';
diff --git a/ozone-ui/packages/shared/src/theme/ThemeProvider.tsx 
b/ozone-ui/packages/shared/src/theme/ThemeProvider.tsx
new file mode 100644
index 00000000000..93b7f52399c
--- /dev/null
+++ b/ozone-ui/packages/shared/src/theme/ThemeProvider.tsx
@@ -0,0 +1,50 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { ConfigProvider, type ThemeConfig } from 'antd';
+import { ozoneTheme } from './antdTheme';
+
+export interface ThemeProviderProps {
+  children: React.ReactNode;
+  /**
+   * Optional partial theme merged on top of the base Ozone theme, for
+   * app-specific tweaks. Only the `token`/`components` maps are 
shallow-merged.
+   */
+  themeOverrides?: ThemeConfig;
+}
+
+/**
+ * Wraps an application subtree with the Ozone UI Ant Design theme. Every Ozone
+ * app should mount this once near its root so all Ant Design components 
inherit
+ * the design-system colours, typography and radii.
+ */
+export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children, 
themeOverrides }) => {
+  const theme: ThemeConfig = themeOverrides
+    ? {
+        ...ozoneTheme,
+        ...themeOverrides,
+        token: { ...ozoneTheme.token, ...themeOverrides.token },
+        components: { ...ozoneTheme.components, ...themeOverrides.components },
+      }
+    : ozoneTheme;
+
+  return <ConfigProvider theme={theme}>{children}</ConfigProvider>;
+};
+
+export default ThemeProvider;
diff --git a/ozone-ui/packages/shared/src/theme/antdTheme.ts 
b/ozone-ui/packages/shared/src/theme/antdTheme.ts
new file mode 100644
index 00000000000..9d573ad4dc7
--- /dev/null
+++ b/ozone-ui/packages/shared/src/theme/antdTheme.ts
@@ -0,0 +1,128 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import type { ThemeConfig } from 'antd';
+import { colors, fontFamilies, radius, semanticColors, textStyles } from 
'./tokens';
+
+/**
+ * Ant Design v5 theme derived from the Ozone UI design tokens.
+ *
+ * Pass this to `<ConfigProvider theme={ozoneTheme}>` (see `ThemeProvider`) so
+ * every Ant Design component picks up the design-system colours, typography 
and
+ * radii without per-component overrides.
+ */
+export const ozoneTheme: ThemeConfig = {
+  token: {
+    // Brand / status colours
+    colorPrimary: semanticColors.brand,
+    colorInfo: semanticColors.info,
+    colorSuccess: semanticColors.success,
+    colorWarning: semanticColors.warning,
+    colorError: semanticColors.error,
+    colorLink: semanticColors.info,
+
+    // Neutrals / text
+    colorText: semanticColors.textPrimary,
+    colorTextSecondary: semanticColors.textSecondary,
+    colorTextTertiary: semanticColors.textTertiary,
+    colorTextDisabled: semanticColors.textDisabled,
+    colorBorder: semanticColors.border,
+    colorBorderSecondary: semanticColors.border,
+    colorBgLayout: semanticColors.bgLayout,
+    colorBgContainer: semanticColors.bgContainer,
+    colorBgElevated: semanticColors.bgElevated,
+    colorFillSecondary: semanticColors.fill,
+
+    // Typography
+    fontFamily: fontFamilies.base,
+    fontFamilyCode: fontFamilies.monospace,
+    fontSize: textStyles.bodyStandard.fontSize,
+    fontSizeSM: textStyles.bodySmall.fontSize,
+    fontSizeLG: textStyles.bodyLarge.fontSize,
+    fontSizeHeading1: textStyles.h1.fontSize,
+    fontSizeHeading2: textStyles.h2.fontSize,
+    fontSizeHeading3: textStyles.h3.fontSize,
+    fontSizeHeading4: textStyles.h4.fontSize,
+    fontSizeHeading5: textStyles.h5.fontSize,
+    lineHeight: textStyles.bodyStandard.lineHeight / 
textStyles.bodyStandard.fontSize,
+
+    // Shape
+    borderRadius: radius.md,
+    borderRadiusLG: radius.lg,
+    borderRadiusSM: radius.sm,
+
+    // Control sizing kept aligned with the 4px spacing rhythm.
+    controlHeight: 32,
+    wireframe: false,
+  },
+  components: {
+    Layout: {
+      headerBg: semanticColors.bgContainer,
+      headerColor: semanticColors.textPrimary,
+      headerHeight: 56,
+      headerPadding: '0 24px',
+      bodyBg: semanticColors.bgLayout,
+      // The navigation rail is a deep pewter surface.
+      siderBg: colors.pewter[900],
+      triggerBg: colors.pewter[800],
+    },
+    Menu: {
+      darkItemBg: colors.pewter[900],
+      darkSubMenuItemBg: colors.pewter[950],
+      darkItemSelectedBg: colors.orange[400],
+      darkItemSelectedColor: 'rgb(255, 255, 255)',
+      darkItemColor: colors.pewter[200],
+      darkItemHoverBg: colors.pewter[800],
+      itemBorderRadius: radius.md,
+      itemSelectedBg: colors.orange[50],
+      itemSelectedColor: colors.orange[500],
+    },
+    Card: {
+      borderRadiusLG: radius.lg,
+      colorBorderSecondary: semanticColors.border,
+      headerFontSize: textStyles.h3.fontSize,
+      paddingLG: 20,
+    },
+    Button: {
+      borderRadius: radius.md,
+      fontWeight: 500,
+      primaryShadow: 'none',
+      defaultShadow: 'none',
+    },
+    Tag: {
+      borderRadiusSM: radius.pill,
+      defaultBg: colors.pewter[50],
+      defaultColor: semanticColors.textSecondary,
+    },
+    Table: {
+      headerBg: colors.pewter[25],
+      headerColor: semanticColors.textSecondary,
+      borderColor: semanticColors.border,
+      rowHoverBg: colors.pewter[25],
+    },
+    Input: {
+      borderRadius: radius.md,
+      colorBorder: semanticColors.borderStrong,
+    },
+    Alert: {
+      borderRadiusLG: radius.md,
+    },
+  },
+};
+
+export default ozoneTheme;
diff --git a/ozone-ui/src/packages/shared/src/index.ts 
b/ozone-ui/packages/shared/src/theme/index.ts
similarity index 79%
rename from ozone-ui/src/packages/shared/src/index.ts
rename to ozone-ui/packages/shared/src/theme/index.ts
index 209bc46478e..8b73edf8595 100644
--- a/ozone-ui/src/packages/shared/src/index.ts
+++ b/ozone-ui/packages/shared/src/theme/index.ts
@@ -15,8 +15,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// Components
-export { default as Sidebar } from './components/Sidebar/Sidebar';
 
-// Utils
-export * from './utils/menuUtils';
+export * from './tokens';
+export { ozoneTheme, default as ozoneThemeDefault } from './antdTheme';
+export { ThemeProvider } from './ThemeProvider';
+export type { ThemeProviderProps } from './ThemeProvider';
diff --git a/ozone-ui/packages/shared/src/theme/tokens.ts 
b/ozone-ui/packages/shared/src/theme/tokens.ts
new file mode 100644
index 00000000000..2034d93bd16
--- /dev/null
+++ b/ozone-ui/packages/shared/src/theme/tokens.ts
@@ -0,0 +1,211 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Ozone UI design tokens.
+ *
+ * The single source of truth for colour and typography across the Ozone UI
+ * monorepo. Prefer referencing these tokens (or the derived Ant Design theme 
in
+ * `antdTheme.ts`) over hard-coding raw colour/size values.
+ */
+
+/** Full colour ramps. The `*` marked step is each family's canonical value. */
+export const colors = {
+  pewter: {
+    25: 'rgb(244, 247, 249)',
+    50: 'rgb(225, 234, 240)',
+    100: 'rgb(206, 219, 228)', // base
+    200: 'rgb(184, 200, 212)',
+    300: 'rgb(163, 181, 194)',
+    400: 'rgb(133, 154, 168)',
+    500: 'rgb(110, 131, 145)',
+    600: 'rgb(86, 104, 117)',
+    700: 'rgb(69, 81, 90)',
+    800: 'rgb(51, 60, 67)',
+    900: 'rgb(35, 43, 48)',
+    950: 'rgb(26, 32, 36)',
+  },
+  blueNova: {
+    25: 'rgb(249, 249, 255)',
+    50: 'rgb(240, 240, 255)',
+    100: 'rgb(220, 220, 252)',
+    200: 'rgb(199, 199, 253)',
+    300: 'rgb(172, 172, 252)',
+    400: 'rgb(144, 144, 252)',
+    500: 'rgb(115, 115, 250)',
+    600: 'rgb(85, 85, 249)', // base
+    700: 'rgb(59, 46, 219)',
+    800: 'rgb(48, 34, 144)',
+    900: 'rgb(28, 19, 78)',
+    950: 'rgb(19, 13, 50)',
+  },
+  orange: {
+    25: 'rgb(255, 244, 240)',
+    50: 'rgb(255, 227, 214)',
+    100: 'rgb(255, 205, 184)',
+    200: 'rgb(255, 169, 133)',
+    300: 'rgb(255, 126, 71)',
+    400: 'rgb(255, 85, 13)', // base (brand primary)
+    500: 'rgb(224, 67, 0)',
+    600: 'rgb(179, 53, 0)',
+    700: 'rgb(133, 39, 0)',
+    800: 'rgb(92, 27, 0)',
+    900: 'rgb(46, 14, 0)',
+    950: 'rgb(20, 6, 0)',
+  },
+  red: {
+    25: 'rgb(255, 248, 248)',
+    50: 'rgb(254, 234, 234)',
+    100: 'rgb(254, 216, 216)',
+    200: 'rgb(253, 192, 191)',
+    300: 'rgb(252, 156, 155)',
+    400: 'rgb(251, 115, 113)',
+    500: 'rgb(247, 65, 59)',
+    600: 'rgb(222, 36, 28)',
+    700: 'rgb(181, 24, 16)', // base
+    800: 'rgb(126, 13, 8)',
+    900: 'rgb(74, 4, 3)',
+    950: 'rgb(48, 2, 1)',
+  },
+  green: {
+    25: 'rgb(240, 252, 241)',
+    50: 'rgb(206, 245, 210)',
+    100: 'rgb(171, 237, 180)',
+    200: 'rgb(126, 229, 147)',
+    300: 'rgb(68, 212, 104)',
+    400: 'rgb(0, 189, 68)',
+    500: 'rgb(0, 162, 57)',
+    600: 'rgb(0, 138, 47)',
+    700: 'rgb(0, 115, 38)', // base
+    800: 'rgb(0, 78, 23)',
+    900: 'rgb(0, 43, 9)',
+    950: 'rgb(0, 27, 4)',
+  },
+  amber: {
+    50: 'rgb(255, 245, 231)',
+    100: 'rgb(255, 235, 204)',
+    200: 'rgb(255, 213, 122)',
+    300: 'rgb(247, 194, 0)', // base
+    400: 'rgb(207, 162, 0)',
+    500: 'rgb(172, 134, 0)',
+    600: 'rgb(135, 105, 0)',
+    700: 'rgb(103, 79, 0)',
+    800: 'rgb(70, 53, 0)',
+    900: 'rgb(41, 30, 0)',
+  },
+} as const;
+
+/**
+ * Semantic colour aliases. UI code should prefer these over raw ramp steps so
+ * intent is explicit and re-theming stays centralised.
+ */
+export const semanticColors = {
+  brand: colors.orange[400],
+  brandHover: colors.orange[300],
+  brandActive: colors.orange[500],
+  info: colors.blueNova[600],
+  success: colors.green[700],
+  warning: colors.amber[300],
+  error: colors.red[700],
+  textPrimary: colors.pewter[900],
+  textSecondary: colors.pewter[600],
+  textTertiary: colors.pewter[500],
+  textDisabled: colors.pewter[400],
+  border: colors.pewter[100],
+  borderStrong: colors.pewter[200],
+  bgLayout: colors.pewter[25],
+  bgContainer: 'rgb(255, 255, 255)',
+  bgElevated: 'rgb(255, 255, 255)',
+  fill: colors.pewter[50],
+  skeleton: colors.pewter[50],
+} as const;
+
+/** Font families. Roboto is the primary UI face; app titles use Plus Jakarta 
Sans. */
+export const fontFamilies = {
+  base: "'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
+  monospace: "'Roboto Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', 
monospace",
+  appTitle: "'Plus Jakarta Sans', 'Roboto', sans-serif",
+} as const;
+
+export interface TextStyle {
+  fontFamily: string;
+  fontSize: number;
+  fontWeight: number;
+  lineHeight: number;
+}
+
+/** Type scale. Sizes/line-heights are in px. */
+export const textStyles = {
+  displayLarge: { fontFamily: fontFamilies.base, fontSize: 24, fontWeight: 
600, lineHeight: 32 },
+  h1: { fontFamily: fontFamilies.base, fontSize: 20, fontWeight: 600, 
lineHeight: 24 },
+  h2: { fontFamily: fontFamilies.base, fontSize: 18, fontWeight: 600, 
lineHeight: 24 },
+  h3: { fontFamily: fontFamilies.base, fontSize: 16, fontWeight: 600, 
lineHeight: 20 },
+  h4: { fontFamily: fontFamilies.base, fontSize: 14, fontWeight: 600, 
lineHeight: 20 },
+  h5: { fontFamily: fontFamilies.base, fontSize: 12, fontWeight: 600, 
lineHeight: 16 },
+  h6: { fontFamily: fontFamilies.base, fontSize: 12, fontWeight: 600, 
lineHeight: 16 },
+  bodyLarge: { fontFamily: fontFamilies.base, fontSize: 16, fontWeight: 400, 
lineHeight: 24 },
+  bodyLargeBold: { fontFamily: fontFamilies.base, fontSize: 16, fontWeight: 
600, lineHeight: 24 },
+  bodyStandard: { fontFamily: fontFamilies.base, fontSize: 14, fontWeight: 
400, lineHeight: 20 },
+  bodyStandardBold: {
+    fontFamily: fontFamilies.base,
+    fontSize: 14,
+    fontWeight: 600,
+    lineHeight: 20,
+  },
+  bodySmall: { fontFamily: fontFamilies.base, fontSize: 12, fontWeight: 400, 
lineHeight: 16 },
+  bodySmallBold: { fontFamily: fontFamilies.base, fontSize: 12, fontWeight: 
600, lineHeight: 16 },
+  monospaceStandard: {
+    fontFamily: fontFamilies.monospace,
+    fontSize: 14,
+    fontWeight: 400,
+    lineHeight: 20,
+  },
+  monospaceSmall: {
+    fontFamily: fontFamilies.monospace,
+    fontSize: 12,
+    fontWeight: 400,
+    lineHeight: 16,
+  },
+  appTitle: { fontFamily: fontFamilies.appTitle, fontSize: 16, fontWeight: 
500, lineHeight: 24 },
+} as const satisfies Record<string, TextStyle>;
+
+/**
+ * Spacing scale (px). A 4px-based scale aligned with Ant Design's default
+ * sizing and the spacing rhythm used across the Ozone screens.
+ */
+export const spacing = {
+  xxs: 2,
+  xs: 4,
+  sm: 8,
+  md: 12,
+  lg: 16,
+  xl: 24,
+  xxl: 32,
+  xxxl: 48,
+} as const;
+
+/** Corner radii (px). */
+export const radius = {
+  sm: 4,
+  md: 6,
+  lg: 8,
+  pill: 999,
+} as const;
+
+export type ColorFamily = keyof typeof colors;
+export type TextStyleName = keyof typeof textStyles;
diff --git a/ozone-ui/src/packages/shared/src/utils/menuUtils.ts 
b/ozone-ui/packages/shared/src/utils/menuUtils.ts
similarity index 79%
rename from ozone-ui/src/packages/shared/src/utils/menuUtils.ts
rename to ozone-ui/packages/shared/src/utils/menuUtils.ts
index f38db2c72fa..92a0773aa8c 100644
--- a/ozone-ui/src/packages/shared/src/utils/menuUtils.ts
+++ b/ozone-ui/packages/shared/src/utils/menuUtils.ts
@@ -59,3 +59,19 @@ export const findSelectedKey = (
   }
   return { selectedKey: null, header: null };
 };
+
+/** Resolve the route path associated with a menu item key, if any. */
+export const getMenuItemPath = (items: MenuItem[], key: string): string | 
undefined => {
+  for (const item of items) {
+    if (item.key === key) {
+      return item.path;
+    }
+    if (item.children) {
+      const path = getMenuItemPath(item.children, key);
+      if (path !== undefined) {
+        return path;
+      }
+    }
+  }
+  return undefined;
+};
diff --git a/ozone-ui/src/packages/shared/tsconfig.json 
b/ozone-ui/packages/shared/tsconfig.json
similarity index 100%
rename from ozone-ui/src/packages/shared/tsconfig.json
rename to ozone-ui/packages/shared/tsconfig.json
diff --git a/ozone-ui/src/pnpm-lock.yaml b/ozone-ui/pnpm-lock.yaml
similarity index 99%
rename from ozone-ui/src/pnpm-lock.yaml
rename to ozone-ui/pnpm-lock.yaml
index f85ee915d06..e3027a092b2 100644
--- a/ozone-ui/src/pnpm-lock.yaml
+++ b/ozone-ui/pnpm-lock.yaml
@@ -105,7 +105,7 @@ importers:
       '@fontsource/roboto':
         specifier: ^4.5.8
         version: 4.5.8
-      '@hadoop-ui/shared':
+      '@ozone-ui/shared':
         specifier: workspace:*
         version: link:../shared
       ag-charts-community:
@@ -226,7 +226,7 @@ importers:
       '@fontsource/roboto':
         specifier: ^4.5.8
         version: 4.5.8
-      '@hadoop-ui/shared':
+      '@ozone-ui/shared':
         specifier: workspace:*
         version: link:../shared
       ag-charts-community:
@@ -347,7 +347,7 @@ importers:
       '@fontsource/roboto':
         specifier: ^4.5.8
         version: 4.5.8
-      '@hadoop-ui/shared':
+      '@ozone-ui/shared':
         specifier: workspace:*
         version: link:../shared
       ag-charts-community:
@@ -484,6 +484,9 @@ importers:
       '@types/react-dom':
         specifier: ^18.3.1
         version: 18.3.7(@types/[email protected])
+      react-router-dom:
+        specifier: ^7.3.0
+        version: 7.6.3([email protected])([email protected])
       typescript:
         specifier: ~5.6.2
         version: 5.6.3
@@ -2828,7 +2831,6 @@ packages:
   /[email protected]:
     resolution: {integrity: 
sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
     engines: {node: '>=18'}
-    dev: false
 
   /[email protected]:
     resolution: {integrity: 
sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
@@ -6306,7 +6308,6 @@ packages:
       react: 18.3.1
       react-dom: 18.3.1([email protected])
       react-router: 7.6.3([email protected])([email protected])
-    dev: false
 
   /[email protected]([email protected])([email protected]):
     resolution: {integrity: 
sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA==}
@@ -6322,7 +6323,6 @@ packages:
       react: 18.3.1
       react-dom: 18.3.1([email protected])
       set-cookie-parser: 2.7.1
-    dev: false
 
   /[email protected]:
     resolution: {integrity: 
sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
diff --git a/ozone-ui/src/pnpm-workspace.yaml b/ozone-ui/pnpm-workspace.yaml
similarity index 100%
rename from ozone-ui/src/pnpm-workspace.yaml
rename to ozone-ui/pnpm-workspace.yaml
diff --git a/ozone-ui/src/.gitignore b/ozone-ui/src/.gitignore
deleted file mode 100644
index db4c6d9b679..00000000000
--- a/ozone-ui/src/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-dist
-node_modules
\ No newline at end of file
diff --git a/ozone-ui/src/packages/shared/src/components/Sidebar/Sidebar.tsx 
b/ozone-ui/src/packages/shared/src/components/Sidebar/Sidebar.tsx
deleted file mode 100644
index 65edfc83a88..00000000000
--- a/ozone-ui/src/packages/shared/src/components/Sidebar/Sidebar.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
-import { Menu, Layout } from 'antd';
-import { DashboardOutlined, DoubleLeftOutlined, EditOutlined } from 
'@ant-design/icons';
-import { useLocation } from 'react-router-dom';
-import { MenuItem, getNavMenuItem, findSelectedKey } from 
'../../utils/menuUtils';
-
-type SiderProps = {
-  setHeader: Dispatch<SetStateAction<string>>;
-};
-
-// Logo components using the favicon
-const expandedSidebarLogo = (
-  <span style={{ padding: '8px 0px 8px 12px', display: 'block', alignItems: 
'center' }}>
-    <img
-      src="/shared/icons/favicon.ico"
-      alt="Ozone Logo"
-      style={{
-        width: '24px',
-        height: '24px',
-        marginRight: '8px',
-        display: 'inline-block',
-        verticalAlign: 'middle',
-      }}
-    />
-    <span style={{ color: '#fff', fontSize: '16px', fontWeight: 'bold' 
}}>Ozone</span>
-  </span>
-);
-
-const collapsedSidebarLogo = (
-  <span style={{ padding: '12px 12px', display: 'block', textAlign: 'center' 
}}>
-    <img
-      src="/shared/icons/favicon.ico"
-      alt="Ozone Logo"
-      style={{
-        width: '24px',
-        height: '24px',
-      }}
-    />
-  </span>
-);
-
-const Sidebar: React.FC<SiderProps> = ({ setHeader }) => {
-  const [collapsed, setCollapsed] = useState<boolean>(false);
-  const [selectedKey, setSelectedKey] = useState<string | null>(null);
-  const location = useLocation();
-
-  const navigationItems: MenuItem[] = [
-    getNavMenuItem('Overview', 'Overview', '/', <DashboardOutlined />, 
undefined),
-    getNavMenuItem('Configurations', 'Configurations', undefined, 
<EditOutlined />, [
-      getNavMenuItem(
-        'Application Settings',
-        'Application Settings',
-        '/appconfig',
-        undefined,
-        undefined
-      ),
-      getNavMenuItem(
-        'Profile Settings',
-        'Profile Settings',
-        '/profileconfig',
-        undefined,
-        undefined
-      ),
-    ]),
-  ];
-
-  useEffect(() => {
-    const { selectedKey: newSelectedKey, header: newHeader } = findSelectedKey(
-      navigationItems,
-      location.pathname
-    );
-
-    if (newSelectedKey && newSelectedKey !== selectedKey) {
-      setSelectedKey(newSelectedKey);
-    }
-    // we are sure that header will be present if selectedKey is found,
-    // But just in case nothing breaks due to any change it is better to 
explicitly check
-    // that newHeader is not null
-    if (newHeader) {
-      setHeader(newHeader);
-    }
-  }, [location.pathname, selectedKey, setHeader]); // eslint-disable-line 
react-hooks/exhaustive-deps
-
-  return (
-    <Layout.Sider
-      prefixCls="navbar"
-      collapsible={true}
-      collapsed={collapsed}
-      collapsedWidth={56}
-      onCollapse={(value: boolean) => setCollapsed(value)}
-      width={'15%'}
-      trigger={<DoubleLeftOutlined />}
-    >
-      {collapsed ? collapsedSidebarLogo : expandedSidebarLogo}
-      <Menu
-        theme="dark"
-        defaultSelectedKeys={['Overview']}
-        selectedKeys={selectedKey ? [selectedKey] : []}
-        mode="inline"
-        items={navigationItems}
-        onSelect={({ keyPath }) => {
-          setHeader([...keyPath].map(keyPath.pop, keyPath).join(' / '));
-        }}
-      />
-    </Layout.Sider>
-  );
-};
-
-export default Sidebar;
diff --git a/ozone-ui/src/packages/shared/utils/menuUtils.tsx 
b/ozone-ui/src/packages/shared/utils/menuUtils.tsx
deleted file mode 100644
index 5d5f1e35f1a..00000000000
--- a/ozone-ui/src/packages/shared/utils/menuUtils.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
diff --git a/ozone-ui/src/tsconfig.json b/ozone-ui/tsconfig.json
similarity index 100%
rename from ozone-ui/src/tsconfig.json
rename to ozone-ui/tsconfig.json
diff --git a/ozone-ui/src/vite.config.shared.ts b/ozone-ui/vite.config.shared.ts
similarity index 100%
rename from ozone-ui/src/vite.config.shared.ts
rename to ozone-ui/vite.config.shared.ts


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to