@odx/product-fruits
v6.1.0
Published
## Table of contents
Readme
@odx/product-fruits
Table of contents
- Install
- Usage
- Configuration Options
- Newsfeed
- Legacy LifeRing
- LifeRing v2
- Knowledgebase custom styles
- Change language
- Host theming
- Live demo
Install 🚀
npm install @odx/product-fruits⚠️ Note: @odx/product-fruits includes everything you need. Avoid installing npm install product-fruits separately, as it may cause issues.
Usage
Setup product fruits by initializing it's enviroment providers with provideProductFruits inside the applications providers array:
import { provideProductFruits } from '@odx/product-fruits';
bootstrapApplication(AppComponent, {
providers: [
// ...
provideProductFruits(/* ...options */),
],
});Configuration Options
The provideProductFruits function accepts two parameters:
- Options Factory - A function that returns the Product Fruits configuration
- Setup Options (optional) - Additional setup configuration
Synchronous Configuration
The simplest way to configure Product Fruits is with a synchronous object:
provideProductFruits(() => ({
language: 'en',
workspaceCode: 'your_workspace_code',
user: {
username: '[email protected]',
props: {
// Optional custom user properties
role: 'admin',
company: 'Draeger',
},
},
// Optional: error handler
onError: (error) => console.error('Product Fruits error:', error),
}));Asynchronous Configuration
You can use Observables or Promises to load configuration asynchronously, for example from an authentication service:
provideProductFruits(() =>
inject(AuthService).identityClaims$.pipe(
filter((user) => !!user), // Filter out unauthenticated users
map((user) => ({
language: user?.preferredLanguage ?? 'en',
user: {
username: user?.email,
props: {
/* user props */
},
},
workspaceCode: /* workspace code */,
})),
),
);Important: When using Observables with operators like filter(), the Observable might never emit (e.g., for unauthenticated users). To prevent blocking the app bootstrap, a timeout is automatically applied (see Setup Options below).
Setup Options
The second parameter allows you to configure initialization behavior:
provideProductFruits(
() => ({
/* ...config */
}),
{
// Disable Product Fruits initialization entirely
disabled: false,
// Timeout in milliseconds for Observable-based configuration
// If the Observable doesn't emit within this time, initialization is skipped
// Default: 5000ms (5 seconds)
timeout: 5000,
},
);Timeout behavior:
- Only applies to Observable-based configurations
- Prevents app from hanging if Observable never emits (e.g., filtered authentication streams)
- Default is 5000ms - enough for most auth checks, but not too long for unauthenticated users
- Adjust value basing on your authentication service needs
- The app will continue bootstrapping after the timeout, Product Fruits simply won't initialize
Example with custom timeout:
provideProductFruits(
() =>
inject(AuthService).identityClaims$.pipe(
filter((user) => !!user),
map((user) => ({
/* ...config */
})),
),
{ timeout: 3000 }, // Wait up to 3 seconds for authentication
);Returning null:
If your factory returns null, Product Fruits won't be initialized:
provideProductFruits(() => {
const env = inject(Environment);
// Don't initialize in development
if (env.isDevelopment) return null;
return {
/* ...config */
};
});Newsfeed
In order to setup the newsfeed UI add the ProductFruitsNewsFeedDirective to your imports and use it in the HeaderComponent:
<odx-header>
<odx-action-group>
<button odxButton odxProductFruitsNewsfeed></button>
</odx-action-group>
</odx-header>The newsfeed button will be disabled if product fruits failed to initialize.
Legacy LifeRing
Deprecated - ProductFruitsLifeRingDirective no longer supports legacy LifeRing. If you had collections of help articles created for the earlier version, you can easily migrate them to v2 in the PF admin app.
LifeRing v2
If you have the basic setup of help articles in your PF admin, the default lifering floating button will appear itself on your webpage - thats all.
However, in order to setup the alternative lifering trigger button, add the ProductFruitsLifeRingDirective to your imports and use it in the HeaderComponent. This will automatically hide the floating button, but it is also recommended to turn it off in your PF configuration.
Heads‑up:
The
odxProductFruitsLifeRingtrigger may not work reliably when placed inside nested, dynamically positioned UI (for example, odx dropdowns, overlays, or popovers). Prefer mounting it in a stable, top‑level container such as your header. Ensure the trigger is reachable and accessible by design: it must be visible and enabled, part of the natural tab order, not clipped or covered by other elements, and operable via keyboard and assistive technologies.
<odx-header>
<odx-action-group>
<button odxButton odxProductFruitsLifeRing></button>
</odx-action-group>
</odx-header>provideProductFruits(() => ({
language: 'en',
user: { username: 'TEST_USER' },
workspaceCode: 'your_workspace_code'
hideInAppCenterLauncher: true, // <-- add this
})),The life-ring button will be disabled if product fruits failed to initialize.
Change language
In order to change the language during runtime use the updateLanguage method of ProductFruitsService.
Host Theming
By default, this library applies ODX theming to certain Product Fruits components to maintain a consistent look. You can control this behavior using the hostTheming configuration object.
hostTheming.bannerV3Background
When false (default), the library forces the banner background color to match ODX theming, overriding any inline styles Product Fruits may set. When true, Product Fruits (or your own host CSS) controls the banner background.
provideProductFruits(() => ({
language: 'en',
user: { username: 'TEST_USER' },
workspaceCode: 'your_workspace_code',
hostTheming: {
bannerV3Background: true, // Allow PF/host to control banner background
},
}));Knowledgebase custom styles
Inside the Customer Portal Settings under the Help category, find the Body HTML or script tags field and paste the snippet below to apply ODX typography, colors, and article navigation styles across the knowledgebase:
<style>
/* Draeger Pangea Text - Regular (400) */
@font-face {
font-display: swap;
font-family: 'Draeger Pangea Text';
font-style: normal;
font-weight: 400;
src: url('https://42.draeger.com/globalAssets/Fonts/DraegerPangeaText-Regular-Latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0100-024F, U+1E00-1EFF; /* Latin + extended */
}
@font-face {
font-display: swap;
font-family: 'Draeger Pangea Text';
font-style: normal;
font-weight: 400;
src: url('https://42.draeger.com/globalAssets/Fonts/DraegerPangeaText-Regular-Greek.woff2') format('woff2');
unicode-range: U+0370-03FF, U+1F00-1FFF; /* Greek */
}
@font-face {
font-display: swap;
font-family: 'Draeger Pangea Text';
font-style: normal;
font-weight: 400;
src: url('https://42.draeger.com/globalAssets/Fonts/DraegerPangeaText-Regular-Cyrillic.woff2') format('woff2');
unicode-range: U+0400-04FF, U+0500-052F, U+2DE0-2DFF, U+A640-A69F; /* Cyrillic */
}
/* Draeger Pangea Text - SemiBold (600) */
@font-face {
font-display: swap;
font-family: 'Draeger Pangea Text';
font-style: normal;
font-weight: 600;
src: url('https://42.draeger.com/globalAssets/Fonts/DraegerPangeaText-SemiBold-Latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0100-024F, U+1E00-1EFF; /* Latin + extended */
}
@font-face {
font-display: swap;
font-family: 'Draeger Pangea Text';
font-style: normal;
font-weight: 600;
src: url('https://42.draeger.com/globalAssets/Fonts/DraegerPangeaText-SemiBold-Greek.woff2') format('woff2');
unicode-range: U+0370-03FF, U+1F00-1FFF; /* Greek */
}
@font-face {
font-display: swap;
font-family: 'Draeger Pangea Text';
font-style: normal;
font-weight: 600;
src: url('https://42.draeger.com/globalAssets/Fonts/DraegerPangeaText-SemiBold-Cyrillic.woff2') format('woff2');
unicode-range: U+0400-04FF, U+0500-052F, U+2DE0-2DFF, U+A640-A69F; /* Cyrillic */
}
/* Draeger Pangea Display - Medium (500) */
@font-face {
font-display: swap;
font-family: 'Draeger Pangea Display';
font-style: normal;
font-weight: 500;
src: url('https://42.draeger.com/globalAssets/Fonts/DraegerPangeaDisplay-Medium-Latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0100-024F, U+1E00-1EFF; /* Latin + extended */
}
@font-face {
font-display: swap;
font-family: 'Draeger Pangea Display';
font-style: normal;
font-weight: 500;
src: url('https://42.draeger.com/globalAssets/Fonts/DraegerPangeaDisplay-Medium-Greek.woff2') format('woff2');
unicode-range: U+0370-03FF, U+1F00-1FFF; /* Greek */
}
@font-face {
font-display: swap;
font-family: 'Draeger Pangea Display';
font-style: normal;
font-weight: 500;
src: url('https://42.draeger.com/globalAssets/Fonts/DraegerPangeaDisplay-Medium-Cyrillic.woff2') format('woff2');
unicode-range: U+0400-04FF, U+0500-052F, U+2DE0-2DFF, U+A640-A69F; /* Cyrillic */
}
/* Standalone subset of ODX tokens used by this theme (light mode values) */
:root {
/* Palette subset */
--odx-palette-blue-00: #eef3fc;
--odx-palette-blue-80: #002766;
--odx-palette-coolgray-00: #f9fafb;
--odx-palette-white: #ffffff;
/* Derived colors (resolved for light theme) */
--odx-color-background-base: var(--odx-palette-coolgray-00);
--odx-color-background-primary-rest: var(--odx-palette-blue-80);
--odx-color-foreground-rest: var(--odx-palette-blue-80);
/* Theme convenience aliases */
--primary-bg-color: var(--odx-color-background-primary-rest, #002766);
--primary-fg-color: var(--odx-color-foreground-rest, #0f172a);
/* Typography families */
--odx-typography-font-family-base: 'Draeger Pangea Text';
--odx-typography-font-family-brand: 'Draeger Pangea Display';
/* Heading sizes */
--odx-typography-font-size-heading-xxl: 2.5rem;
--odx-typography-font-size-heading-xl: 1.875rem;
--odx-typography-font-size-heading-lg: 1.5rem;
--odx-typography-font-size-heading-md: 1.25rem;
--odx-typography-font-size-heading-sm: 1rem;
--odx-typography-font-size-heading-xs: 0.875rem;
}
html {
color: var(--odx-color-foreground-rest);
}
html,
body {
font-family:
'Draeger Pangea Text',
Inter,
system-ui,
-apple-system,
'Segoe UI',
Roboto,
Helvetica,
Arial,
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol';
}
body {
background-color: var(--odx-color-background-base);
}
.footer {
background-color: var(--odx-color-background-base);
}
.article-v2 h1,
.article-v2 h2,
.article-v2 h3,
.article-v2 h4,
.article-v2 h5,
.article-v2 h6 {
color: var(--odx-color-foreground-rest);
font-weight: 600;
}
.article-v2 h1,
.article-v2 h2 {
font-family:
var(--odx-typography-font-family-brand),
'Draeger Pangea Text',
Inter,
system-ui,
-apple-system,
'Segoe UI',
Roboto,
Helvetica,
Arial,
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol';
font-weight: 500;
}
.article h1,
.article h2,
.article h3,
.article h4,
.article h5,
.article h6 {
color: var(--odx-color-foreground-rest);
font-weight: 600;
}
.article h1 {
font-size: var(--odx-typography-font-size-heading-xxl, 2.5rem);
}
.article h2 {
font-size: var(--odx-typography-font-size-heading-xl, 1.875rem);
}
.article h3 {
font-size: var(--odx-typography-font-size-heading-lg, 1.5rem);
}
.article h4 {
font-size: var(--odx-typography-font-size-heading-md, 1.25rem);
}
.article h5 {
font-size: var(--odx-typography-font-size-heading-sm, 1rem);
}
.article h6 {
font-size: var(--odx-typography-font-size-heading-xs, 0.875rem);
}
.article-navigation .article-nav-link {
padding-left: 3px;
}
.article-navigation .active {
color: var(--primary-bg-color);
font-weight: 600;
}
.articles-menu .active {
box-shadow: -2px 0 0 0 var(--primary-bg-color);
}
.articles-menu .article-menu-item:hover {
box-shadow: -2px 0 0 0 var(--primary-bg-color);
}
</style>Live demo ⭐
Please refer to our Storybook, to see the components in action and to get further information.
