);
}
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 1c6bbc3b61e1..814e0415e532 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(
-
+
+
+
);
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 9726123e879e..d0ed72b742fa 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 653da3d02455..d07ab0bbb4c6 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%
rename from ozone-ui/src/packages/om/src/main.tsx
rename to ozone-ui/packages/scm/src/main.tsx
index e5de33f5d7ba..e4b310bf1344 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(
-
+
+
+
);
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 b817af1296fd..a1b7859a7d26 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 83%
rename from ozone-ui/src/packages/shared/package.json
rename to ozone-ui/packages/shared/package.json
index a8f720c1f296..2d400d66f30f 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/packages/shared/src/components/Alert/Alert.tsx b/ozone-ui/packages/shared/src/components/Alert/Alert.tsx
new file mode 100644
index 000000000000..67e1b86cd263
--- /dev/null
+++ b/ozone-ui/packages/shared/src/components/Alert/Alert.tsx
@@ -0,0 +1,35 @@
+/**
+ * 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 { 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 = ({ showIcon = true, 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 000000000000..8933c48e3ba1
--- /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 = ({
+ sider,
+ title,
+ headerExtra,
+ children,
+ maxContentWidth,
+}) => {
+ return (
+
+ {sider}
+
+ {(title || headerExtra) && (
+
+ {typeof title === 'string' ? (
+
+ {title}
+
+ ) : (
+ title
+ )}
+ {headerExtra && (
+
+ {headerExtra}
+
+ )}
+
+ )}
+
+
+ {children}
+
+
+
+
+ );
+};
+
+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 000000000000..a9f9a01b597e
--- /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, 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 = ({
+ emphasis = 'outlined',
+ collapsible = false,
+ defaultCollapsed = false,
+ extra,
+ children,
+ style,
+ styles,
+ ...rest
+}) => {
+ const [collapsed, setCollapsed] = useState(defaultCollapsed);
+
+ const collapseToggle = collapsible ? (
+ : }
+ onClick={() => setCollapsed((c) => !c)}
+ />
+ ) : null;
+
+ const mergedExtra = collapsible ? (
+
+ {extra}
+ {collapseToggle}
+
+ ) : (
+ extra
+ );
+
+ return (
+
+ {collapsible && collapsed ? null : children}
+
+ );
+};
+
+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 000000000000..d6308608de9e
--- /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 = {
+ 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 = {
+ standard: { height: 22, paddingInline: 10, fontSize: 12, lineHeight: '20px' },
+ small: { height: 18, paddingInline: 8, fontSize: 11, lineHeight: '16px' },
+};
+
+export interface ChipProps extends Omit {
+ /** 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 = ({
+ color = 'neutral',
+ variant = 'full',
+ size = 'standard',
+ selected = false,
+ style,
+ children,
+ ...rest
+}) => {
+ const palette = palettes[color];
+ const dims = sizeStyles[size];
+
+ return (
+
+ {variant === 'dot' && (
+
+ )}
+ {children}
+
+ );
+};
+
+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 000000000000..4a4de97c39cf
--- /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 = {
+ dashboard: ,
+ health: (
+
+ ),
+ settings: (
+
+ ),
+ docs: (
+
+ ),
+ service: (
+
+ ),
+ reports: (
+
+ ),
+ notifications: (
+
+ ),
+ user: (
+
+ ),
+ search: (
+
+ ),
+ close: (
+
+ ),
+ menu: (
+
+ ),
+ 'chevron-right': (
+
+ ),
+ 'chevron-left': (
+
+ ),
+ 'chevron-down': (
+
+ ),
+ 'chevron-up': (
+
+ ),
+ 'external-link': (
+
+ ),
+ copy: (
+
+ ),
+};
+
+export interface IconProps extends Omit, '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 = ({ name, size = 16, style, ...rest }) => (
+
+);
+
+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 000000000000..078683f207c9
--- /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 {
+ /** The icon to render (e.g. an `` 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 = ({
+ icon,
+ label,
+ tooltip,
+ size = 'standard',
+ type = 'text',
+ style,
+ ...rest
+}) => {
+ const dimension = size === 'large' ? 40 : 32;
+ const button = (
+
+ );
+
+ const tip = tooltip === undefined ? label : tooltip;
+ return tip ? {button} : 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 000000000000..dba2a175be80
--- /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 = ({
+ label,
+ value,
+ href,
+ layout = 'vertical',
+ labelWidth = 160,
+ copyable = false,
+ style,
+}) => {
+ const isHorizontal = layout === 'horizontal';
+
+ const labelNode = (
+
+ {label}
+
+ );
+
+ const valueNode = href ? (
+
+ {value}
+
+ ) : (
+
+ {value}
+
+ );
+
+ return (
+
+ {labelNode}
+ {valueNode}
+
+ );
+};
+
+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 000000000000..7c54c4636713
--- /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 = ({
+ title,
+ subtitle,
+ breadcrumb,
+ actions,
+ style,
+}) => {
+ return (
+
+ );
+};
+
+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 000000000000..39b8815c7698
--- /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 = ({
+ items,
+ logo,
+ collapsedLogo,
+ onHeaderChange,
+ collapsed: collapsedProp,
+ defaultCollapsed = false,
+ onCollapse,
+ width = '15%',
+ collapsedWidth = 56,
+}) => {
+ const location = useLocation();
+ const navigate = useNavigate();
+ const [internalCollapsed, setInternalCollapsed] = useState(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 (
+ }
+ >
+ {branding}
+
+
+ );
+};
+
+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 000000000000..cfaa21b50a68
--- /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 = ({
+ external = false,
+ size = 'standard',
+ style,
+ children,
+ ...rest
+}) => {
+ const scale = size === 'small' ? textStyles.bodySmall : textStyles.bodyStandard;
+ const externalProps = external ? { target: '_blank', rel: 'noopener noreferrer' } : {};
+
+ return (
+
+ {children}
+ {external && }
+
+ );
+};
+
+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 000000000000..ebdce680bf47
--- /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 = ({
+ leading,
+ title,
+ center,
+ actions,
+ height = 48,
+ style,
+}) => (
+
+
+ {leading}
+ {title && (
+
+ {title}
+
+ )}
+
+
+ {center &&
{center}
}
+
+
+ {actions}
+
+
+);
+
+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 000000000000..87e7994659cd
--- /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 000000000000..93b7f52399ca
--- /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 = ({ children, themeOverrides }) => {
+ const theme: ThemeConfig = themeOverrides
+ ? {
+ ...ozoneTheme,
+ ...themeOverrides,
+ token: { ...ozoneTheme.token, ...themeOverrides.token },
+ components: { ...ozoneTheme.components, ...themeOverrides.components },
+ }
+ : ozoneTheme;
+
+ return {children};
+};
+
+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 000000000000..9d573ad4dc75
--- /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 `` (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 209bc46478ef..8b73edf85954 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 000000000000..2034d93bd167
--- /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;
+
+/**
+ * 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 f38db2c72fa0..92a0773aa8c7 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 f85ee915d06b..e3027a092b2c 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/react@18.3.23)
+ react-router-dom:
+ specifier: ^7.3.0
+ version: 7.6.3(react-dom@18.3.1)(react@18.3.1)
typescript:
specifier: ~5.6.2
version: 5.6.3
@@ -2828,7 +2831,6 @@ packages:
/cookie@1.0.2:
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
engines: {node: '>=18'}
- dev: false
/copy-anything@2.0.6:
resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
@@ -6306,7 +6308,6 @@ packages:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-router: 7.6.3(react-dom@18.3.1)(react@18.3.1)
- dev: false
/react-router@7.6.3(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA==}
@@ -6322,7 +6323,6 @@ packages:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
set-cookie-parser: 2.7.1
- dev: false
/react@18.3.1:
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 db4c6d9b6797..000000000000
--- 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 65edfc83a88b..000000000000
--- 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>;
-};
-
-// Logo components using the favicon
-const expandedSidebarLogo = (
-
-
- Ozone
-
-);
-
-const collapsedSidebarLogo = (
-
-
-
-);
-
-const Sidebar: React.FC = ({ setHeader }) => {
- const [collapsed, setCollapsed] = useState(false);
- const [selectedKey, setSelectedKey] = useState(null);
- const location = useLocation();
-
- const navigationItems: MenuItem[] = [
- getNavMenuItem('Overview', 'Overview', '/', , undefined),
- getNavMenuItem('Configurations', 'Configurations', undefined, , [
- 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 (
- setCollapsed(value)}
- width={'15%'}
- trigger={}
- >
- {collapsed ? collapsedSidebarLogo : expandedSidebarLogo}
-
- );
-};
-
-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 5d5f1e35f1ae..000000000000
--- 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