Skip to content
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
eeca1f1
Add basic classes for ClaimFlag (partial impl)
VadimKovalenkoSNF Feb 26, 2026
c83b677
Optimize class assigment
VadimKovalenkoSNF Feb 26, 2026
fb4d282
Minor style refactoring
VadimKovalenkoSNF Feb 26, 2026
b32a24c
Dialog tooltip optimization
VadimKovalenkoSNF Feb 26, 2026
b738170
Pass embed config flag (secure measure)
VadimKovalenkoSNF Feb 26, 2026
ee87028
Clear timeout for TooltipDialog popup
VadimKovalenkoSNF Feb 26, 2026
6304eab
Clear timeout for DialogTooltip popup, adjust user accessibility
VadimKovalenkoSNF Feb 26, 2026
5e3984d
Connect closure status badge
VadimKovalenkoSNF Feb 26, 2026
e2a475e
Guard React.cloneElement against non-element children in interactive …
VadimKovalenkoSNF Feb 27, 2026
af374f6
Add unit tests
VadimKovalenkoSNF Feb 27, 2026
1b60b17
Fix lint issues
VadimKovalenkoSNF Feb 27, 2026
e07a9ed
Add proptypes to the closure report component, fix minor issues
VadimKovalenkoSNF Feb 27, 2026
5be8398
Fix unit test
VadimKovalenkoSNF Feb 27, 2026
a8ddc91
Merge branch 'main' into OSDEV-2374-implement-claim-banner-with-statu…
VadimKovalenkoSNF Feb 27, 2026
3234ffe
Merge branch 'main' into OSDEV-2374-implement-claim-banner-with-statu…
VadimKovalenkoSNF Feb 27, 2026
31761c9
Fix post-merge errors
VadimKovalenkoSNF Feb 27, 2026
249ba92
Refactor DialogTooltip to pass href as a separate component
VadimKovalenkoSNF Feb 28, 2026
ee7fda9
Refactor DialogTooltip keyboard navigation
VadimKovalenkoSNF Feb 28, 2026
bfc273e
Refactor ClaimFlag util functions
VadimKovalenkoSNF Feb 28, 2026
84f9d9b
Split ClaimFlag into smaller inner components
VadimKovalenkoSNF Feb 28, 2026
5ecf343
Fix lint issues
VadimKovalenkoSNF Feb 28, 2026
a630082
Add os id and data source section (partial impl)
VadimKovalenkoSNF Feb 28, 2026
43a4655
Adjust styles, add tooltip and toogle switch
VadimKovalenkoSNF Mar 2, 2026
4fd3cce
Align styles across components (partial impl)
VadimKovalenkoSNF Mar 2, 2026
173413a
Create frontend.md docs
VadimKovalenkoSNF Mar 2, 2026
ed9e9b4
Minor style fix, update tooltip text
VadimKovalenkoSNF Mar 2, 2026
89610eb
Merge branch 'main' into OSDEV-2375-location-name-os-id-understanging…
VadimKovalenkoSNF Mar 3, 2026
a111202
Fix lint issue
VadimKovalenkoSNF Mar 3, 2026
e080926
Update styles and layout, use rem units for body fonts
VadimKovalenkoSNF Mar 3, 2026
6aa0e18
Merge branch 'main' into OSDEV-2375-location-name-os-id-understanging…
VadimKovalenkoSNF Mar 3, 2026
84ba6ab
Add unit tests
VadimKovalenkoSNF Mar 3, 2026
11c9a02
Coercing embed to a boolean when passing it to ProductionLocationDeta…
VadimKovalenkoSNF Mar 3, 2026
154aa72
interactive attr is no longer set to MUI’s Tooltip
VadimKovalenkoSNF Mar 3, 2026
544ee14
Apply set of minor html fixes
VadimKovalenkoSNF Mar 3, 2026
df43954
Fix lint issues
VadimKovalenkoSNF Mar 3, 2026
bc34dc9
Fix icon data source section on small devices
VadimKovalenkoSNF Mar 3, 2026
52ff8ef
Update colors for the data source section
VadimKovalenkoSNF Mar 3, 2026
e1e90a3
Update release notes
VadimKovalenkoSNF Mar 3, 2026
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
156 changes: 156 additions & 0 deletions doc/frontend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Typography Guide

How to use typography across the application: which shared styles to use, how to combine them with MUI `Typography`, and when to use each.

---

## 1. Source of truth

