diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bbaef6d6..bdaf6182e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [Unreleased]
+### Added
+
+- ``
+ - component for hiding elements in specific media
+
+### Changed
+
+- automatically hide user interaction elements in print view
+ - all application header components except ``
+ - `` and ``
+ - `actionOptions` of ``
+ - `actions` of ``
+ - ``
+- automatically serialize display of layout elements in print view
+ - ``
+ - ``
+ - `` and ``
+
## [25.0.0] - 2025-12-01
This is a major release, and it might be not compatible with your current usage of our library. Please read about the necessary changes in the section about how to migrate.
diff --git a/src/components/Application/ApplicationViewability.tsx b/src/components/Application/ApplicationViewability.tsx
new file mode 100644
index 000000000..c9dafcc34
--- /dev/null
+++ b/src/components/Application/ApplicationViewability.tsx
@@ -0,0 +1,61 @@
+import React from "react";
+import classNames from "classnames";
+
+import { CLASSPREFIX as eccgui } from "../../configuration/constants";
+
+type media = "print" | "screen";
+
+interface ApplicationViewabilityShow {
+ /**
+ * Show on media type.
+ * If used, `hide` cannot be set.
+ */
+ show: media;
+ hide?: never;
+}
+
+interface ApplicationViewabilityHide {
+ /**
+ * Hide on media type.
+ * If used, `show` cannot be set.
+ */
+ hide: media;
+ show?: never;
+}
+
+interface ApplicationViewabilityUndecided {
+ /**
+ * Only one child allowed.
+ * Need to process the `className` property.
+ */
+ children: React.ReactElement<{ className?: string }>;
+}
+
+export type ApplicationViewabilityProps = ApplicationViewabilityUndecided &
+ (ApplicationViewabilityShow | ApplicationViewabilityHide);
+
+/**
+ * Sets the viewability of the the contained element regarding media.
+ * Can be used to hide elements, e.g. when the page is printed.
+ */
+export const ApplicationViewability = ({ children, show, hide }: ApplicationViewabilityProps) => {
+ if (!show && !hide) {
+ return children;
+ }
+ if (show === hide) {
+ // eslint-disable-next-line no-console
+ console.warn("`` used with same media type for `hide` and `show`.");
+ return children;
+ }
+
+ const enhancedClone = React.cloneElement(children, {
+ className: classNames(children.props.className, {
+ [`${eccgui}-application__hide--${hide}`]: hide,
+ [`${eccgui}-application__show--${show}`]: show,
+ }),
+ });
+
+ return enhancedClone;
+};
+
+export default ApplicationViewability;
diff --git a/src/components/Application/_content.scss b/src/components/Application/_content.scss
index d4ac1cc66..9cef44271 100644
--- a/src/components/Application/_content.scss
+++ b/src/components/Application/_content.scss
@@ -25,3 +25,10 @@ $ui-02: $eccgui-color-workspace-background !default;
.#{$eccgui}-application__content--railsidebar {
margin-left: mini-units(8);
}
+
+@media print {
+ .#{$eccgui}-application__content {
+ padding: $eccgui-size-block-whitespace 0 0 0 !important;
+ margin: 0;
+ }
+}
diff --git a/src/components/Application/_header.scss b/src/components/Application/_header.scss
index 59ec6b7fa..27d7fd492 100644
--- a/src/components/Application/_header.scss
+++ b/src/components/Application/_header.scss
@@ -98,10 +98,10 @@ span.#{$prefix}--header__name {
.#{$eccgui}-application__title--content {
display: inline-block;
overflow: hidden;
+ text-overflow: ellipsis;
font-size: $eccgui-size-typo-caption;
font-weight: $eccgui-font-weight-bold;
line-height: $eccgui-size-typo-caption-lineheight;
- text-overflow: ellipsis;
letter-spacing: $eccgui-font-spacing-wide;
white-space: nowrap;
}
@@ -122,7 +122,7 @@ span.#{$prefix}--header__name {
height: auto;
max-height: mini-units(5);
padding: 0;
- margin: mini-units(1.4) 0 mini-units(1.6) 0;
+ margin: mini-units(1.4) 0 mini-units(1.6);
vertical-align: middle;
}
}
@@ -195,9 +195,9 @@ a.#{$prefix}--header__menu-item:active {
.#{$prefix}--header__action.#{$prefix}--btn--primary:focus,
a.#{$prefix}--header__name:focus,
a.#{$prefix}--header__menu-item:focus {
- border: none;
outline: 1px dotted $shell-header-focus;
outline-offset: -1px;
+ border: none;
box-shadow: none;
}
.#{$prefix}--header__menu-title[aria-expanded="true"] {
@@ -267,3 +267,12 @@ a.#{$prefix}--header__menu-item:focus > svg {
margin: 0;
}
}
+
+@media print {
+ .#{$eccgui}-application__header {
+ position: relative;
+ & > :not(.#{$eccgui}-workspace__header) {
+ display: none;
+ }
+ }
+}
diff --git a/src/components/Application/_viewability.scss b/src/components/Application/_viewability.scss
new file mode 100644
index 000000000..d944eba59
--- /dev/null
+++ b/src/components/Application/_viewability.scss
@@ -0,0 +1,13 @@
+@media print {
+ .#{eccgui}-application__hide--print,
+ .#{eccgui}-application__show--screen {
+ display: none !important;
+ }
+}
+
+@media screen {
+ .#{eccgui}-application__hide--screen,
+ .#{eccgui}-application__show--print {
+ display: none !important;
+ }
+}
diff --git a/src/components/Application/application.scss b/src/components/Application/application.scss
index ea0de1677..460d62ee2 100644
--- a/src/components/Application/application.scss
+++ b/src/components/Application/application.scss
@@ -10,3 +10,4 @@
// @import '~@carbon/react/scss/components/ui-shell/navigation-menu';
@import "content";
@import "dropzone";
+@import "viewability";
diff --git a/src/components/Application/index.ts b/src/components/Application/index.ts
index eaa115f36..83cae7309 100644
--- a/src/components/Application/index.ts
+++ b/src/components/Application/index.ts
@@ -8,4 +8,5 @@ export * from "./ApplicationToolbar";
export * from "./ApplicationToolbarSection";
export * from "./ApplicationToolbarAction";
export * from "./ApplicationToolbarPanel";
+export * from "./ApplicationViewability";
export * from "./helper";
diff --git a/src/components/Application/stories/ApplicationViewability.stories.tsx b/src/components/Application/stories/ApplicationViewability.stories.tsx
new file mode 100644
index 000000000..3290ee89d
--- /dev/null
+++ b/src/components/Application/stories/ApplicationViewability.stories.tsx
@@ -0,0 +1,37 @@
+import React from "react";
+import { LoremIpsum } from "react-lorem-ipsum";
+import { Meta, StoryFn } from "@storybook/react";
+
+import { ApplicationViewability } from "../../../index";
+export default {
+ title: "Components/Application/Viewability",
+ component: ApplicationViewability,
+ argTypes: {
+ children: {
+ control: false,
+ },
+ hide: {
+ control: {
+ type: "radio",
+ },
+ options: ["print", "screen"],
+ },
+ show: {
+ control: {
+ type: "radio",
+ },
+ options: ["print", "screen"],
+ },
+ },
+} as Meta;
+
+const TemplateBasicExample: StoryFn = (args) => ;
+
+export const Default = TemplateBasicExample.bind({});
+Default.args = {
+ children: (
+
+
+
+ ),
+};
diff --git a/src/components/Application/tests/ApplicationViewability.test.tsx b/src/components/Application/tests/ApplicationViewability.test.tsx
new file mode 100644
index 000000000..16fa55645
--- /dev/null
+++ b/src/components/Application/tests/ApplicationViewability.test.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+import { expect } from "@storybook/test";
+import { render } from "@testing-library/react";
+
+import "@testing-library/jest-dom";
+
+import { ApplicationViewability, ApplicationViewabilityProps, CLASSPREFIX as eccgui } from "../../../index";
+
+import { Default as ApplicationViewabilityStory } from "./../stories/ApplicationViewability.stories";
+
+const applyViewabilityAndCheckClass = (props: Omit) => {
+ const { container } = render();
+ const element = container.getElementsByClassName(
+ props.hide ? `${eccgui}-application__hide--${props.hide}` : `${eccgui}-application__show--${props.show}`
+ );
+ expect(element.length).toBe(1);
+ return element;
+};
+
+describe("ApplicationViewability", () => {
+ it("should be visible on `show=screen`", () => {
+ applyViewabilityAndCheckClass({ show: "screen" });
+ /**
+ * Currently we cannot really test visibility via jest if it is defined by S/CSS rules because those styles are not known.
+ * Looks like it is not too easy to include and test them.
+ * So we only test for the correct CSS class.
+ */
+ // console.log(window.getComputedStyle(element.item(0)??new Element).getPropertyValue("display"));
+ // waitFor(() => expect(element).toBeVisible());
+ });
+ it("should not be visible on `hide=screen`", () => {
+ applyViewabilityAndCheckClass({ hide: "screen" });
+ // waitFor(() => expect(element).not.toBeVisible());
+ });
+ it("should be visible on `hide=print`", () => {
+ applyViewabilityAndCheckClass({ hide: "print" });
+ // waitFor(() => expect(element).toBeVisible());
+ });
+ it("should not be visible on `show=print`", () => {
+ applyViewabilityAndCheckClass({ show: "print" });
+ // waitFor(() => expect(element).not.toBeVisible());
+ });
+});
diff --git a/src/components/Card/card.scss b/src/components/Card/card.scss
index a551cbcf8..6f5797e64 100644
--- a/src/components/Card/card.scss
+++ b/src/components/Card/card.scss
@@ -288,3 +288,9 @@ $eccgui-size-card-spacing: $eccgui-size-typo-base !default;
}
}
}
+
+@media print {
+ .#{$eccgui}-card__actions {
+ display: none;
+ }
+}
diff --git a/src/components/Checkbox/checkbox.scss b/src/components/Checkbox/checkbox.scss
index f72a9fbdd..e575dc988 100644
--- a/src/components/Checkbox/checkbox.scss
+++ b/src/components/Checkbox/checkbox.scss
@@ -24,9 +24,15 @@ $control-indicator-spacing: $eccgui-size-inline-whitespace !default;
// $switch-background-color-active: rgba($gray1, 0.5) !default;
// $switch-background-color-disabled: $button-background-color-disabled !default;
$switch-checked-background-color: $eccgui-color-accent !default;
-$switch-checked-background-color-hover: eccgui-color-rgba($switch-checked-background-color, $eccgui-opacity-narrow) !default;
+$switch-checked-background-color-hover: eccgui-color-rgba(
+ $switch-checked-background-color,
+ $eccgui-opacity-narrow
+) !default;
$switch-checked-background-color-active: $switch-checked-background-color-hover !default;
-$switch-checked-background-color-disabled: eccgui-color-rgba($switch-checked-background-color, $eccgui-opacity-disabled) !default;
+$switch-checked-background-color-disabled: eccgui-color-rgba(
+ $switch-checked-background-color,
+ $eccgui-opacity-disabled
+) !default;
@import "~@blueprintjs/core/src/components/forms/controls"; // Checkbox, Radio, Switch
@@ -73,3 +79,9 @@ $switch-checked-background-color-disabled: eccgui-color-rgba($switch-checked-bac
display: inline-block;
vertical-align: text-top;
}
+
+@media print {
+ .#{$ns}-control {
+ print-color-adjust: exact;
+ }
+}
diff --git a/src/components/ContentGroup/_contentgroup.scss b/src/components/ContentGroup/_contentgroup.scss
index 4519fe65d..c5afaf5a6 100644
--- a/src/components/ContentGroup/_contentgroup.scss
+++ b/src/components/ContentGroup/_contentgroup.scss
@@ -60,3 +60,12 @@ $eccgui-color-scontentgroup-border-sub: eccgui-color-rgba(
flex-shrink: 1;
width: 100%;
}
+
+@media print {
+ .#{$eccgui}-contentgroup__header__options {
+ display: none;
+ }
+ .#{$eccgui}-contentgroup--border-sub::after {
+ print-color-adjust: exact;
+ }
+}
diff --git a/src/components/Depiction/depiction.scss b/src/components/Depiction/depiction.scss
index 44d6cda8b..a7a3dc9f0 100644
--- a/src/components/Depiction/depiction.scss
+++ b/src/components/Depiction/depiction.scss
@@ -220,3 +220,9 @@ $eccgui-size-depiction-border-radius: $pt-border-radius !default;
position: fixed;
left: -5000rem;
}
+
+@media print {
+ .#{$eccgui}-depiction {
+ print-color-adjust: exact;
+ }
+}
diff --git a/src/components/FlexibleLayout/flexiblelayout.scss b/src/components/FlexibleLayout/flexiblelayout.scss
index 2efeba7af..5cb8eb00f 100644
--- a/src/components/FlexibleLayout/flexiblelayout.scss
+++ b/src/components/FlexibleLayout/flexiblelayout.scss
@@ -46,3 +46,19 @@
flex-basis: auto;
}
}
+
+@media print {
+ .#{$eccgui}-flexible__container,
+ .#{$eccgui}-flexible__item {
+ position: relative;
+ display: block;
+ width: auto;
+ height: auto;
+ padding: 0;
+ margin: 0;
+
+ &:is(.#{$eccgui}-flexible__item) {
+ margin-bottom: $eccgui-size-block-whitespace;
+ }
+ }
+}
diff --git a/src/components/Grid/grid.scss b/src/components/Grid/grid.scss
index c3b5e55e5..4a4b72ba9 100644
--- a/src/components/Grid/grid.scss
+++ b/src/components/Grid/grid.scss
@@ -86,3 +86,20 @@ $grid-gutter: rem($eccgui-size-grid-gutter);
flex-wrap: nowrap;
}
}
+
+@media print {
+ .#{$eccgui}-grid,
+ .#{$eccgui}-grid__row,
+ .#{$eccgui}-grid__column {
+ position: relative;
+ display: block;
+ width: auto;
+ height: auto;
+ padding: 0;
+ margin: 0;
+
+ &:is(.#{$eccgui}-grid__column) {
+ margin-bottom: $eccgui-size-block-whitespace;
+ }
+ }
+}
diff --git a/src/components/Grid/stories/Grid.stories.tsx b/src/components/Grid/stories/Grid.stories.tsx
index 0cd3c527b..96bf087e0 100644
--- a/src/components/Grid/stories/Grid.stories.tsx
+++ b/src/components/Grid/stories/Grid.stories.tsx
@@ -11,19 +11,22 @@ export default {
subcomponents: { GridRow, GridColumn },
argTypes: {
children: {
- control: "none",
+ control: false,
},
},
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
} as Meta;
-const Template: StoryFn = (args) => (
-
-
-
-);
+const Template: StoryFn = (args) => ;
export const Default = Template.bind({});
Default.args = {
- children: ,
+ children: [, ],
verticalStretchable: true,
};
diff --git a/src/components/Grid/stories/GridRow.stories.tsx b/src/components/Grid/stories/GridRow.stories.tsx
index e126170ab..28ed25677 100644
--- a/src/components/Grid/stories/GridRow.stories.tsx
+++ b/src/components/Grid/stories/GridRow.stories.tsx
@@ -10,21 +10,27 @@ export default {
component: GridRow,
argTypes: {
children: {
- control: "none",
+ control: false,
},
},
} as Meta;
-const Template: StoryFn = (args) => (
-
-
-
-);
+const Template: StoryFn = (args) => ;
export const Default = Template.bind({});
Default.args = {
- children: ,
+ children: [
+ ,
+ ,
+ ],
};
+Default.decorators = [
+ (Story) => (
+
+
+
+ ),
+];
const TemplateStretched: StoryFn = (args) => (
diff --git a/src/components/Notification/notification.scss b/src/components/Notification/notification.scss
index 8df9a59b0..ac376c280 100644
--- a/src/components/Notification/notification.scss
+++ b/src/components/Notification/notification.scss
@@ -91,3 +91,9 @@
color: inherit;
}
}
+
+@media print {
+ .#{$eccgui}-notification__actions {
+ display: none;
+ }
+}
diff --git a/src/components/OverviewItem/overviewitem.scss b/src/components/OverviewItem/overviewitem.scss
index 41744f93f..4381d429b 100644
--- a/src/components/OverviewItem/overviewitem.scss
+++ b/src/components/OverviewItem/overviewitem.scss
@@ -197,3 +197,12 @@ $eccgui-size-overviewitem-line-typo-large-lineheight: $eccgui-size-typo-subtitle
display: flex;
}
}
+
+@media print {
+ .#{$eccgui}-overviewitem__actions {
+ display: none;
+ }
+ .#{$eccgui}-overviewitem__depiction {
+ print-color-adjust: exact;
+ }
+}
diff --git a/src/components/PropertyValuePair/propertyvalue.scss b/src/components/PropertyValuePair/propertyvalue.scss
index 494f6ace0..1f1edc292 100644
--- a/src/components/PropertyValuePair/propertyvalue.scss
+++ b/src/components/PropertyValuePair/propertyvalue.scss
@@ -5,9 +5,9 @@
}
.#{$eccgui}-propertyvalue__pair {
+ clear: both;
display: block;
width: 100%;
- clear: both;
&.#{$eccgui}-propertyvalue__pair--hasdivider {
&:not(:last-child) {
@@ -94,3 +94,25 @@
}
}
}
+
+@media print {
+ .#{$eccgui}-propertyvalue__pair,
+ .#{$eccgui}-propertyvalue__property,
+ .#{$eccgui}-propertyvalue__value {
+ position: relative;
+ float: none !important;
+ display: block;
+ width: auto;
+ height: auto;
+ min-height: 0 !important;
+ padding: 0;
+ margin: 0 !important;
+
+ &:is(.#{$eccgui}-propertyvalue__property) {
+ margin-bottom: 0.25 * $eccgui-size-block-whitespace !important;
+ }
+ &:is(.#{$eccgui}-propertyvalue__pair) {
+ margin-bottom: 0.5 * $eccgui-size-block-whitespace !important;
+ }
+ }
+}
diff --git a/src/components/Separation/separation.scss b/src/components/Separation/separation.scss
index 239b8f101..dc32d7480 100644
--- a/src/components/Separation/separation.scss
+++ b/src/components/Separation/separation.scss
@@ -101,3 +101,9 @@ $eccgui-color-separation-divider: $pt-divider-black !default;
margin: 0 $eccgui-size-separation-spacing-medium;
}
}
+
+@media print {
+ .#{$eccgui}-separation__divider-horizontal {
+ print-color-adjust: exact;
+ }
+}
diff --git a/src/components/Table/table.scss b/src/components/Table/table.scss
index 1f9bd5223..e926e8619 100644
--- a/src/components/Table/table.scss
+++ b/src/components/Table/table.scss
@@ -356,3 +356,25 @@ tr.#{$prefix}--parent-row.#{$prefix}--expandable-row + tr[data-child-row] {
min-height: $eccgui-size-tablecell-height-regular;
}
}
+
+@media print {
+ .#{$eccgui}-simpletable:has(.#{$eccgui}-simpletable__cell > .#{$eccgui}-typography__overflowtext) {
+ // allow 2 lines of text in `` elements that are direct children of table cells
+ .#{$eccgui}-simpletable__cell {
+ & > .#{$eccgui}-typography__overflowtext,
+ & > .#{$eccgui}-typography__overflowtext--passdown {
+ display: inline;
+ overflow: visible;
+ text-overflow: unset;
+ white-space: unset;
+ }
+ & > .#{$eccgui}-typography__overflowtext {
+ display: -webkit-box;
+ overflow: hidden;
+ -webkit-line-clamp: 2;
+ line-clamp: 2;
+ -webkit-box-orient: vertical;
+ }
+ }
+ }
+}
diff --git a/src/components/Tag/tag.scss b/src/components/Tag/tag.scss
index 2ef47acdb..49e3dd222 100644
--- a/src/components/Tag/tag.scss
+++ b/src/components/Tag/tag.scss
@@ -267,3 +267,9 @@ $tag-round-adjustment: 0 !default;
}
}
}
+
+@media print {
+ .#{$eccgui}-tag__item {
+ print-color-adjust: exact;
+ }
+}