Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions ui/.cursor/rules/percona_ui-styling.mdc
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
---
description: "Percona UI + PMM UI styling — MUI + @percona/percona-ui, theme tokens, layout conventions, what not to do"
description: "Percona UI + PMM UI styling — MUI + @percona/peak-ui, theme tokens, layout conventions, what not to do"
alwaysApply: true
---

## Stack order

1. **MUI** (mostly `@mui/material`, `@mui/icons-material`, `@mui/x-date-pickers` — though other MUI dependencies may also be in use) is the base component and styling API. Use MUI primitives for layout and composition.
2. **`@percona/percona-ui`** is the Percona design layer on top of MUI (theme, branded components, tables, dialogs). Prefer exports from `@percona/percona-ui` when they wrap or standardize behavior (e.g. `Table`, `Dialog`, `ThemeContextProvider`, `pmmThemeOptions`, `NotistackMuiSnackbar`, `primitives`).
2. **`@percona/peak-ui`** is the Percona design layer on top of MUI (theme, branded components, tables, dialogs). Prefer exports from `@percona/peak-ui` when they wrap or standardize behavior (e.g. `Table`, `Dialog`, `ThemeContextProvider`, `pmmThemeOptions`, `NotistackMuiSnackbar`, `primitives`).

## Do not introduce

- Do not introduce other CSS/UI frameworks (Tailwind, Bootstrap, styled-components, etc.) or ad-hoc global CSS that fights the theme.
- Do not introduce hard-coded colors, font families, or spacing that bypass the theme. Prefer `sx` with `theme.palette`, `theme.spacing`, breakpoints, and MUI `Typography` variants.
- Do not introduce custom component libraries outside MUI + `@percona/percona-ui` unless explicitly requested.
- Do not introduce custom component libraries outside MUI + `@percona/peak-ui` unless explicitly requested.

## Theming and color mode

- The app root uses `ThemeContextProvider` with `pmmThemeOptions` from `@percona/percona-ui` — do not replace with a separate `ThemeProvider` or duplicate theme objects in feature code.
- The app root uses `ThemeContextProvider` with `pmmThemeOptions` from `@percona/peak-ui` — do not replace with a separate `ThemeProvider` or duplicate theme objects in feature code.
- Use `ColorModeContext` / existing hooks (e.g. `useColorMode` in `hooks/theme.ts`) for light/dark; avoid direct `document` or `localStorage` theme hacks.

## Layout and PMM consistency
Expand All @@ -31,8 +31,8 @@ alwaysApply: true

## When something is missing

- If a token or component behavior should be shared across products, explain it to them, plan a change, present it as a proposal to extend **`@percona/percona-ui`** (theme / components) rather than embedding one-off design in PMM only.
- If unsure whether a primitive exists in `percona-ui`, check that package or Storybook before inventing a parallel implementation in PMM.
- If a token or component behavior should be shared across products, explain it to them, plan a change, present it as a proposal to extend **`@percona/peak-ui`** (theme / components) rather than embedding one-off design in PMM only.
- If unsure whether a primitive exists in `peak-ui`, check that package or Storybook before inventing a parallel implementation in PMM.

## Examples

Expand All @@ -44,7 +44,7 @@ alwaysApply: true
<div className="tailwind-p-4 text-gray-500">

// Good — branded table from design system
import { Table } from '@percona/percona-ui';
import { Table } from '@percona/peak-ui';