- **File:** `src/react/src/util/typographyStyles.js`
- **Usage:** Call `getTypographyStyles(theme)` in your component's `withStyles` (or `makeStyles`) and spread the returned keys into your class objects; then pass those classes to MUI `Typography` via `className={classes.xyz}`.
- **Rule:** Prefer these tokens over hardcoding font sizes/weights so headings, labels, body text, and links stay consistent.
- **Body text size:** Use `1rem` for body and paragraph text (section description, bodyText, inline highlight). In this app the root font size is the browser default (typically 16px), so 1rem resolves accordingly.
- **MUI v3 Typography variants:** Use `component` for semantics (e.g. `component="h1"`) and `variant` for MUI’s styling. Valid variants are: `"display4"`, `"display3"`, `"display2"`, `"display1"`, `"headline"`, `"title"`, `"subheading"`, `"body2"`, `"body1"`, `"caption"`, `"button"`, `"srOnly"`, `"inherit"`. Do not use `variant="h1"` / `"h2"` / `"h3"` (they are not supported in MUI v3). Use `variant="headline"` for page title, `variant="title"` for major/section headings, `variant="subheading"` for sub-sections.

---

## 2. Semantic roles and which style to use

| Role | When to use | Style key | Typical MUI usage |
|------|-------------|-----------|-------------------|
| **Page title** | Main title of a page (e.g. facility/location name) | (custom from `formLabelTight`) | `<Typography component="h1" variant="headline" className={classes.title} />` |
| **Major section** | Big section blocks (e.g. claim intro) | (custom as needed) | `component="h2"`, `variant="title"` |
| **Section title** | Subsections ("Understanding Data Sources", "Partner Data", etc.) | `sectionTitle` | `component="h3"`, `variant="title"`, `className` with `...typography.sectionTitle` |
| **Field / UI label** | Form labels, bold labels ("OS ID:", "CLAIMED PROFILE", "Claimed", "Crowdsourced") | `formLabel` or `formLabelTight` | `component="span"` or `"label"`, `variant="body1"`, `className` with `...typography.formLabelTight` |
| **Body / paragraph** | Normal paragraphs, descriptions, "Show more" labels | `bodyText` | `component="p"` or `"span"`, `variant="body1"`, `className` with `...typography.bodyText` |
| **Section description** | Intro or explanatory block under a section | `sectionDescription` | `component="p"`, `variant="body1"`, `className` with `...typography.sectionDescription` |
| **Inline highlight** | Short emphasized bits (name, date, ID value) inline in text | `inlineHighlight` | `component="span"`, `className` with `...typography.inlineHighlight` |

---

## 3. Style attributes (from `typographyStyles.js`)

- **formLabel / formLabelRoot / formLabelTight**
Base: `fontSize: '21px'`, `fontWeight: 600`.
`formLabel` adds `margin: '24px 0 8px 0'`; `formLabelRoot` uses `marginTop: 0`, `marginBottom: '8px'`; `formLabelTight` has no extra margin.
Use for field labels and strong short labels.

- **sectionTitle**
`fontSize: '24px'`, `fontWeight: theme.typography.fontWeightSemiBold`, `marginTop: '25px'`.
Use for h3-level section titles.

- **sectionDescription**
`fontSize: '1rem'`, `marginBottom: '10px'`.
Use for the first paragraph or intro under a section.

- **bodyText**
`fontSize: '1rem'`, `color: theme.palette.text.secondary`.
Use for regular body and secondary text.

- **inlineHighlight**
`fontSize: '1rem'`, `fontWeight: 500`, `color: theme.palette.text.primary`, `display: 'inline'`.
Use for emphasized inline pieces (e.g. facility name, date, OS ID value).

---

## 4. Headings

- **h1 – Page title**
One per page (e.g. facility/location name).
Example: `component="h1"`, `variant="headline"`, `className` from styles that extend `formLabelTight` (e.g. 28px, 700 for prominence).

- **h2 – Major section**
Top-level sections (e.g. claim intro).
`component="h2"`, `variant="title"`, plus custom class if needed.

- **h3 – Section title**
Subsections ("Understanding Data Sources", "Partner Data", "Interactive map", etc.).
`component="h3"`, `variant="title"`, `className` with `...typography.sectionTitle` (and margin overrides as needed, e.g. `marginTop: 0`).

- **h4 / h5 / h6**
Use sparingly for sub-subsections (e.g. ClaimFlag uses `h4` for "CLAIMED PROFILE").
Prefer a class that extends `formLabelTight` or `sectionTitle` so sizing stays consistent. Use MUI v3 variants such as `"title"` or `"subheading"` (not `"h4"`/`"h5"`/`"h6"`).

---

## 5. Regular text

