Skip to content
Open
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
23 changes: 10 additions & 13 deletions src/community/components/button-content/button-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import { Icon, IconProps } from '../../../tedi/components/base/icon/icon';
import { Spinner } from '../../../tedi/components/loaders/spinner/spinner';
import Print from '../../../tedi/components/misc/print/print';
import { AllowedHTMLTags, PolymorphicComponentPropWithRef, PolymorphicRef } from '../../helpers/polymorphic/types';
import { IntentionalAny } from '../../types';
import { ButtonColor, ButtonType } from '../button/button';
Expand Down Expand Up @@ -94,7 +93,7 @@
{
children,
as,
text,

Check warning on line 96 in src/community/components/button-content/button-content.tsx

View workflow job for this annotation

GitHub Actions / build

'text' is defined but never used. Allowed unused args must match /^_/u
className,
visualType = 'primary',
color = 'default',
Expand All @@ -107,7 +106,7 @@
isActive,
isLoading = false,
noStyle,
renderWrapperElement,

Check warning on line 109 in src/community/components/button-content/button-content.tsx

View workflow job for this annotation

GitHub Actions / build

'renderWrapperElement' is defined but never used. Allowed unused args must match /^_/u
fullWidth,
onClick,
...rest
Expand Down Expand Up @@ -168,18 +167,16 @@
};

return (
<Print visibility="hide">
<Component
data-name="button-content"
{...rest}
aria-disabled={isLoading || rest['aria-disabled']}
onClick={onClickHandler}
ref={ref}
className={BEM}
>
{!noStyle ? renderContent() : children}
</Component>
</Print>
<Component
data-name="button-content"
{...rest}
aria-disabled={isLoading || rest['aria-disabled']}
onClick={onClickHandler}
ref={ref}
className={BEM}
>
{!noStyle ? renderContent() : children}
</Component>
);
}
);
Expand Down
43 changes: 23 additions & 20 deletions src/community/components/button/button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { forwardRef } from 'react';

import Print from '../../../tedi/components/misc/print/print';
import { PolymorphicRef } from '../../helpers/polymorphic/types';
import { IntentionalAny } from '../../types';
import ButtonContent, { ButtonContentProps } from '../button-content/button-content';
Expand Down Expand Up @@ -54,26 +55,28 @@ const InternalButton = forwardRef(
const ComponentAs = as || 'button';

return (
<ButtonContent
data-name="button"
{...(rest as IntentionalAny)}
type={type || 'button'}
formNoValidate={formNoValidate ?? type === 'submit' ? true : undefined}
ref={ref}
as={ComponentAs}
visualType={visualType}
color={color}
size={size}
icon={icon}
iconLeft={iconLeft}
iconRight={iconRight}
underline={underline}
isHovered={isHovered}
isActive={isActive}
noStyle={noStyle}
>
{children}
</ButtonContent>
<Print visibility="hide">
<ButtonContent
data-name="button"
{...(rest as IntentionalAny)}
type={type || 'button'}
formNoValidate={formNoValidate ?? type === 'submit' ? true : undefined}
ref={ref}
as={ComponentAs}
visualType={visualType}
color={color}
size={size}
icon={icon}
iconLeft={iconLeft}
iconRight={iconRight}
underline={underline}
isHovered={isHovered}
isActive={isActive}
noStyle={noStyle}
>
{children}
</ButtonContent>
</Print>
);
}
);
Expand Down
19 changes: 7 additions & 12 deletions src/tedi/components/buttons/button-content/button-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { AllowedHTMLTags, PolymorphicComponentPropWithRef, PolymorphicRef } from
import { UnknownType } from '../../../types/commonTypes';
import { Icon, IconWithoutBackgroundProps } from '../../base/icon/icon';
import { Spinner } from '../../loaders/spinner/spinner';
import { Print } from '../../misc/print/print';
import { Tooltip } from '../../overlays/tooltip';
import { ButtonColor, ButtonType } from '../button/button';
import styles from './button-content.module.scss';
Expand Down Expand Up @@ -214,17 +213,13 @@ const InternalButtonContent = forwardRef(
</Component>
);