// Bad — pulling in another data-table library for the same job
```
12 changes: 6 additions & 6 deletions ui/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The UI uses a **Yarn workspaces + Turborepo** monorepo with three packages:
| **TypeScript** | Type-safe development |
| **Vite** | Dev server and production build (main app) |
| **MUI (Material UI)** | Component library |
| **@percona/percona-ui** | Percona's shared UI component library and theme |
| **@percona/peak-ui** | Percona's shared UI component library and theme |
| **TanStack Query (React Query)** | Server state management (API caching, mutations) |
| **React Context** | UI/auth state (AuthProvider, SettingsProvider, etc.) |
| **Vitest** | Unit testing (main app) |
Expand Down Expand Up @@ -75,7 +75,7 @@ Query keys follow the pattern: `['domain:action', params]` (e.g., `['services:li

### UI/Auth State (React Context)

Providers are composed in `Providers.tsx`, all wrapped by `ThemeContextProvider` (theme from `@percona/percona-ui`) in `App.tsx`:
Providers are composed in `Providers.tsx`, all wrapped by `ThemeContextProvider` (theme from `@percona/peak-ui`) in `App.tsx`:

- `AuthProvider` — authentication state
- `UserProvider` — current user info
Expand All @@ -91,10 +91,10 @@ API calls are organized in `src/api/` using axios. Each API module provides type

## UI Component Library

`@percona/percona-ui` ("Peak Design") is PMM's shared component library — MUI v7-based themed components, design tokens, and `react-hook-form`-integrated inputs, exposing a `pmmThemeOptions` theme variant. **Browse the catalog before hand-rolling a component:**
`@percona/peak-ui` ("Peak Design") is PMM's shared component library — MUI v7-based themed components, design tokens, and `react-hook-form`-integrated inputs, exposing a `pmmThemeOptions` theme variant. **Browse the catalog before hand-rolling a component:**

- **Storybook (component catalog):** https://percona.github.io/percona-ui
- **Source & theme options:** https://github.com/percona/percona-ui
- **Storybook (component catalog):** https://percona.github.io/peak-ui
- **Source & theme options:** https://github.com/percona/peak-ui

The app is wrapped in `ThemeContextProvider` (see `App.tsx`); style with the theme-aware `sx` prop rather than ad-hoc CSS.

Expand All @@ -104,7 +104,7 @@ The app is wrapped in `ThemeContextProvider` (see `App.tsx`); style with the the

- Use TanStack Query (`useQuery`, `useMutation`) for all server state
- Create custom hooks per API domain in `src/hooks/`
- Use MUI and `@percona/percona-ui` components for consistent styling — browse the [Storybook catalog](https://percona.github.io/percona-ui) before building a component from scratch
- Use MUI and `@percona/peak-ui` components for consistent styling — browse the [Storybook catalog](https://percona.github.io/peak-ui) before building a component from scratch
- Use TypeScript strict mode — define types in `src/types/`
- Co-locate test files next to components (`*.test.tsx`)
- Use `CrossFrameMessenger` for communication with the Grafana iframe
Expand Down
18 changes: 9 additions & 9 deletions ui/apps/pmm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ See the [PMM Documentation](https://www.percona.com/doc/percona-monitoring-and-m

See detailed information about prerequisites and setup [here](../../README.md)

# Locally testing @percona/percona-ui
# Locally testing @percona/peak-ui

`@percona/percona-ui` is a normal npm dependency (see `package.json`). To iterate on the library and PMM together, `yarn link` an in-progress checkout against this app. The recipe depends on whether you're running `make dev` on the host or `make run-ui` inside the PMM devcontainer.
`@percona/peak-ui` is a normal npm dependency (see `package.json`). To iterate on the library and PMM together, `yarn link` an in-progress checkout against this app. The recipe depends on whether you're running `make dev` on the host or `make run-ui` inside the PMM devcontainer.

In both cases:

- Check out the lib from https://github.com/percona/percona-ui.
- Check out the lib from https://github.com/percona/peak-ui.
- After linking, **uncomment** the `exclude` block in `vite.config.ts` so Vite stops pre-bundling the linked package:
```ts
// exclude: ['@percona/percona-ui'],
// exclude: ['@percona/peak-ui'],
```
- When you're done, **comment the `exclude` block back**, then from `ui/apps/pmm`:
```bash
yarn unlink @percona/percona-ui
yarn unlink @percona/peak-ui
yarn install --force
```
- Restarting the dev server between linking/unlinking is advised.

## Host-local flow (`make dev`)

- From the lib folder on the host: `pnpm build:watch` and `yarn link`.
- From `ui/apps/pmm` on the host: `yarn link @percona/percona-ui`.
- From `ui/apps/pmm` on the host: `yarn link @percona/peak-ui`.
- Any change in the lib triggers a rebuild and HMR in PMM.

## Devcontainer flow (`make run-ui`)
Expand All @@ -41,16 +41,16 @@ The host's `yarn link` global registry isn't visible inside the devcontainer, so

**Bind-mount a host checkout** — keeps the lib editable from your host IDE:

1. Clone `percona-ui` alongside `pmm` on the host (so it sits at `../percona-ui` relative to the repo root).
1. Clone `peak-ui` alongside `pmm` on the host (so it sits at `../peak-ui` relative to the repo root).
2. Uncomment the volume mapping in `docker-compose.dev.yml`
3. Run `make env-up` then `make env` from the host.
4. Inside the container:
```bash
cd /root/go/src/github.com/percona/percona-ui
cd /root/go/src/github.com/percona/peak-ui
yarn install
yarn link
pnpm build:watch & # leave the watcher running
cd /root/go/src/github.com/percona/pmm/ui/apps/pmm
yarn link @percona/percona-ui
yarn link @percona/peak-ui
```
5. Uncomment the `exclude` block in `vite.config.ts`, then back at the repo root: `make run-ui`.
2 changes: 1 addition & 1 deletion ui/apps/pmm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@mui/icons-material": "^7.3.7",
"@mui/material": "^7.3.7",
"@mui/x-date-pickers": "^7.5.0",
"@percona/percona-ui": "1.0.23",
"@percona/peak-ui": "1.0.25",
"@pmm/shared": "*",
"@reactour/tour": "^3.8.0",
"@tanstack/react-query": "^5.45.1",
Expand Down
2 changes: 1 addition & 1 deletion ui/apps/pmm/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
ThemeContextProvider,
pmmThemeOptions,
NotistackMuiSnackbar,
} from '@percona/percona-ui';
} from '@percona/peak-ui';
import { ThemeClass } from 'components/theme-class';
import { useEffect } from 'react';
import type { ComponentType } from 'react';
Expand Down
3 changes: 2 additions & 1 deletion ui/apps/pmm/src/components/app-bar/AppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Breadcrumbs } from 'components/breadcrumbs';
import { PMM_SUPPORT_URL } from 'lib/constants';
import { Messages } from './AppBar.messages';
import { HomeLink } from 'components/home-link';
import Help from '@mui/icons-material/Help';
import { Icon } from 'components/icon';

export const AppBar = () => (
Expand Down Expand Up @@ -39,7 +40,7 @@ export const AppBar = () => (
data-testid="appbar-support-link"
>
<Stack gap={1} direction="row" alignItems="center">
<Icon name="help-fill" />
<Help />
<Typography>{Messages.support}</Typography>
</Stack>
</Link>
Expand Down
2 changes: 1 addition & 1 deletion ui/apps/pmm/src/components/details-pane/DataPoint.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Tooltip } from '@percona/percona-ui';
import { Tooltip } from '@percona/peak-ui';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import { FC, PropsWithChildren } from 'react';
import Box from '@mui/material/Box';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Card } from '@percona/percona-ui';
import { Card } from '@percona/peak-ui';
import { Page } from 'components/page';
import { useUser } from 'contexts/user';
import { FC } from 'react';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useTheme } from '@mui/material';
import Box from '@mui/material/Box';
import { Icon } from 'components/icon';
import { CirclesExtIcon } from '@percona/peak-ui';
import { FC } from 'react';
import { getStyles } from './HighAvailabilityIcon.styles';
import { HighAvailabilityIconProps } from './HighAvailabilityIcon.types';
Expand All @@ -13,7 +14,7 @@ const HighAvailabilityIcon: FC<HighAvailabilityIconProps> = ({ health }) => {

return (
<Box sx={{ position: 'relative' }}>
<Icon data-testid="ha-icon" name="cluster" />
<CirclesExtIcon data-testid="ha-icon" />
{haIcon && (
<Box
data-testid="ha-health-icon"
Expand Down
60 changes: 2 additions & 58 deletions ui/apps/pmm/src/components/icon/Icon.constants.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,20 @@
export const DYNAMIC_ICON_IMPORT_MAP = {
account: () => import('icons/account.svg?react'),
alerts: () => import('icons/alerts.svg?react'),
backups: () => import('icons/backups.svg?react'),
'browse-dashboards': () => import('icons/browse-dashboards.svg?react'),
configuration: () => import('icons/configuration.svg?react'),
dashboards: () => import('icons/dashboards.svg?react'),
encrypted: () => import('icons/users-and-access.svg?react'),
explore: () => import('icons/explore.svg?react'),
haproxy: () => import('icons/haproxy.svg?react'),
help: () => import('icons/help.svg?react'),
'help-fill': () => import('icons/help-fill.svg?react'),
'high-availability': () => import('icons/high-availability.svg?react'),
home: () => import('icons/home.svg?react'),
intelligence: () => import('icons/percona-intelligence.svg?react'),
inventory: () => import('icons/inventory.svg?react'),
'percona-mo': () => import('icons/percona-mo.svg?react'),
'percona-my': () => import('icons/percona-my.svg?react'),
node: () => import('icons/home.svg?react'),
overview: () => import('icons/overview.svg?react'),
'percona-po': () => import('icons/percona-po.svg?react'),
'pmm-titled': () => import('icons/pmm-titled.svg?react'),
'pmm-titled-outlined': () => import('icons/pmm-titled-outlined.svg?react'),
'pmm-rounded': () => import('icons/pmm-rounded.svg?react'),
proxysql: () => import('icons/proxysql.svg?react'),
qan: () => import('icons/qan.svg?react'),
'operating-system': () => import('icons/operating-system.svg?react'),
'sign-out': () => import('icons/sign-out.svg?react'),
summary: () => import('icons/summary.svg?react'),
'theme-dark': () => import('icons/theme-dark.svg?react'),
'theme-light': () => import('icons/theme-light.svg?react'),
'graph-6': () => import('icons/graph-6.svg?react'),
percona: () => import('icons/percona.svg?react'),
check: () => import('icons/check.svg?react'),
'knowledge-base': () => import('icons/knowledge-base.svg?react'),
search: () => import('icons/search.svg?react'),
'percona-va': () => import('icons/percona-va.svg?react'),
'my-organization': () => import('icons/my-organization.svg?react'),
memory: () => import('icons/memory.svg?react'),
network: () => import('icons/network.svg?react'),
'real-time-database-off': () =>
import('icons/real-time-database-off.svg?react'),
cluster: () => import('icons/cluster.svg?react'),
'status-at-risk': () => import('icons/status-at-risk.svg?react'),
'status-down': () => import('icons/status-down.svg?react'),
'status-updating': () => import('icons/status-updating.svg?react'),
'arrow-link': () => import('icons/arrow-link.svg?react'),
'bottom-panel-close': () => import('icons/bottom-panel-close.svg?react'),
'collapse-content': () => import('icons/collapse-content.svg?react'),
'expand-content': () => import('icons/expand-content.svg?react'),
'dynamic-feed': () => import('icons/dynamic-feed.svg?react'),
'bottom-panel-open': () => import('icons/bottom-panel-open.svg?react'),
'electric-bolt': () => import('icons/electric-bolt.svg?react'),
'electric-bolt-off': () => import('icons/electric-bolt-off.svg?react'),
'play-arrow': () => import('icons/play-arrow.svg?react'),
pause: () => import('icons/pause.svg?react'),
refresh: () => import('icons/refresh.svg?react'),
'file-download': () => import('icons/file-download.svg?react'),
// todo: move to percona-ui
// todo: move to peak-ui
'emergency-home': () => import('icons/emergency-home.svg?react'),
// todo: move to percona-ui
// todo: move to peak-ui
'chat-info-outlined': () => import('icons/chat-info-outlined.svg?react'),
};

export const VIEWBOX_MAP: Partial<
Record<keyof typeof DYNAMIC_ICON_IMPORT_MAP, string>
> = {
percona: '0 0 180 155',
'knowledge-base': '0 0 28 28',
'pmm-rounded': '0 0 160 160',
'pmm-titled': '0 0 141 48',
'pmm-titled-outlined': '0 0 252 113',
'real-time-database-off': '0 0 192 192',
'electric-bolt': '0 0 20 20',
'electric-bolt-off': '0 0 20 20',
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { NavItemProps } from './SidebarNavItem.types';
import { NavItem as NavTreeItem } from 'types/navigation.types';
import { collapseClasses } from '@mui/material/Collapse';
import { MemoryRouterProps } from 'react-router-dom';
import { ThemeContextProvider, pmmThemeOptions } from '@percona/percona-ui';
import { ThemeContextProvider, pmmThemeOptions } from '@percona/peak-ui';

const TEST_NAV_TREE: NavTreeItem = {
id: 'level-0',
Expand Down Expand Up @@ -150,7 +150,7 @@ describe('SidebarNavItem', () => {
it('shows dot on root if children has a badge and is hidden', async () => {
const item: NavTreeItem = {
id: 'with-badge',
icon: 'home',
icon: 'pmm-rounded',
url: '/root',
children: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from './SidebarNavItem.utils';
import { getStyles } from './SidebarNavItem.styles';
import { useTheme } from '@mui/material/styles';
import { NavItem } from '@percona/percona-ui';
import { NavItem } from '@percona/peak-ui';
import ListItemText from '@mui/material/ListItemText';
import Stack from '@mui/material/Stack';
import ListItem from '@mui/material/ListItem';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const TestMemoizedIcon = memo(TestIcon);

describe('NavItemIcon', () => {
it('should render an icon (string)', () => {
render(<NavItemIcon icon="search" />);
render(<NavItemIcon icon="pmm-rounded" />);
expect(screen).not.toBeNull();
});

Expand Down
Loading
Loading