- **Paragraphs:**
`component="p"`, `variant="body1"`, and a class that includes `...typography.bodyText` (and optionally `...typography.sectionDescription` for intro blocks).
Body text uses `fontSize: '1rem'`. Override `fontSize` or `marginBottom` only when needed.

- **Inline text / labels:**
`component="span"`, `variant="body1"`, and either `formLabelTight` (labels) or `bodyText` (secondary inline).

---

## 6. Links

- **Color:** `theme.palette.primary.main`.
- **Implementation:**
Use `<a href="..." className={classes.link}>` or `<Link to="..." className={classes.link}>` with a style like `link: { color: theme.palette.primary.main }`.
For "Learn more →" in tooltips, inline `style={{ color: 'white' }}` is used for contrast on dark tooltip background; elsewhere use the theme primary color.
- **Optional:** Add `textDecoration: 'none'` and `'&:hover': { textDecoration: 'underline' }` for inline links (e.g. in DataSourcesInfo `learnMoreLink`).

---

## 7. Usage pattern

1. In the component's `styles.js`:
`import { getTypographyStyles } from '../../path/to/typographyStyles';`
then `const typography = getTypographyStyles(theme);` and use `...typography.sectionTitle`, etc., in your class objects.

2. In the JSX:
Use MUI `Typography` with both:
- **Semantic props:** `component` (e.g. `component="h3"`) for the HTML element and `variant` for MUI v3 styling (e.g. `variant="title"` or `variant="subheading"` — use only valid MUI v3 variants, not `"h1"`/`"h2"`/`"h3"`).
- **Visual style:** `className={classes.yourClass}` where `yourClass` spreads the right typography token and any local overrides.

3. Keep **one** clear hierarchy per page: one h1, then h2/h3 as needed; use the table in §2 to pick the right style key and component/variant.

---

## 8. Quick reference

| Use case | component | variant | Typography style key |
|----------|-----------|--------|----------------------|
| Page title (e.g. facility name) | h1 | headline | formLabelTight (custom size/weight) |
| Section title | h3 | title | sectionTitle |
| Field / subsection label | span | body1 | formLabelTight |
| Paragraph / body | p | body1 | bodyText or sectionDescription |
| Inline emphasis (name, ID, date) | span | — | inlineHighlight |
| Links | a or Link | — | color: theme.palette.primary.main |

---

## 9. Container styles (commonStyles)

- **File:** `src/react/src/components/ProductionLocation/commonStyles.js`
- **Purpose:** Shared base styles for section containers (e.g. cards, content blocks) so background and shadow stay consistent across the Production Location UI.

### What it provides

`commonStyles(theme)` returns an object with a `container` key:

- **container:** `backgroundColor: theme.palette.background.white`, `boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)'`

### How to use it

Spread `commonStyles(theme).container` into your own container class and add any overrides (margin, padding, etc.). This keeps the same background and shadow while letting each component set its own spacing.

**Import in your styles file:**

```js
import commonStyles from '../../commonStyles'; // adjust path to your component
```

**Example – extend and override:**

```js
container: Object.freeze({
...commonStyles(theme).container,
marginBottom: spacing * 3,
padding: '20px 20px 20px 36px',
}),
```

Here the container gets the shared `backgroundColor` and `boxShadow` from `commonStyles`, plus component-specific `marginBottom` and `padding`. Use this pattern for any Production Location section that should look like a card (e.g. LocationTitle, DataSourcesInfo).
101 changes: 101 additions & 0 deletions src/react/src/__tests__/components/DataSourcesInfo.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import ProductionLocationDetailsDataSourcesInfo from '../../components/ProductionLocation/Heading/DataSourcesInfo/DataSourcesInfo';

jest.mock('../../components/Contribute/DialogTooltip', () => {
function MockDialogTooltip({ childComponent }) {
return <>{childComponent}</>;
}
return MockDialogTooltip;
});

const theme = createMuiTheme();

const renderDataSourcesInfo = (props = {}) =>
render(
<MuiThemeProvider theme={theme}>
<ProductionLocationDetailsDataSourcesInfo {...props} />
</MuiThemeProvider>,
);

