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]