return (
<Print visibility="hide">
{showTooltip && isIconOnly && buttonText ? (
<Tooltip ariaHidden>
<Tooltip.Trigger>{buttonElement}</Tooltip.Trigger>
<Tooltip.Content>{buttonText}</Tooltip.Content>
</Tooltip>
) : (
buttonElement
)}
</Print>
return showTooltip && isIconOnly && buttonText ? (
<Tooltip ariaHidden>
<Tooltip.Trigger>{buttonElement}</Tooltip.Trigger>
<Tooltip.Content>{buttonText}</Tooltip.Content>
</Tooltip>
) : (
buttonElement
);
}
);
Expand Down
5 changes: 5 additions & 0 deletions src/tedi/components/buttons/button/button.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,9 @@ describe('Button component', () => {
fireEvent.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});

it('is hidden when printing', () => {
render(<Button>Save</Button>);
expect(screen.getByRole('button', { name: 'Save' })).toHaveClass('no-print');
});
});
23 changes: 13 additions & 10 deletions src/tedi/components/buttons/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { forwardRef } from 'react';
import { BreakpointSupport, useBreakpointProps } from '../../../helpers';
import { PolymorphicRef } from '../../../helpers/polymorphic/types';
import { UnknownType } from '../../../types/commonTypes';
import { Print } from '../../misc/print/print';
import ButtonContent, { ButtonContentProps } from '../button-content/button-content';

export type ButtonType = 'primary' | 'secondary' | 'neutral' | 'link';
Expand Down Expand Up @@ -42,16 +43,18 @@ const ButtonComponent = forwardRef(
const ComponentAs = as || 'button';

return (
<ButtonContent
data-name="button"
{...(rest as UnknownType)}
type={type}
formNoValidate={formNoValidate}
ref={ref}
as={ComponentAs}
>
{children}
</ButtonContent>
<Print visibility="hide">
<ButtonContent
data-name="button"
{...(rest as UnknownType)}
type={type}
formNoValidate={formNoValidate}
ref={ref}
as={ComponentAs}
>
{children}
</ButtonContent>
</Print>
);
}
);
Expand Down
53 changes: 35 additions & 18 deletions src/tedi/components/misc/print/printing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@ import * as PrintStories from './print.stories';

# Printing in TEDI-Ready

Components and layouts include built-in optimizations for print media (via `@media print` styles). This ensures clean, readable output when users trigger print preview or print to PDF.
TEDI components include print styles for browser printing and saving as PDF.
These styles apply automatically. Use the `<Print>` component to customise
print visibility and page breaks.

## Global Print Behaviors (Applied Automatically)