describe('ProductionLocation DataSourcesInfo', () => {
test('renders without crashing', () => {
renderDataSourcesInfo();

expect(
screen.getByRole('heading', { name: 'Understanding Data Sources' }),
).toBeInTheDocument();
});

test('renders section title "Understanding Data Sources"', () => {
renderDataSourcesInfo();

expect(
screen.getByRole('heading', { level: 3, name: 'Understanding Data Sources' }),
).toBeInTheDocument();
});

test('shows "Open" label when toggle is off', () => {
renderDataSourcesInfo();

expect(screen.getByText('Open')).toBeInTheDocument();
expect(screen.queryByText('Close')).not.toBeInTheDocument();
});

test('shows "Close" label when toggle is switched on', () => {
renderDataSourcesInfo();

const switchControl = screen.getByRole('checkbox', {
name: /show extra info under each data source/i,
});
fireEvent.click(switchControl);

expect(screen.getByText('Close')).toBeInTheDocument();
expect(screen.queryByText('Open')).not.toBeInTheDocument();
});

test('renders all three data source items', () => {
renderDataSourcesInfo();

expect(screen.getByText('Claimed')).toBeInTheDocument();
expect(screen.getByText('Crowdsourced')).toBeInTheDocument();
expect(screen.getByText('Partner Data')).toBeInTheDocument();
});

test('toggle switch shows subsection text when opened', () => {
renderDataSourcesInfo();

expect(
screen.queryByText(/General information & operational details submitted by production location/),
).not.toBeInTheDocument();

const switchControl = screen.getByRole('checkbox', {
name: /show extra info under each data source/i,
});
fireEvent.click(switchControl);

expect(
screen.getByText(/General information & operational details submitted by production location/),
).toBeInTheDocument();
});

test('renders info button for data sources tooltip', () => {
renderDataSourcesInfo();

expect(
screen.getByRole('button', {
name: /more information about data sources/i,
}),
).toBeInTheDocument();
});

test('applies custom className when provided', () => {
const { container } = renderDataSourcesInfo({
className: 'custom-class',
});

const wrapper = container.querySelector('.custom-class');
expect(wrapper).toBeInTheDocument();
});
});
115 changes: 115 additions & 0 deletions src/react/src/__tests__/components/LocationTitle.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import ProductionLocationDetailsTitle from '../../components/ProductionLocation/Heading/LocationTitle/LocationTitle';

jest.mock('react-toastify', () => ({
toast: jest.fn(),
}));

jest.mock('../../components/CopySearch', () => {
function MockCopySearch({ children }) {
return <>{children}</>;
}
return MockCopySearch;
});

jest.mock('../../components/Contribute/DialogTooltip', () => {
function MockDialogTooltip({ childComponent }) {
return <>{childComponent}</>;
}
return MockDialogTooltip;
});

const theme = createMuiTheme();

const renderLocationTitle = (props = {}) =>
render(
<MuiThemeProvider theme={theme}>
<ProductionLocationDetailsTitle data={null} {...props} />
</MuiThemeProvider>,
);

describe('ProductionLocation LocationTitle', () => {
test('renders without crashing when data is null', () => {
renderLocationTitle({ data: null });

expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument();
expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent(/OS ID:/);
});

test('renders location name from data.properties.name', () => {
const data = {
properties: {
name: 'Test Facility Name',
os_id: 'CN2021250D1DTN7',
},
};

renderLocationTitle({ data });

expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Test Facility Name');
});

test('renders OS ID from data.properties.os_id', () => {
const data = {
properties: {
name: 'Test Facility',
os_id: 'CN2021250D1DTN7',
},
};

renderLocationTitle({ data });

expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent('OS ID: CN2021250D1DTN7');
});

test('shows Copy Link and Copy OS ID buttons when os_id is present', () => {
const data = {
properties: {
name: 'Test Facility',
os_id: 'CN2021250D1DTN7',
},
};

renderLocationTitle({ data });

expect(screen.getByRole('button', { name: /copy link/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /copy os id/i })).toBeInTheDocument();
});

test('does not show copy buttons when os_id is missing', () => {
const data = {
properties: {
name: 'Test Facility',
},
};

renderLocationTitle({ data });

expect(screen.queryByRole('button', { name: /copy link/i })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: /copy os id/i })).not.toBeInTheDocument();
});

test('does not show copy buttons when data is null', () => {
renderLocationTitle({ data: null });

expect(screen.queryByRole('button', { name: /copy link/i })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: /copy os id/i })).not.toBeInTheDocument();
});

test('renders info button for OS ID tooltip', () => {
const data = {
properties: {
name: 'Test',
os_id: 'US123',
},
};

renderLocationTitle({ data });

expect(
screen.getByRole('button', { name: /more information about os id/i }),
).toBeInTheDocument();
});
});
1 change: 0 additions & 1 deletion src/react/src/components/Contribute/DialogTooltip.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ const DialogTooltip = ({
<Tooltip
enterDelay={interactive ? 0 : 200}
leaveDelay={interactive ? 0 : 200}
interactive={interactive}
open={interactive ? open : undefined}
onClose={interactive ? () => setOpen(false) : undefined}
title={titleContent}
Expand Down
Loading