- Text colors and many components are converted to grayscale for better ink economy and readability.
- Most background colors are forced to white (or transparent) to reduce ink usage.
- Links and selected text colours are greyscaled. Individual components also define their own print styles.
- The page uses a light neutral background. Core sets `print-color-adjust: exact`
to ask browsers to preserve the colours defined by the print styles.
- Base font size is set to **14px**, with proportional scaling for headings, spacing, and line heights.
- Interactive or non-essential elements are **hidden by default** in print:
- Buttons
- Links/Anchors (unless overridden)
- Header, Footer
- Breadcrumbs
- Sidenav / Navigation
- Links and anchors **remain visible when printing**, preserving linked text in the document.
- Breadcrumbs **remain visible when printing** — the trail shows where the printed page came from.
(The older community `Breadcrumbs` still hides itself.)
- Expandable components are **expanded by default**:
- Accordion
- Collapse
Expand All @@ -27,33 +31,38 @@ Components and layouts include built-in optimizations for print media (via `@med
- Review large or multi-column tables individually — consider hiding columns or switching to a simplified print layout when needed.
- Some components prevent page breaks internally (e.g. `break-inside: avoid` on table rows, Placeholder content), but most page-break decisions should be made explicitly using the tools below.

**Firefox & Page Breaks Note**
Firefox has supported the modern `break-before`, `break-after`, and `break-inside` properties since version **65** (January 2019). Legacy `page-break-*` properties are aliased for compatibility.
Many fragmentation issues — especially with flex containers and `avoid` values — were resolved around Firefox 122 (late 2023). Complex flexbox layouts may still encounter minor pagination quirks in edge cases.
Always test in Firefox print preview. Relevant historical bugs (mostly fixed):
**Browser support and page breaks**

- [Meta: Flex fragmentation/print issues](https://bugzilla.mozilla.org/show_bug.cgi?id=939897)
- Related fixes: [1744363](https://bugzilla.mozilla.org/show_bug.cgi?id=1744363), [1695475](https://bugzilla.mozilla.org/show_bug.cgi?id=1695475), [1807406](https://bugzilla.mozilla.org/show_bug.cgi?id=1807406)
Modern browsers support CSS page-break controls, but complex layouts can still
paginate differently. Firefox continues to track
[flex layout pagination issues](https://bugzilla.mozilla.org/show_bug.cgi?id=939897).
Check multi-page content in both Firefox and Chrome print preview, especially
long flex layouts and tables. If content is clipped or breaks in an awkward
place, consider simplifying the layout for print or applying break controls to
smaller sections. See [MDN's `break-inside` reference](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/break-inside)
for how break rules are resolved.

## How to Control Print Output

Use these tools — in recommended order:

### 1. Preferred: `<Print>` Component

Wrap content to apply print-specific visibility or page-break behavior via cloned `className`.
Wrap elements in `<Print>` to control print visibility and page breaks. It adds
print classes to its child elements without rendering an additional DOM element.
Custom child components must pass `className` to their DOM element.

```tsx
import { Print } from '@tedi-design-system/react/tedi';

// Hide during print
<Print visibility="hide">
This toolbar / interactive section disappears when printing
<div>This toolbar is hidden when printing.</div>
</Print>

// Show only during print
// Override no-print on the same element
<Print visibility="show">
This message or simplified content is only visible in print preview / on paper
<div className="no-print">This content remains visible when printing.</div>
</Print>

// Control page breaks
Expand All @@ -70,6 +79,12 @@ import { Print } from '@tedi-design-system/react/tedi';
- `breakBefore` / `breakAfter` / `breakInside`: `'auto'`, `'avoid'`, `'avoid-page'`, `'avoid-column'`, `'avoid-region'`
- Full props & stories → [Print component docs](/docs/tedi-ready-components-helpers-print--docs)

**`show` only overrides `no-print` on the same element.** Content inside a
hidden parent remains hidden. It does not hide the element on screen or make it
visible only when printing.

**`<Print>` supports avoiding page breaks, but cannot force a new page.**

### 2. Conditional Rendering: `usePrint()` Hook + `PrintingProvider`

For completely different markup, structure, or logic during print (e.g. no charts, simplified tables, no filters).
Expand All @@ -94,7 +109,8 @@ function ReportPage({ data }) {
}
```

**Setup (already handled globally):**
**Setup:** Wrap the relevant part of your application in `PrintingProvider` if
it is not already inside one. `usePrint()` requires this provider.

```tsx
// In your App.tsx
Expand All @@ -106,5 +122,6 @@ import { PrintingProvider } from '@tedi-design-system/react/tedi';
```

- Returns `true` during browser print preview / printing
- Consistent value across all nested components (no race conditions)
- Uses `beforeprint` / `afterprint` events (more reliable than `matchMedia('print')` alone)
- Shares the print state with components inside the provider
- Reads the initial state from `matchMedia('print')` and updates it using
`beforeprint` / `afterprint` events
5 changes: 5 additions & 0 deletions src/tedi/components/navigation/link/link.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,9 @@ describe('Link component', () => {
const link = screen.getByRole('link');
expect(link).not.toHaveClass('tedi-btn--link--icon-standalone');
});

it('is printed, unlike buttons', () => {
render(<Link href="#terms">Terms</Link>);
expect(screen.getByRole('link', { name: 'Terms' })).not.toHaveClass('no-print');
});
});
Loading