| Version | Tag | Status | API |
|---|---|---|---|
| v1 | <oc-sheet-v1> |
Stable, allowed for generation | SheetV1 |
Overview (v1)
Source: ./src/components/sheet/v1/Overview.mdx
Sheet
The sheet component is a modular overlay designed to display additional information or interactive elements atop the current context. It is customizable through various attributes that control its visibility, header display, and content sourcing.
Use the sheet for simple tasks that are connected to the current context. For more complex tasks, like filling out a long form, the usage of the Focused Dialog is recommended.
Skip to:
Default variation
Configuration
To explore all the available options and adjust the component, use the component configurator and see the changes affect the component in real-time.
Usage guidelines
Before integrating the sheet component into your project, make sure you have correctly installed the OTTO components package. Look through the variations page for examples of possible component variations. Here, you can discover both common and specific variations that address different use cases. Learn more about how to interact programmatically with a sheet in the Sheet API documentation. To persist and restore application state when users navigate through browser history, refer to the Restore application context guide.
Tracking
Note that the tracking of the sheet component may not work properly if the content is set via a content string in the default slot.
To ensure proper tracking, use the url or base64-url attribute to load the content from an external source.
The team is aware of the issue and is working to resolve it.
Content Change
On desktops, when a new sheet opens while another is already active, it instantly replaces the previous one, bypassing the open/close animation to indicate the content change to the user. This behavior allows for the implementation of different contents/pages within the 'same' sheet. In this scenario, a back button automatically appears in the header, facilitating a return to the previous sheet seamlessly, mirroring the animation-free transition between sheets. For more information on the visibility and navigation of sheets, see the Sheet presentation and DOM lifecycle documentation.
How the sheet is rendered and positioned in the DOM
When you add the sheet component to your markup, its DOM node is initially rendered exactly where you place it in your code. However, when the sheet is opened:
- The DOM node of the sheet is automatically teleported to the end of the document, right before the closing
</body>tag. - At the original DOM node position of the sheet, a placeholder comment is inserted as an anchor, using the
idof the sheet.
This ensures the sheet can overlay other content on the page without display issues and always opens at the correct absolute position from the side, regardless of where it was placed in your markup.
For example, if your sheet node is:
<oc-sheet-v1 id="auto-uOTGS7VxOhhRTaMnCIcLs" style="display: none;"></oc-sheet-v1>
When the trigger to open this sheet is clicked, the node is teleported to the end of the <body> and replaced at its original location with a placeholder comment:
<!--auto-uOTGS7VxOhhRTaMnCIcLs-->
This anchor acts as a placeholder, marking where the sheet was originally placed. When the sheet is closed, the DOM node is teleported back to its original position, replacing the anchor comment.
Accessibility
The sheet component comes with a set of built-in accessibility features to ensure a seamless experience for all users.
Keyboard navigation
The sheet component supports keyboard navigation for accessibility purposes. The following table lists the keyboard shortcuts available for this component:
| Shortcut | Description |
|---|---|
| Tab | Moves focus to next focusable element inside the dialog. |
| Shift + Tab | Moves focus to previous focusable element inside the dialog. |
| Escape | Closes the dialog. |
Focus handling
The focus is always kept inside an open sheet ("focus trap"). While a sheet is open, it is not possible to move the focus outside of the sheet via keyboard navigation.
Note: This accessibility-friendly behavior of the focus trap is disabled for all previews of the different sheet variations! It would prevent users from interacting with the menus and toolbars of this component library documentation.
Use oc-aria-label
To make the sheet recognizable for screen readers, use the oc-aria-label attribute to provide a clear and descriptive label when the component does not have a headline attribute.
Note
If your sheet does not have a
headline, you must specify its purpose usingoc-aria-labelto ensure accessibility for screen readers.See the general accessibility documentation for guidance on using
oc-aria-label, including how it works with link and masked link behavior.
Configuration (v1)
Source: ./src/components/sheet/v1/Configuration.mdx
Sheet configuration
Configure the sheet component with the controls below and see the changes live in the preview canvas. Click the Show code tab within the preview canvas to see the source code for the current component configuration.
Interactive configurator (Storybook controls); every option is listed in the API section of this file.
Migration (v1)
Source: ./src/components/sheet/v1/Migration.mdx
Migration from legacy sheet to Sheet v1
The oc-sheet component is the successor of the deprecated pattern library sheet.
It is implemented as an accessible web component.
Skip to:
What's new
Web component
The sheet is now a proper web component instead of a JavaScript-based pattern library module, providing:
- Encapsulated styling
- Improved accessibility
- Better integration with modern web standards
- Declarative attribute-based API
Attribute-based configuration
The new sheet uses data attributes for configuration, eliminating the need for JavaScript initialization code in most cases.
Static content support
Static sheets can now be defined as HTML elements in the DOM, making them easier to manage and style:
<oc-sheet-v1 id="my-sheet" headline="Sheet Title">
<p>Sheet content goes here</p>
</oc-sheet-v1>
API changes
Changed attributes
| v0 Attribute/Class | v1 Equivalent | Notes |
|---|---|---|
js_openInPaliSheet class |
data-oc-sheet-v1-create or data-oc-sheet-v1-open attributes |
Replaced JavaScript class with declarative attributes |
Removed attributes
The following legacy attributes have been removed:
| v0 Attribute | v1 Equivalent | Notes |
|---|---|---|
data-sheet-ub64e |
data-oc-sheet-v1-create.base64-url |
New attribute namespace |
data-sheet-content |
Use <oc-sheet-v1> element with slotted content |
Inline content no longer supported via attributes |
data-sheet-title |
headline attribute on <oc-sheet-v1> element |
Now a component attribute |
data-sheet-initial-mobile-height |
full-height attribute |
Simplified height control |
How to migrate
The main change is migrating from the legacy pattern library sheet to a web component-based implementation. The new sheet API simplifies configuration and provides better accessibility.
Migrate a sheet with content from a URL
The deprecated sheet API uses the class js_openInPaliSheet and several data-sheet-* attributes to configure the sheet:
<span
class="js_openInPaliSheet pl_link100--primary "
data-sheet-ub64e="L3Nob3BwYWdlcy9kZXRhaWx2aWV3X3NpemluZ19zcG9ydHNfc2hvZXNfYWRpZGFzX21lbg=="
>
<span>Größentabelle</span>
</span>
The new sheet API uses only data-oc-sheet-v1-create* attributes:
<span
class="pl_link100--primary"
data-oc-sheet-v1-create
data-oc-sheet-v1-create.
base64-url="L3Nob3BwYWdlcy9kZXRhaWx2aWV3X3NpemluZ19zcG9ydHNfc2hvZXNfYWRpZGFzX21lbg=="
>Größentabelle
</span>
Migrate a sheet with static content
The deprecated pattern library sheet uses the data-sheet-content attribute to pass in static content:
<span
class="js_openInPaliSheet pl_link100--primary"
data-sheet-title="Ratenrechner"
data-sheet-initial-mobile-height="80vh"
data-sheet-content='<div class="pl_copy100"> ... </div>'
>Zum Ratenrechner
</span>
Passing in formatted content through attributes is not supported anymore.
Instead, use a distinct sheet tag <oc-sheet-v1> to define static sheets with formatted and styled content.
You can place the attributes data-oc-sheet-v1-open and data-oc-sheet-v1-open.id="my-sheet-id" for opening the sheets on any other element.
<oc-sheet-v1 id="ratenrechner-sheet" headline="Ratenrechner" full-height>
<div class="pl_copy100">...</div>
</oc-sheet-v1>
<span
class="pl_link100--primary"
data-oc-sheet-v1-open
data-oc-sheet-v1-open.id="ratenrechner-sheet"
>Zum Ratenrechner
</span>
API (v1)
Source: ./src/components/sheet/v1/API.mdx
Sheet APIs
The sheet component offers multiple flexible APIs for programmatic interaction. Choose from DOM manipulation, module imports, or declarative HTML attributes based on your implementation needs. Each approach is detailed below with examples and configuration options.
Skip to:
- Instance API
- Module API
- Convenience API
- Hash parameter API
- Error handling
- External content configuration options
Instance API
You can interact with the sheet component directly through its instance API:
// Create a sheet
const mySheet = document.createElement("oc-sheet-v1");
// Configure the sheet
// The id must begin with a letter ([A-Za-z]).
mySheet.id = "my-sheet";
mySheet.headline = "My sheet headline";
// Open the sheet
mySheet.open = true;
// Close the sheet
mySheet.open = false;
// Listen to open events
mySheet.addEventListener("oc-open", sheetOpenHandlerFn);
Module API
The component provides an alternative api powered by the @otto-ec/global-resources/nexus package.
This API allows you to interact with the sheet component programmatically without directly manipulating the DOM.
Refer to the Nexus documentation for more information about using and creating global OTTO frontend APIs.
For example, you can create and open a sheet instance using the module API as follows:
import { sheetV1 } from "@otto-ec/otto-components/sheet";
// Create a sheet with passed in configuration
// The id must begin with a letter ([A-Za-z]).
sheetV1.create({
id: "any-sheet-id",
headline: "My sheet headline",
url: "/path-to-sheet-content",
});
// Open a sheet with the ID "any-sheet-id"
sheetV1.open("any-sheet-id");
Note
The sheet
idis optional. If omitted, a random uniqueidis generated and added to the trigger element. See the Trigger element configuration section for more details.
Events
You can subscribe to events from all sheet instances using the events object of the module API.
Unlike the DOM event API, the module API broadcasts events for all sheet instances.
Use the id or instance property in the event detail to identify the specific sheet that triggered the event.
import { sheetV1 } from "@otto-ec/otto-components/sheet";
// Subscribe to the oc-open event
sheetV1.events.open.subscribe((event) => {
console.log("Sheet opened:", event.detail.id, event.detail.instance);
});
Special case: before open
All sheet events have an instance property set to the current sheet instance which is being requested to open.
The before open event however is dispatched immediately on the incoming open request.
The request itself only uses the id of the instance, and therefore the instance may not be created yet.
This gives you the opportunity to create the sheet instance beforehand in case it must be done dynamically.
Signals
For information about working with signals, refer to the Signals and events section in the DOM lifecycle documentation.
Convenience API
The convenience API allows you to interact with the sheet component through data attributes on HTML elements. This API is useful for creating and opening sheets directly from the HTML without the need for additional JavaScript code.
Create and open a sheet
You can automatically assign a click event listener to any HTML element that supports the click event and use the data-oc-sheet-v1-create attribute to create a new sheet instance and open it.
The underlying mechanism uses the attribute parser to parse the configuration options.
You can pass any configuration option supported by the sheet API either as a specific data attribute, or by using a JSON object to set all required options at once.
<!-- The id must begin with a letter ([A-Za-z]). -->
<button
data-oc-sheet-v1-create
data-oc-sheet-v1-create.id="sheet-usk18"
data-oc-sheet-v1-create.url="/shoppages/usk-18-hinweis"
>
create and open sheet
</button>
Note
The sheet
idis optional. If omitted, a random uniqueidis generated and added to the trigger element. See the Trigger element configuration section for more details.
Close a sheet
You can close an open sheet by adding the data-oc-sheet-v1-close attribute to any button-like element inside the sheet content.
<oc-button-v1
variant="primary"
slot="actions"
data-oc-sheet-v1-close='{ "info": { "source": "sheet-content.html", "action": "close-by-button" } }'
>
Close Sheet
</oc-button-v1>
Use info parameter
You can also pass additional info parameter to either default atztribute or as extra parameter attribute like ata-oc-sheet-v1-close.info.
Info can be anything and is simply passed through to all events dispatched by the sheet component during transition.
This may be usefull to identify source of the action triggered the transition of the sheet component.
Use button-like elements to open a sheet
The sheet component responds to click events.
- Button-like elements (such as
<button>,<oc-button-v1>, or<oc-link-v2 as-button>) triggerclickautomatically for both mouse and keyboard (Space/Enter) users. - Generic elements like
<div>do not triggerclickon keyboard activation, even withrole="button"ortabindex.
For accessibility and keyboard support, always use button-like elements:
Recommended are elements that have a native click event, for example:
<oc-button-v1>open sheet</oc-button-v1> <oc-link-v2 as-button>open sheet</oc-link-v2>
Not recommended are elements that do not have a native click event, for example:
<oc-button-v1 base64-href="Iw==">open sheet</oc-button-v1>
<oc-link-v2 base64-href="Iw==">open sheet</oc-link-v2>
<oc-link-v2><a href="#">open sheet</a></oc-link-v2>
<div role="button">Open sheet from div</div>
Pass through data set for the sheet instance
The convenience API allows you to pass through a data set that's added to the instance of the sheet, for example, to set up tracking via the data-tr-v1 attribute.
The following example uses JSON to pass options to the sheet instance and a separate data-set option to setup tracking.
Note
Ensure that
innerJSON data is properly escaped.
<!-- The id must begin with a letter ([A-Za-z]). -->
<button
data-oc-sheet-v1-create='{ "id": "sheet-usk18", "url": "/shoppages/usk-18-hinweis" }'
data-oc-sheet-v1-create.data-set='{"trV1": "{ \"ot_label\": [\"some\",\"foo\"] }", "trV1.track": "oc-open,oc-close" }'
>
create and open sheet
</button>
Control target container for the sheet
By default, sheets are appended to the adjacent parent container.
You can override this behavior by specifying a different target container using the container option.
which expects a CSS selector string.
<!-- The id must begin with a letter ([A-Za-z]). -->
<button
data-oc-sheet-v1-create='{ "id": "sheet-usk18", "url": "/shoppages/usk-18-hinweis", "container": "#my-custom-container" }'
>
create and open sheet
</button>
<div id="my-custom-container"></div>
<!-- The id must begin with a letter ([A-Za-z]). -->
<button
data-oc-sheet-v1-create='{ "id": "sheet-usk18", "url": "/shoppages/usk-18-hinweis" }'
data-oc-sheet-v1-create.container="#my-custom-container"
>
create and open sheet
</button>
<div id="my-custom-container"></div>
Only open a sheet instance
This API works similar to the create API, but requries a sheet instance that's already created and searchable via the given ID.
It supports the possibility to update the instance configuration, but doesn't support passing through a data-set.
Open a sheet with the ID my-sheet:
<oc-sheet-v1 id="my-sheet">sheet content</oc-sheet-v1>
<button data-oc-sheet-v1-open data-oc-sheet-v1-open.id="my-sheet">open sheet</button>
Open the same sheet instance with two different trigger elements with different configuration options:
<oc-sheet-v1 id="my-sheet">sheet content</oc-sheet-v1>
<button data-oc-sheet-v1-open='{ "id": "my-sheet", "url": "/some/url/1" }'>open url 1</button>
<button data-oc-sheet-v1-open='{ "id": "my-sheet", "url": "/some/url/2" }'>open url 2</button>
Trigger element configuration
You can omit the sheet ID defined by the data-oc-sheet-v1-create.id attribute.
If omitted, a unique ID is generated and added to the trigger element upon click.
Trigger element before click:
<button data-oc-sheet-v1-create data-oc-sheet-v1-create.url="/shoppages/usk-18-hinweis">
create and open sheet
</button>
Trigger element after click (with an auto generated ID):
<button
data-oc-sheet-v1-create
data-oc-sheet-v1-create.url="/shoppages/usk-18-hinweis"
data-oc-sheet-v1-create.id="s-62Ffi9UvktbM9tw6JMB"
>
create and open sheet
</button>
The create sheet method as well create convenience API method are idempotent in regards of creating new sheet instances. Meaning the instance will only be creatd if there is not exsiting instance with the same ID.
Meaning you don't need to handle this case in your code.
Configuration via JSON object
To avoid creating multiple instances of the same sheet, use the same ID for all trigger elements that should create or open the same sheet. Instead of using a dedicated data attribute for each configuration detail, pass the entire configuration as a stringified JSON object:
<oc-sheet-v1 id="my-sheet">sheet content</oc-sheet-v1>
<button data-oc-sheet-v1-open='{"id":"my-sheet"}'>open sheet</button>
Create and open a sheet with a specific URL and full height:
<button
data-oc-sheet-v1-create='{"id":"sheet-usk18","url":"/shoppages/usk-18-hinweis","fullHeight":true}'
>
create and open sheet
</button>
Important
When working with stringified JSON objects, use the camelCase notation for property names.
Hash parameter API
The sheet component can be controlled via the oc-sheet-v1 hash parameter, which supports three different modes of operation:
Open existing sheets
To open an existing sheet instance, pass its ID as the parameter value.
For example, to open a sheet with the ID DISPOSAL_NOTE:
#oc-sheet-v1=DISPOSAL_NOTE
If no sheet with the specified ID exists, the parameter is ignored.
Create sheets dynamically
To create and open a sheet dynamically, pass a URI encoded JSON configuration object.
For example, URI encode {"url": "/foo/bar"} to %7B%22url%22%3A%20%22%2Ffoo%2Fbar%22%7D:
#oc-sheet-v1=%7B%22url%22%3A%20%22%2Ffoo%2Fbar%22%7D
The JSON object supports all sheet configuration attributes.
Note
Only sheets with a
urlattribute display content. Inline content is not supported for dynamically created sheets.
Use Base64-encoded configuration
To encode complex configurations with special characters, use Base64 encoding for the JSON configuration. This approach simplifies URL handling by avoiding issues with quotes, spaces, and other special characters in URLs. All standard sheet configuration attributes remain fully supported when using this Base64 encoding method.
#oc-sheet-v1=eyJ1cmwiOiIvc29tZS9mb28ifQ==
Important
Malformed or incomplete JSON/Base64 strings result in a misconfigured sheet.
Direct sheet navigation
You can open a sheet by navigating directly to a URL that contains the oc-sheet-v1 hash parameter.
This is also known as cross-document navigation or page enter navigation.
For example, sharing or linking to the following URL opens a sheet when the page loads:
https://example.com/page#oc-sheet-v1=DISPOSAL_NOTE
Back navigation behavior
The behavior when closing the sheet depends on how the oc-sheet-v1 parameter is set:
- Plain sheet ID: When the parameter contains a plain sheet ID (for example
DISPOSAL_NOTE), closing the sheet triggers browser back navigation. This is because the component cannot determine whether the page was reached via a direct link or by in-page navigation. The browser history API does not provide information about the type of navigation that triggered the current page load. - JSON configuration object: When the parameter contains a JSON configuration object, closing the sheet does not trigger back navigation. The JSON object acts as both the sheet configuration and as a signal to the component that the page entry was triggered externally (for example, via a direct link or a cross-document navigation). This allows the component to suppress the back navigation on sheet close.
Suppress back navigation on close
To suppress back navigation when a sheet is opened via direct page navigation, use a JSON configuration object as the parameter value instead of a plain sheet ID.
For an existing sheet instance, pass an object with the id property:
#oc-sheet-v1=%7B%22id%22%3A%22DISPOSAL_NOTE%22%7D
Or using Base64 encoding:
#oc-sheet-v1=eyJpZCI6ICJESVNQT1NBTF9OT1RFIn0=
When a dynamic sheet is created via a JSON configuration object, back navigation is always suppressed by default:
#oc-sheet-v1=%7B%22url%22%3A%22%2Ffoo%2Fbar%22%7D
Error handling
If the resource server returns a response with an error status code when loading remote content, the content is discarded, and the sheet displays the content that is set via the loading-error slot.
This slot shows a static error block that must be configured.
If no error block is defined, the sheet shows nothing in case of an error.
If the response header includes cache-control:no-transform and content-type:text/html, the error content is displayed (even if the status code is not 2xx) and processed as usual, including executing inline scripts.
If error response itself throws an error, then static error block is displayed as last resort.
- Use the
allowedErrorStatusCodesattribute to define a list of status codes that should be displayed to a user, instead of the staticloading-error. If this list is empty, all status codes are considered as user faced error. - Use the
oc-content-loading-errorevent to handle the error programmatically if needed.
External content configuration options
When loading content from a remote server, the content loaded into the sheet is unknown and can vary, even if the URI remains the same.
This can lead to mismatches, such as between the expected and actual headlines.
Because the content can only be adjusted after it is displayed, it can cause flickering or Flash of Unstyled Content (FOUC).
To adress this, teams providing sheet fragments can include an inert script block with sheet configuration options within the content, allowing dynamic adjustments to specific sheet attributes.
- Be carefull what props you want to override, it may affect the integration party in a bad manner.
- Special note about
idproperty: it may only be overridden, if it starts withauto-prefix, meaning it has been autogenerated. - Special note about
extraData: it may only be overridden, if it is not set yet, meaning it has been set by the integration party.
How it works
The sheet searches automatically for a script block of type application/json in the remote content with the data-oc-sheet-v1 attribute.
If found, the included JSON object with the configuration options is parsed by the @otto-ec/global-resources/attribute-parser and the content adjustments are applied to the sheet.
For more details on using the attribute parser for complex use cases, refer to the Attribute parser documentation.
The configuration options are applied in the following way:
- Options that are not already defined on the sheet element will be applied.
- If an option is already set, the options from the external content will be ignored.
How to provide configuration options
There are two ways to provide options, either as a JSON object or as individual attributes on the script element.
Both methods are equivalent, but attributes always take precedence.
The following example demonstrates how to adjust the headline and fullHeight attributes of a sheet via a JSON object:
<script type="application/json" data-oc-sheet-v1>
{
"headline": "Headline via JSON object",
"fullHeight": true
}
</script>
The following example demonstrates how to adjust the no-content-padding, headline, and fullHeight attributes via both methods.
Here, the headline attribute takes precedence over the headline property in the JSON object:
<script
type="application/json"
data-oc-sheet-v1
data-oc-sheet-v1.no-content-padding
data-oc-sheet-v1.headline="Headline via attribute"
>
{
"headline": "Headline via JSON object",
"fullHeight": true
}
</script>
API v1
Source: ./src/components/sheet/v1/SheetV1.API.g.mdx
Sheet v1 API
API: <oc-sheet-v1> (SheetV1)
A Sheet is a module that shows additional information or interactions on top of the current context.
Attributes / properties
| Attribute | Type | Default | Required | Description |
|---|---|---|---|---|
id |
string |
"computed" |
no | (Optional) Sets the id of the sheet instance. The id must be globally unique. If not provided, a random id is generated, and a warning is logged.Important The id must begin with a letter ([A-Za-z]). Starting the id with a number leads to errors. |
adaptive-alignment |
"left" | "right" | "center" |
"right" |
no | Sheet positioning in the viewport. - left : On desktop, it is shown on the left side. On mobile, it is shown from the bottom. - right : On desktop, it is shown on the right side. On mobile, it is shown from the bottom. - center : On desktop, it is shown in the center of the screen with a maximum width. On mobile, it is shown from the bottom. |
headline |
string |
undefined |
no | Sets the headline that is displayed in the header. Will be overrridden by the corresponding slot. Accepts plain text or HTML. If HTML is used, it is rendered inside an H1 element, thus only inline elements are allowed. |
headline-level |
1 | 2 | 3 | 4 | 5 | 6 |
undefined |
no | If set, renders the headline content inside a heading tag with the specified level. Only applies if the headline attribute is set.The heading level has no visual effect but provides semantic structure for screen readers. If not set, renders a div instead of a heading tag. |
hide-back-button |
boolean |
false |
no | Set to true to hide the back button. The back button is only visible if the sheet is opened on top of another sheet. The back button closes the current sheet and shows the previous one. If this prop is set to true, the back button is not shown. However this will not prevent the back navigation via the browser. |
hide-close-button |
boolean |
false |
no | Set to `true to hide the close button. The sheet can still be closed by clicking an area outside of it or by swiping down on mobile. |
hide-header |
boolean |
false |
no | Set to true to hide the header element. This also hides the header content that is passed via the headline attribute. Has no effect if the header slot is used. The content stretches until the top of sheet and the header buttons are still visible. |
disable-tracking |
boolean |
false |
no | If set to true, the sheet will not create a tracking context for the sheet instance. This is useful if the sheet is used in a context where tracking is not desired, such as in a modal or a dialog. |
open |
boolean |
false |
no | Sets the visibility state of the sheet. Set to true to open the sheet. Has the same effect as using the open or close method. |
disable-close-interactions |
boolean |
false |
no | Set to true to disable all interactions that would lead to closing the sheet, such as clicking outside of the sheet, swiping down on mobile or pressing the escape key. The sheet can only be closed programmatically. |
full-height |
boolean |
false |
no | Gives the content all available height. On desktop this pushes the actions slot down to the bottom of the sheet. On mobile, switches are shown without transitions. |
max-width-centered |
string |
48rem |
no | max width in any unit for the centered sheet e.g. 500px, 50%, 40rem |
extra-data |
Record<string, unknown> |
null |
no | In the sheet history management process it may be necessary to bind additional data to the sheet instance. This data is not used by the sheet itself, but can be used by the consumer to store additional information. The data will also be passed to the sheet events. |
allowed-error-status-codes |
number[] |
[] |
no | Defines a list of error status codes that are allowed to be displayed to a user. This only applies to server responses with cache-control: no-transform and content-type: text/html. Find more information in the error handling section. |
discard-content-on-inactive |
boolean |
false |
no | If set to true the external content will be discarded when the sheet becomes inactive. This means that the content will be removed from the DOM and will be reloaded when the sheet becomes active again. This applies to close and hide events.Implies the url or base64Url attribute is in use. |
no-content-padding |
boolean |
false |
no | Set to true to remove spacing around the content. |
url |
string |
undefined |
no | Sets a URL that points to a resource that is either same as the location or the allowed origin. On opening the sheet, the resource is loaded and displayed. The resource must be a valid HTML document and may contain scripts, for example to a svelte app. On changing the URL, the sheet reloads the content and replaces the existing one. |
base64-url |
string |
undefined |
no | Similar to the {@link url} attribute, but accepts a URL as a base64-encoded string. The string is decoded and processed in same way as the {@link url} prop. |
oc-aria-label |
string |
undefined |
no | Sets the ARIA label of the sheet. If not set, the headline attribute is used as the label. |
close-history-mode |
"pop" | "push" | "replace" |
"pop" |
no | controlls the way how the browser history is handled when the sheet is closed. The default value is pop, which means that the browser history will be rewound to the previous state. If set to push, a new history entry will be created, and if set to replace, the current history entry will be replaced.By push and replace will remove sheet scope from the state and commit the history as configured. This setting can be used in cases where sheet is not as a modal, but rather as a way to manipulate the history state of the application. This is used in the search filters for example. |
Slots
| Slot | Required | Description |
|---|---|---|
headline |
no | Sets the sheet headline that is displayed in the sheet header. Example: <div slot='headline'>My headline</div> |
default |
no | Sets the main text content of the sheet. |
loading-error |
no | Sets the error message that is displayed on loading content errors. Example: <div slot='loading-error'>My loading error</div> |
actions |
no | Holds the link components that provide interactions to the user. Example: <oc-link-v2 variant="underlined" href="#" slot="actions">Action 1</oc-link-v2> |
Events
| Event | Detail type | Description |
|---|---|---|
oc-sheet-presentation-start |
CustomEvent<DetailBase> |
Dispatched first time a sheet instance is activated while there is no other sheet instance active. Once a sheet instance activation (show) has been approved. New Presentation stack is created for the current presentation. The instance which is being opened will have this event dispatched. This event is not cancelable. |
oc-sheet-presentation-end |
CustomEvent<DetailBase> |
Dispatched after all sheet instnaces have been closed and the presentation stack has been cleared. This event will be dispatched for all sheet instances which have participated on the current presentation stack, thus giving the owner of the instance a chance to clean up or perform any other actions that are necessary after the presentation has ended. This event is not cancelable. |
oc-sheet-before-open |
CustomEvent<BeforeOpen> |
Dispatched immediately after the instance activation has been requested by the integating party. And before the activation process has started. This should give the the owner of the instance to confirm the activation and, for example, make sure that the instance exist in the DOM. The intergating party may cancel the event, which will prevent the activation process from starting. This may lead to confusion, because the user interaction may end up without any feedback. |
oc-sheet-open |
CustomEvent<Activation> |
Dispatched after the instance activation has completed and the element has been teleported to the end of the body element, and is now visible to DOM. The user agent can now properly draw the sheet element so the upcomming fly-in transition can be applied. |
oc-sheet-flyin |
CustomEvent<Activation> |
Dispatched after the sheet instance has begun its fly-in transition. This event is dispatched after the instance has become visible and the display transition has started. This event is not cancelable. |
oc-sheet-after-open |
CustomEvent<Activation> |
Dispatched after the sheet instance has been fully opened and the fly-in transition has completed. Also the eventually configured external content has been loaded and applied to the sheet DOM tree. This marks the end of the activation process, and the instance is now ready for user interaction.This event is not cancelable. |
oc-sheet-before-close |
CustomEvent<BeforeClose> |
Dispatched immediately after the instance deactivation has been requested by the integrating party, Or due to a user interaction, such as clicking the close button or swiping down on mobile. Also triggered by the back / forward browser navigation especially is this causes another sheet to be opened on top of the current one. The integrating party may cancel the event, which will prevent the deactivation process from starting. This may lead to confusion, because the user interaction may end up with sheet still being opened thus preventing the user from interacting with the web page. |
oc-sheet-flyout |
CustomEvent<Close> |
Dispatched after the sheet instance has begun its fly-out transition. This event is dispatched after the instance has become invisible and the display transition has started. This event is not cancelable. |
oc-sheet-close |
CustomEvent<Close> |
Dispatched after the sheet instance has been closed and the fly-out transition has completed. The instance has become inactive, and is scheduled to be teleported to its original position in the DOM tree, and set to display: none.This event is not cancelable. |
oc-sheet-after-close |
CustomEvent<Close> |
Dispatched after the sheet instance has been teleported back to its original position in the DOM tree, and set to display: none. This marks end of the deactivation process, the presentation stack may still be active and presenting other sheet instances. To run processes on presentation end, use the corresponding presentation-end event. |
oc-sheet-content-fetch |
CustomEvent<ContentFetch> |
Dispatched in case the sheet instance has become active and is being opened in the viewport. This event is dispatched after the external content has been fetched from the configured resource URL. The event will carry the resulting html text and the url used to fetch the content. The integrating party may inspect the raw html text and modify it if needed. The mutated html text will then be be parsed into DOM and applied to the sheet content. This allows the integrating party to modify the content before it is displayed to the user. This event is not cancelable |
oc-sheet-content-parse |
CustomEvent<ContentParsed> |
Dispatched after the external content has been fetched and processed into usable DOM Tree. Also the externally provided configuration has been extracted from the response, and will be applied to the sheet instance once applicable. The integrating party may inspect the processed DOM, and options and modify them if needed. Those modifications will be applied to the sheet DOM tree before the content is displayed. This event is not cancelable |
oc-sheet-content-error |
CustomEvent<ContentError> |
Dispatched in case the external content could not be fetched or processed. The event will carry the error object that caused the failure, which may be a network error, a parsing error, or any other error that occurred during the content fetching or processing. Also the response object, if available, and eventually the Processed DOM tree that was created out of the errored response. The integrating party may inspect the error and modify the error contents as needed. The integrating party may provide a fallback content by using the loading-error slot.This event is not cancelable |
oc-sheet-content-apply |
CustomEvent<ContentApply> |
Dispatched after the external content has been successfully fetched, processed and incerted into the sheet light DOM replacing the potentially existing content. The DOM has not been activated yet, any scripts that are included in the content have not been executed. The integrating party may inspect the content of the Sheet DOM and modify it if needed. After this event the content will be activated, scripts will be executed and the sheet fly-in transition will be triggered, thus making the content visible to the user. This event is not cancelable |
oc-sheet-content-discard |
CustomEvent<ContentDiscard> |
Dispatched when the instance is configured to discard the external content when the sheet becomes inactive. This event is dispatched after the external content has been removed from the intance DOM tree This event is not cancelable |
oc-property-change |
OcSheetV1Events["oc-property-change"] |
Whenever a property value changes, this event triggers. Use this event to track all property changes within the component. Refer to the Events documentation for more information. |
oc-mount |
{ component: string; } |
Fired when the component is mounted to the DOM. The event is fired when the onMount hook of the component is called by the runtime. |
oc-unmount |
{ component: string; } |
Fired when the component is unmounted from the DOM. The event is fired when the function returned by the onMount hook of the component is called by the runtime. |
Methods
back:(options?: ActivationOptions | undefined) => voidOpens the previously opened sheet, if available, by providing a back-button-like behavior. If there is no previous sheet, it closes the current sheet.
Parameters:
options:ActivationOptions | undefined
Returns:
voidExample:
const sheet = document.querySelector("oc-sheet-v1"); sheet.back();close:(options?: ActivationOptions | undefined) => voidCloses the sheet. Provides the same effect as setting the atrribute
opentofalse. In contrast to the {@link back} method this method closes alls sheet instances and clears the open sheet history.This method provides the same effect as clicking the close button.
Parameters:
options:ActivationOptions | undefined
Returns:
voidExample:
const sheet = document.querySelector("oc-sheet-v1"); sheet.close();getHeader:() => HTMLElement | null(deprecated: do not use this method, query from element instead)Returns the header element if it exists, otherwise returns null.
Returns:
HTMLElement | nullgetContent:() => HTMLElement | null(deprecated: do not use this method, query from element instead)Returns the content of the default slot wrapped in a div element, or null if no content elements are present.
Returns:
HTMLElement | nullgetActions:() => HTMLElement | null(deprecated: do not use this method, query from element instead)Returns the element assigned to the sheet actions slot, or null if no actions are present.
Returns:
HTMLElement | nullisContentApplied:() => booleanREturns a boolean indicating whether external content has been applied to the sheet DOM. This will be true if the
urlorbase64Urlattribute is set and external content has been loaded parsed and injected into the DOM of the sheet component successfully.This will be a falsy value if the sheet is not configured to load external content, or if the content has not been loaded yet, or if the content has been discarded, or if the content has been loaded but is errorneous.
Returns:
booleanExample:
const sheet = document.querySelector("oc-sheet-v1"); const contentIsApplied = sheet.isContentApplied();
CSS custom properties
| Custom property | Default | Description |
|---|---|---|
--content-background-color |
undefined |
Sets the background color of the sheet content area. |
--title-spacing |
undefined |
Controls how much the title should be indented. |
DOM lifecycle (v1)
Source: ./src/components/sheet/v1/DOM lifecycle.mdx
Sheet presentation and DOM lifecycle
This document explains the lifecycle of the oc-sheet-v1 component, including its states, the presentation stack, and how to work with events and signals for advanced integrations.
Skip to:
How the presentation stack manages sheet visibility
The presentation stack of the oc-sheet-v1 component manages how sheets are displayed and navigated within the application.
Opening and stacking sheets
When a sheet is present in the DOM but not opened, it is simply part of the markup and not visible. No presentation stack exists.
When a sheet is opened (e.g., by setting
open = trueor using the API):- If no stack exists, a new presentation stack is created.
- The opened sheet is added to the stack and becomes visible.
If another sheet is opened, it is added to the stack. Only the topmost sheet is visible; others in the stack are hidden.
Navigating back (such as using a back button) removes the topmost sheet from the stack and reveals the previous one.
The stack persists as long as at least one sheet is open. When all sheets are closed, the stack is destroyed.
This stack-based approach allows for complex flows, such as opening a new sheet from within another sheet and returning to the previous sheet without losing context.
Closing sheets
When closing a sheet, consider the following:
- When a sheet is opened, the DOM element must stay in the document where it was instantiated.
- You can only move a sheet within the DOM while it is closed. Once opened, the stack expects the sheet to exist until it is closed again.
Note
As long as a sheet is opened and part of the presentation stack, it must not be moved within the DOM or removed from the DOM. This can lead to undefined behavior and state. Remove sheets from the DOM only when there is no active presentation stack, i.e., when all sheets are closed and the stack is empty (
isActiveisfalse).
Checking the presentation stack status
You can check the status of the presentation stack using the following command in the browser console:
otto.components.sheetV1.current.get();
Executing this command returns the current presentation stack object, which includes information about the stack and its sheets:
isActive: Indicates whether there is an active presentation stack.length: Provides the number of sheets currently in the stack.
Sheet state transitions
The lifecycle of a sheet instance includes several distinct states and transitions:
- Created (inactive): The sheet instance exists in the DOM but is not visible and is not part of any presentation stack.
- Activation request: An activation event, such as
oc-sheet-before-open, triggers the opening of the sheet. - Presentation stack created: If no stack exists, a new presentation stack is created and the sheet is added to it.
- Activated: The sheet becomes visible and is now the top item in the stack.
- External content (optional): If the sheet loads external content, it fetches and processes the content before displaying it.
- Deactivation request: A deactivation event, such as
oc-sheet-before-close, triggers the closing of the sheet. - Deactivated: The sheet is removed from the stack. If other sheets remain, the previous sheet is shown. If the stack is empty, it is destroyed.
Signals and events
The sheet component supports both events and signals for integration.
- Events are stateless; they trigger listeners only when fired.
- Signals are stateful; they trigger subscribers whenever the state changes.
Events
Events are stateless notifications dispatched at specific points in the lifecycle of the sheet, such as oc-open, oc-close, oc-sheet-before-open, and oc-sheet-before-close.
Listen for these events to trigger actions. Events do not retain state and listeners are only called when the event is fired.
Signals
Signals, powered by the Nexus signaling API, are stateful and reactive. Subscribe to signals to react to changes in the sheet's state, not just when a specific event is fired.
The current signal
The sheetV1.current signal provides a reactive way to access the currently opened sheet instance.
Use it to update your UI or perform actions based on the active sheet.
To synchronously access the current sheet instance, use the get method of the signal.
The signal always reflects the current state.
For example:
import { sheetV1 } from "@otto-ec/otto-components/sheet";
// Get the current sheet instance
const currentSheet = sheetV1.current.get().instance;
// Returns null if no sheet is currently opened
// Watch for changes to the current sheet instance
sheetV1.current.subscribe((c) => {
console.log("Current sheet instance changed:", c.instance);
});
Variations (v1)
Source: ./src/components/sheet/v1/Variations.mdx
Variations
Listed below are the most common variations of the sheet component as well as specific component variations for different use cases.
You can explore all available options using the component configurator, adjust the component to your needs, and see the changes live in a preview canvas.
Default
Story: components-sheet-variations--default · tags: components, sheet, v1, variations
The default configuration has no content.
Args: id=default-sheet, oc-aria-label=default sheet without label, hide-header=true
<oc-sheet-v1 id="default-sheet" oc-aria-label="default sheet without label" hide-header></oc-sheet-v1>
Basic
Story: components-sheet-variations--basic-sheet · tags: components, sheet, v1, variations
Variant with a headline and a placeholder in the default slot.
Args: id=basic-sheet, headline=Basic sheet example, defaultSlot=(see snippet)
<oc-sheet-v1 id="basic-sheet" headline="Basic sheet example">
${unsafeHTML(headlineSlot)}
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:400px;">
PLACEHOLDER
</oc-placeholder-v1>
${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Basic sheet</h2>
<br />
<p>This option allows the content of the sheet to stretch out to the very top.</p>
<p>The header buttons are still visible and float above the content</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=basic-sheet size="50">
Open sheet
</oc-button-v1>
</p>
Story source (TypeScript, verbatim from Storybook)
{
name: "Basic",
args: {
id: "basic-sheet",
headline: "Basic sheet example",
defaultSlot: `
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:400px;">
PLACEHOLDER
</oc-placeholder-v1>
`
},
render({
defaultSlot,
loadingErrorSlot,
actionsSlot,
headlineSlot,
...props
}) {
return html`
<oc-sheet-v1 ${spread(props)}>
${unsafeHTML(headlineSlot)}${unsafeHTML(defaultSlot)}${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Basic sheet</h2>
<br />
<p>This option allows the content of the sheet to stretch out to the very top.</p>
<p>The header buttons are still visible and float above the content</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=${props.id} size="50">
Open sheet
</oc-button-v1>
</p>
`;
}
}
Center Sheet
Story: components-sheet-variations--center-sheet · tags: components, sheet, v1, variations
Variant with a headline and a placeholder in the default slot.
Args: id=center-sheet, adaptive-alignment=center, headline=Centered sheet example, defaultSlot=(see snippet)
<oc-sheet-v1 id="center-sheet" adaptive-alignment="center" headline="Centered sheet example">
${unsafeHTML(headlineSlot)}
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:400px;">
PLACEHOLDER
</oc-placeholder-v1>
${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Center sheet</h2>
<br />
<p>This option allows the content of the sheet to stretch out to the very top.</p>
<p>The header buttons are still visible and float above the content</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=center-sheet size="50">
Open sheet
</oc-button-v1>
</p>
Story source (TypeScript, verbatim from Storybook)
{
name: "Center Sheet",
args: {
id: "center-sheet",
"adaptive-alignment": "center",
headline: "Centered sheet example",
defaultSlot: `
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:400px;">
PLACEHOLDER
</oc-placeholder-v1>
`
},
render({
defaultSlot,
loadingErrorSlot,
actionsSlot,
headlineSlot,
...props
}) {
return html`
<oc-sheet-v1 ${spread(props)}>
${unsafeHTML(headlineSlot)}${unsafeHTML(defaultSlot)}${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Center sheet</h2>
<br />
<p>This option allows the content of the sheet to stretch out to the very top.</p>
<p>The header buttons are still visible and float above the content</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=${props.id} size="50">
Open sheet
</oc-button-v1>
</p>
`;
}
}
Full height
Story: components-sheet-variations--full-height · tags: components, sheet, v1, variations
Variation with full-height=true to make the sheet content take all the available space.
<oc-sheet-v1 id="full-height-1" headline="Sheet with full height" full-height>
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:400px;">
SHEET-1 CONTENT
</oc-placeholder-v1>
<div slot="actions">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="full-height-2">
Open Sheet-2
</oc-button-v1>
</div>
</oc-sheet-v1>
<oc-sheet-v1 id="full-height-2" headline="Sheet No. 2" full-height>
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:600px;">
SHEET-2 CONTENT
</oc-placeholder-v1>
</oc-sheet-v1>
<br />
<h2>Two sheets with full-height option</h2>
<br />
<p>The full-height option gives the sheet content all the available space.</p>
<p>As a consequence on desktop the actions slot content is pushed to the bottom.</p>
<p>
In case of a sheet content change (open a sheet while another is already open) no transition
animation is shown on mobile!
</p>
<br />
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="full-height-1" size="50">
Open Sheet-1
</oc-button-v1>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="full-height-2" size="50">
Open Sheet-2
</oc-button-v1>
</p>
<p></p>
Story source (TypeScript, verbatim from Storybook)
{
name: "Full height",
argTypes: hideControlsBadge(Metadata),
parameters: {
controls: {
disabled: true
}
},
render() {
return html`
<oc-sheet-v1 id="full-height-1" headline="Sheet with full height" full-height>
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:400px;">
SHEET-1 CONTENT
</oc-placeholder-v1>
<div slot="actions">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="full-height-2">
Open Sheet-2
</oc-button-v1>
</div>
</oc-sheet-v1>
<oc-sheet-v1 id="full-height-2" headline="Sheet No. 2" full-height>
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:600px;">
SHEET-2 CONTENT
</oc-placeholder-v1>
</oc-sheet-v1>
<br />
<h2>Two sheets with full-height option</h2>
<br />
<p>The full-height option gives the sheet content all the available space.</p>
<p>As a consequence on desktop the actions slot content is pushed to the bottom.</p>
<p>
In case of a sheet content change (open a sheet while another is already open) no transition
animation is shown on mobile!
</p>
<br />
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="full-height-1" size="50">
Open Sheet-1
</oc-button-v1>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="full-height-2" size="50">
Open Sheet-2
</oc-button-v1>
</p>
<p></p>
`;
}
}
With actions in footer
Story: components-sheet-variations--actions · tags: components, sheet, v1, variations
Variation with a headline set via the headline attribute, a placeholder in the default slot and action buttons in the actions slot.
Args: id=actions, headline=Sheet with action buttons in footer, defaultSlot=(see snippet), actionsSlot=(see snippet)
<oc-sheet-v1 id="actions" headline="Sheet with action buttons in footer">
${unsafeHTML(headlineSlot)}
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:600px;">
PLACEHOLDER
</oc-placeholder-v1>${unsafeHTML(loadingErrorSlot)}
<div slot="actions" style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<oc-button-v1 onclick="closest('oc-sheet-v1').open=false" variant="secondary">Close</oc-button-v1>
<oc-button-v1 variant="primary">Label</oc-button-v1>
</div>
</oc-sheet-v1>
<br />
<h2>Sheet with action buttons</h2>
<br />
<p>
Provide actions via the actions slot. The slot content is placed below the default content.
</p>
<p>
You can use any kind of content as actions but need to take care of the styling yourself.
</p>
<h2>Simple Close behaviour</h2>
<br />
<p>
The code tab provides an example that showcases how to implement a close button by adding an
inline onclick event to the button and how to use
<code>closest('oc-sheet-v1').open=false</code>
to close the sheet which is a parent of the interactive element.
</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=actions size="50">
Open sheet
</oc-button-v1>
</p>
Story source (TypeScript, verbatim from Storybook)
{
name: "With actions in footer",
args: {
id: "actions",
headline: "Sheet with action buttons in footer",
defaultSlot: `
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:600px;">
PLACEHOLDER
</oc-placeholder-v1>`,
actionsSlot: `
<div slot="actions" style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<oc-button-v1 onclick="closest('oc-sheet-v1').open=false" variant="secondary">Close</oc-button-v1>
<oc-button-v1 variant="primary">Label</oc-button-v1>
</div>`
},
render({
defaultSlot,
loadingErrorSlot,
actionsSlot,
headlineSlot,
...props
}) {
return html`
<oc-sheet-v1 ${spread(props)}>
${unsafeHTML(headlineSlot)}${unsafeHTML(defaultSlot)}${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Sheet with action buttons</h2>
<br />
<p>
Provide actions via the actions slot. The slot content is placed below the default content.
</p>
<p>
You can use any kind of content as actions but need to take care of the styling yourself.
</p>
<h2>Simple Close behaviour</h2>
<br />
<p>
The code tab provides an example that showcases how to implement a close button by adding an
inline onclick event to the button and how to use
<code>closest('oc-sheet-v1').open=false</code>
to close the sheet which is a parent of the interactive element.
</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=${props.id} size="50">
Open sheet
</oc-button-v1>
</p>
`;
}
}
Disabled Close Interactions
Story: components-sheet-variations--disabled-close-interactions · tags: components, sheet, v1, variations
Variation with a headline set via the headline attribute, a placeholder in the default slot and action buttons in the actions slot.
Args: id=actions, headline=Sheet with action buttons in footer, defaultSlot=(see snippet), actionsSlot=(see snippet), disable-close-interactions=true
<oc-sheet-v1 id="actions" headline="Sheet with action buttons in footer" disable-close-interactions>
${unsafeHTML(headlineSlot)}
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:600px;">
PLACEHOLDER
</oc-placeholder-v1>${unsafeHTML(loadingErrorSlot)}
<div slot="actions" style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<oc-button-v1 onclick="closest('oc-sheet-v1').open=false" variant="secondary">Close</oc-button-v1>
<oc-button-v1 variant="primary">Label</oc-button-v1>
</div>
</oc-sheet-v1>
<p>
This sheet can only be closed programmatically or by using the close button in the actions
slot. All other closing interactions like backdrop click, escape key press or drag to close
are disabled.
</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=actions size="50">
Open sheet
</oc-button-v1>
</p>
Story source (TypeScript, verbatim from Storybook)
{
name: "Disabled Close Interactions",
args: {
id: "actions",
headline: "Sheet with action buttons in footer",
defaultSlot: `
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:600px;">
PLACEHOLDER
</oc-placeholder-v1>`,
actionsSlot: `
<div slot="actions" style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<oc-button-v1 onclick="closest('oc-sheet-v1').open=false" variant="secondary">Close</oc-button-v1>
<oc-button-v1 variant="primary">Label</oc-button-v1>
</div>`,
"disable-close-interactions": true
},
render({
defaultSlot,
loadingErrorSlot,
actionsSlot,
headlineSlot,
...props
}) {
return html`
<oc-sheet-v1 ${spread(props)}>
${unsafeHTML(headlineSlot)}${unsafeHTML(defaultSlot)}${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<p>
This sheet can only be closed programmatically or by using the close button in the actions
slot. All other closing interactions like backdrop click, escape key press or drag to close
are disabled.
</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=${props.id} size="50">
Open sheet
</oc-button-v1>
</p>
`;
}
}
With headline
Story: components-sheet-variations--with-headline-slot · tags: components, sheet, v1, variations
Variation with the headline slot displaying a headline and the default slot showing a placeholder.
Args: id=headline-slot-sheet, headlineSlot=<h3 slot="headline" class="oc-headline-100">Sheet with headline slot</h3>, defaultSlot=(see snippet)
<oc-sheet-v1 id="headline-slot-sheet">
<h3 slot="headline" class="oc-headline-100">Sheet with headline slot</h3>
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:400px;">
PLACEHOLDER
</oc-placeholder-v1>
${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Sheet with a headline configured by its slot content</h2>
<br />
<p>A sheet´s headline can also be configured through a content slot element.</p>
<p>
Keep in mind that for all slot-content it's your responsibility to ensure the correct
semantic and styling.
</p>
<p>
For the <code>headline</code> slot use a headline element which contains the CSS class
"oc-headline-100":
</p>
<br />
<p>
<textarea
style="width: 100%; resize:none; font-family: Monospace; font-size:10px; border:0;"
aria-label="example"
disabled
>
<h3 slot="headline" class="oc-headline-100">This slot content is used as the sheet header</h3> </textarea>
</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=headline-slot-sheet size="50">
Open sheet
</oc-button-v1>
</p>
Story source (TypeScript, verbatim from Storybook)
{
name: "With headline",
args: {
id: "headline-slot-sheet",
headlineSlot: `<h3 slot="headline" class="oc-headline-100">Sheet with headline slot</h3>`,
defaultSlot: `
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:400px;">
PLACEHOLDER
</oc-placeholder-v1>
`
},
render({
defaultSlot,
headlineSlot,
loadingErrorSlot,
actionsSlot,
...props
}) {
return html`
<oc-sheet-v1 ${spread(props)}>
${unsafeHTML(headlineSlot)}${unsafeHTML(defaultSlot)}${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Sheet with a headline configured by its slot content</h2>
<br />
<p>A sheet´s headline can also be configured through a content slot element.</p>
<p>
Keep in mind that for all slot-content it's your responsibility to ensure the correct
semantic and styling.
</p>
<p>
For the <code>headline</code> slot use a headline element which contains the CSS class
"oc-headline-100":
</p>
<br />
<p>
<textarea
style="width: 100%; resize:none; font-family: Monospace; font-size:10px; border:0;"
aria-label="example"
disabled
>
<h3 slot="headline" class="oc-headline-100">This slot content is used as the sheet header</h3> </textarea>
</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=${props.id} size="50">
Open sheet
</oc-button-v1>
</p>
`;
}
}
Without header
Story: components-sheet-variations--without-header · tags: components, sheet, v1, variations
Variation with hide-header=true to hide the headline and shows a placeholder in the default slot.
Args: id=no-header, hide-header=true, oc-aria-label=Aria label for this sheet, defaultSlot=(see snippet)
<oc-sheet-v1 id="no-header" hide-header oc-aria-label="Aria label for this sheet">
${unsafeHTML(headlineSlot)}
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:400px;">
PLACEHOLDER
</oc-placeholder-v1>${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Sheet without header</h2>
<br />
<p>This option allows the content of the sheet to stretch out to the very top.</p>
<p>The header buttons are still visible and float above the content</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=no-header size="50">
Open sheet
</oc-button-v1>
</p>
Story source (TypeScript, verbatim from Storybook)
{
name: "Without header",
args: {
id: "no-header",
"hide-header": true,
"oc-aria-label": "Aria label for this sheet",
defaultSlot: `
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:400px;">
PLACEHOLDER
</oc-placeholder-v1>`
},
render({
defaultSlot,
loadingErrorSlot,
actionsSlot,
headlineSlot,
...props
}) {
return html`
<oc-sheet-v1 ${spread(props)}>
${unsafeHTML(headlineSlot)}${unsafeHTML(defaultSlot)}${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Sheet without header</h2>
<br />
<p>This option allows the content of the sheet to stretch out to the very top.</p>
<p>The header buttons are still visible and float above the content</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=${props.id} size="50">
Open sheet
</oc-button-v1>
</p>
`;
}
}
Demo: three sheets
Story: components-sheet-variations--three-sheets-example · tags: components, sheet, v1, variations
A demo with three sheets showcasing the difference between the open and content change methods.
<oc-sheet-v1 id="three-sheets-1" headline="Sheet No. 1">
<oc-placeholder-v1 class="oc-headline-100 oc-m-0" variant="text" style="height:400px;">
SHEET-1 CONTENT
</oc-placeholder-v1>
<br />
<br />
<oc-text-field-v1 name="myName" value="" type="text" placeholder="this is a placeholder">
Demo text field
</oc-text-field-v1>
<div slot="actions">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="three-sheets-2">
Open Sheet-2
</oc-button-v1>
</div>
</oc-sheet-v1>
<oc-sheet-v1 id="three-sheets-2" headline="Sheet No. 2">
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:300px;">
SHEET-2 CONTENT
</oc-placeholder-v1>
<div slot="actions">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="three-sheets-3">
Open Sheet-3
</oc-button-v1>
</div>
</oc-sheet-v1>
<oc-sheet-v1 id="three-sheets-3" headline="Sheet No. 3">
<oc-placeholder-v1 class="oc-headline-100 oc-m-0" variant="text" style="height:600px;">
SHEET-3 CONTENT
</oc-placeholder-v1>
<div slot="actions">
<oc-button-v1 href="https://www.otto.de">leave page</oc-button-v1>
</div>
</oc-sheet-v1>
<br />
<h2>Page with three sheets</h2>
<br />
<p>This story contains three different sheets and three buttons for opening each sheet.</p>
<p>
Sheet-1 and Sheet-2 also contain a button inside its content for opening another sheet.<br />
Note the difference in behavior when opening a new sheet while another one is already open.
</p>
<h2>Window History</h2>
<br />
<p>
The sheet instances push them into the window.history and also restore the open sheet in
case of a refresh or back navigation event which you can see by leaving the page from
sheet-3.
</p>
<br />
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="three-sheets-1" size="50">
Open Sheet-1
</oc-button-v1>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="three-sheets-2" size="50">
Open Sheet-2
</oc-button-v1>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="three-sheets-3" size="50">
Open Sheet-3
</oc-button-v1>
</p>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: three sheets",
parameters: {
controls: {
disabled: true
}
},
argTypes: hideControlsBadge(Metadata),
render() {
const {
events
} = otto.components.sheetV1;
events.beforeOpen.sub(e => console.log("before open", e));
events.open.sub(e => console.log("open", e));
events.afterOpen.sub(e => console.log("after open", e));
events.beforeClose.sub(e => console.log("before close", e));
events.close.sub(e => console.log("close", e));
events.afterClose.sub(e => console.log("after close", e));
const legacyEvents = events as unknown as Record<string, NexusSignal<DetailBase>>;
legacyEvents.beforeContentChange.sub(e => console.log("before content change", e));
legacyEvents.contentChangeShow.sub(e => console.log("content change show", e));
legacyEvents.contentChangeHide.sub(e => console.log("content change hide", e));
legacyEvents.contentLoaded.sub(e => console.log("content loaded", e));
legacyEvents.contentLoadingError.sub(e => console.log("content loading error", e));
return html`
<oc-sheet-v1 id="three-sheets-1" headline="Sheet No. 1">
<oc-placeholder-v1 class="oc-headline-100 oc-m-0" variant="text" style="height:400px;">
SHEET-1 CONTENT
</oc-placeholder-v1>
<br />
<br />
<oc-text-field-v1 name="myName" value="" type="text" placeholder="this is a placeholder">
Demo text field
</oc-text-field-v1>
<div slot="actions">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="three-sheets-2">
Open Sheet-2
</oc-button-v1>
</div>
</oc-sheet-v1>
<oc-sheet-v1 id="three-sheets-2" headline="Sheet No. 2">
<oc-placeholder-v1 class="oc-headline-100" variant="text" style="height:300px;">
SHEET-2 CONTENT
</oc-placeholder-v1>
<div slot="actions">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="three-sheets-3">
Open Sheet-3
</oc-button-v1>
</div>
</oc-sheet-v1>
<oc-sheet-v1 id="three-sheets-3" headline="Sheet No. 3">
<oc-placeholder-v1 class="oc-headline-100 oc-m-0" variant="text" style="height:600px;">
SHEET-3 CONTENT
</oc-placeholder-v1>
<div slot="actions">
<oc-button-v1 href="https://www.otto.de">leave page</oc-button-v1>
</div>
</oc-sheet-v1>
<br />
<h2>Page with three sheets</h2>
<br />
<p>This story contains three different sheets and three buttons for opening each sheet.</p>
<p>
Sheet-1 and Sheet-2 also contain a button inside its content for opening another sheet.<br />
Note the difference in behavior when opening a new sheet while another one is already open.
</p>
<h2>Window History</h2>
<br />
<p>
The sheet instances push them into the window.history and also restore the open sheet in
case of a refresh or back navigation event which you can see by leaving the page from
sheet-3.
</p>
<br />
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="three-sheets-1" size="50">
Open Sheet-1
</oc-button-v1>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="three-sheets-2" size="50">
Open Sheet-2
</oc-button-v1>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="three-sheets-3" size="50">
Open Sheet-3
</oc-button-v1>
</p>
`;
}
}
Demo: programmatically created sheet
Story: components-sheet-variations--programmatic-sheet · tags: components, sheet, v1, variations
A demo showcasing how to programmatically create a sheet and open it.
<h2>Programatic sheet creation</h2>
<br />
<p>
<oc-text-field-v1
id="sheet-headline"
name="sheet-label"
placeholder="Sheet label"
value="My programmatically created sheet"
>Sheet label
</oc-text-field-v1>
</p>
<p>
<oc-text-area-v1
id="sheet-content"
name="sheet-content"
maxlength="4000"
placeholder="Sheet content"
value="<p>This is the content of my programmatically created sheet</p>"
></oc-text-area-v1>
</p>
<div style="display: flex; gap: 10px;">
<oc-button-v1 id="create" size="50">Create sheet</oc-button-v1>
<oc-button-v1 id="open" size="50" disabled="">Open sheet</oc-button-v1>
</div>
<br />
<p>
You can programmatically create a new sheet element by creating a new sheet element and
attaching it to the document.
</p>
<p>For example:</p>
<br />
<pre>
function createSheet() {
const sheetElement = document.createElement("oc-sheet-v1");
sheetElement.id = "prog-sheet";
sheetElement.open = true;
// Set sheet content
sheetElement.headline = "My sheet headline";
sheetElement.innerHTML = "Any content";
document.querySelector("body").append(sheetElement);
}
function openSheet() {
const sheetElement = document.getElementById("prog-sheet");
sheetElement.open = true;
}
</pre>
<br />
<p>
On page load or when navigating to a page that had an opened sheet, you must handle the
content of the automatically created sheet instance.
</p>
<pre>
// Once the module is loaded, check if there is an open sheet instance like so:
otto.components.sheetV1.getOpenSheet().then((sheetElement) => {
if (sheetElement.id === SHEET_ID) {
// Set the sheet content on auto created sheet instance.
sheetElement.headline = "My sheet headline";
sheetElement.innerHTML = "Any content";
}
});
</pre>
<script>
(() => {
const SHEET_ID = "prog-sheet";
const createButton = document.getElementById("create");
const openButton = document.getElementById("open");
const sheetHeadline = document.getElementById("sheet-headline");
const sheetContent = document.getElementById("sheet-content");
createButton.addEventListener("click", createSheet);
openButton.addEventListener("click", openSheet);
function setSheetProps(sheet) {
sheet.headline = sheetHeadline.value;
sheet.innerHTML = sheetContent.value;
sheet.contentPadding = true;
createButton.disabled = true;
openButton.disabled = false;
}
function createSheet() {
const sheetElement = document.createElement("oc-sheet-v1");
sheetElement.id = SHEET_ID;
const parentElement = document.querySelector("body");
const previousSheetElement = document.getElementById(SHEET_ID);
if (previousSheetElement) {
previousSheetElement.remove();
}
setSheetProps(sheetElement);
parentElement.append(sheetElement);
}
otto.components.sheetV1.getOpenSheet().then((sheetElement) => {
if (sheetElement?.id === SHEET_ID) {
setSheetProps(sheetElement);
}
});
function openSheet() {
const sheetElement = document.getElementById(SHEET_ID);
sheetElement.open = true;
}
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: programmatically created sheet",
argTypes: hideControlsBadge(Metadata),
parameters: {
controls: {
disabled: true
}
},
render() {
return html`
<h2>Programatic sheet creation</h2>
<br />
<p>
<oc-text-field-v1
id="sheet-headline"
name="sheet-label"
placeholder="Sheet label"
value="My programmatically created sheet"
>Sheet label
</oc-text-field-v1>
</p>
<p>
<oc-text-area-v1
id="sheet-content"
name="sheet-content"
maxlength="4000"
placeholder="Sheet content"
value="<p>This is the content of my programmatically created sheet</p>"
></oc-text-area-v1>
</p>
<div style="display: flex; gap: 10px;">
<oc-button-v1 id="create" size="50">Create sheet</oc-button-v1>
<oc-button-v1 id="open" size="50" disabled="">Open sheet</oc-button-v1>
</div>
<br />
<p>
You can programmatically create a new sheet element by creating a new sheet element and
attaching it to the document.
</p>
<p>For example:</p>
<br />
<pre>
function createSheet() {
const sheetElement = document.createElement("oc-sheet-v1");
sheetElement.id = "prog-sheet";
sheetElement.open = true;
// Set sheet content
sheetElement.headline = "My sheet headline";
sheetElement.innerHTML = "Any content";
document.querySelector("body").append(sheetElement);
}
function openSheet() {
const sheetElement = document.getElementById("prog-sheet");
sheetElement.open = true;
}
</pre>
<br />
<p>
On page load or when navigating to a page that had an opened sheet, you must handle the
content of the automatically created sheet instance.
</p>
<pre>
// Once the module is loaded, check if there is an open sheet instance like so:
otto.components.sheetV1.getOpenSheet().then((sheetElement) => {
if (sheetElement.id === SHEET_ID) {
// Set the sheet content on auto created sheet instance.
sheetElement.headline = "My sheet headline";
sheetElement.innerHTML = "Any content";
}
});
</pre>
<script>
(() => {
const SHEET_ID = "prog-sheet";
const createButton = document.getElementById("create");
const openButton = document.getElementById("open");
const sheetHeadline = document.getElementById("sheet-headline");
const sheetContent = document.getElementById("sheet-content");
createButton.addEventListener("click", createSheet);
openButton.addEventListener("click", openSheet);
function setSheetProps(sheet) {
sheet.headline = sheetHeadline.value;
sheet.innerHTML = sheetContent.value;
sheet.contentPadding = true;
createButton.disabled = true;
openButton.disabled = false;
}
function createSheet() {
const sheetElement = document.createElement("oc-sheet-v1");
sheetElement.id = SHEET_ID;
const parentElement = document.querySelector("body");
const previousSheetElement = document.getElementById(SHEET_ID);
if (previousSheetElement) {
previousSheetElement.remove();
}
setSheetProps(sheetElement);
parentElement.append(sheetElement);
}
otto.components.sheetV1.getOpenSheet().then((sheetElement) => {
if (sheetElement?.id === SHEET_ID) {
setSheetProps(sheetElement);
}
});
function openSheet() {
const sheetElement = document.getElementById(SHEET_ID);
sheetElement.open = true;
}
})();
</script>
`;
}
}
Demo: load remote content
Story: components-sheet-variations--loaded-content · tags: components, sheet, v1, variations
A demo showcasing how to load remote HTML content from a URL.
Args: id=loading-content-sheet, headline=Sheet with loaded content, url=https://www.otto.de/assets-storybook/assets/sheet-content.rP5OITvp.html, defaultSlot=(see snippet), loadingErrorSlot=Loading Error Fallback
<oc-sheet-v1 id="loading-content-sheet" headline="Sheet with loaded content" url="https://www.otto.de/assets-storybook/assets/sheet-content.rP5OITvp.html">
<p>
<oc-spinner-v1 size="100" variant="default" class="spinner"></oc-spinner-v1>loading
content ...
</p>
Loading Error Fallback ${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<p>
When loading content, use the <code>default</code> slot to show some loading indication to
the user.
</p>
<p>
When a sheet is opened it tries to load content from the URL that is defined via the
<code>url</code> or <code>base64-url</code> attribute.
</p>
<p>
While loading, the default content provided via the <code>default</code> slot is shown (if
available) and then replaced by the loaded content. Use this behavior to display some
loading information.
</p>
<div style="display: flex; gap: 10px;">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=loading-content-sheet size="50">
Open sheet
</oc-button-v1>
</div>
<style>
.spinner {
float: left;
margin-right: 12px;
}
.loading-error {
padding: 8px 16px;
color: red;
font-weight: bold;
}
</style>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: load remote content",
parameters: {
// Snapshot contains no value
chromatic: {
disableSnapshot: true
}
},
args: {
id: "loading-content-sheet",
headline: "Sheet with loaded content",
url: `${externalSheetContentUrl}`,
defaultSlot: `
<p>
<oc-spinner-v1 size="100" variant="default" class="spinner"></oc-spinner-v1>loading
content ...
</p>
`,
loadingErrorSlot: `Loading Error Fallback`
},
render({
defaultSlot,
loadingErrorSlot,
actionsSlot,
...props
}) {
return html`
<oc-sheet-v1 ${spread(props)}>
${unsafeHTML(defaultSlot)} ${unsafeHTML(loadingErrorSlot)} ${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<p>
When loading content, use the <code>default</code> slot to show some loading indication to
the user.
</p>
<p>
When a sheet is opened it tries to load content from the URL that is defined via the
<code>url</code> or <code>base64-url</code> attribute.
</p>
<p>
While loading, the default content provided via the <code>default</code> slot is shown (if
available) and then replaced by the loaded content. Use this behavior to display some
loading information.
</p>
<div style="display: flex; gap: 10px;">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=${props.id} size="50">
Open sheet
</oc-button-v1>
</div>
<style>
.spinner {
float: left;
margin-right: 12px;
}
.loading-error {
padding: 8px 16px;
color: red;
font-weight: bold;
}
</style>
`;
}
}
Demo: change sheet URL
Story: components-sheet-variations--change-sheet-url · tags: components, sheet, v1, variations
Args: id=loading-content-sheet, headline=Sheet with changed URL, defaultSlot=(see snippet), loadingErrorSlot=Loading Error Fallback
<oc-sheet-v1 id="loading-content-sheet" headline="Sheet with changed URL">
<p>
<oc-spinner-v1 size="100" variant="default" class="spinner"></oc-spinner-v1>loading
content ...
</p>
Loading Error Fallback ${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<p>
When loading content, use the <code>default</code> slot to show some loading indication
to the user.
</p>
<p>
It is possible to change the <code>URL</code> property of the sheet instance. This triggers a reload
of the content from the new URL the next time the sheet is opened.
You can enforce a reload of the sheet content by using cache-busting techniques
like adding a random query parameter to the URL. For example: <code>?v=Math.random()</code>
or: <code>?v=Date.now()</code>
</p>
<p>
Do this only while the sheet instance is closed to ensure tracking consistency.
</>
<div style="display: flex; gap: 10px;">
<oc-button-v1 data-oc-sheet-v1-create='{ "id": "loading-content-sheet", "url": "${externalSheetContentUrl}" }' size="50">
Open Sheet with first URL
</oc-button-v1>
<oc-button-v1 data-oc-sheet-v1-create='{ "id": "loading-content-sheet", "url": "${externalSheetContentExtraUrl}" }' size="50">
Open Sheet with second URL
</oc-button-v1>
</div>
<style>
.spinner { float: left; margin-right: 12px; }
.loading-error { padding: 8px 16px; color: red; font-weight: bold; }
</style>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: change sheet URL",
parameters: {
// Snapshot contains no value
chromatic: {
disableSnapshot: true
}
},
args: {
id: "loading-content-sheet",
headline: "Sheet with changed URL",
defaultSlot: `
<p>
<oc-spinner-v1 size="100" variant="default" class="spinner"></oc-spinner-v1>loading
content ...
</p>
`,
loadingErrorSlot: `Loading Error Fallback`
},
render({
defaultSlot,
loadingErrorSlot,
actionsSlot,
...props
}) {
return html`
<oc-sheet-v1 ${spread(props)}>
${unsafeHTML(defaultSlot)} ${unsafeHTML(loadingErrorSlot)} ${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<p>
When loading content, use the <code>default</code> slot to show some loading indication
to the user.
</p>
<p>
It is possible to change the <code>URL</code> property of the sheet instance. This triggers a reload
of the content from the new URL the next time the sheet is opened.
You can enforce a reload of the sheet content by using cache-busting techniques
like adding a random query parameter to the URL. For example: <code>?v=Math.random()</code>
or: <code>?v=Date.now()</code>
</p>
<p>
Do this only while the sheet instance is closed to ensure tracking consistency.
</>
<div style="display: flex; gap: 10px;">
<oc-button-v1 data-oc-sheet-v1-create='{ "id": "${props.id}", "url": "${externalSheetContentUrl}" }' size="50">
Open Sheet with first URL
</oc-button-v1>
<oc-button-v1 data-oc-sheet-v1-create='{ "id": "${props.id}", "url": "${externalSheetContentExtraUrl}" }' size="50">
Open Sheet with second URL
</oc-button-v1>
</div>
<style>
.spinner { float: left; margin-right: 12px; }
.loading-error { padding: 8px 16px; color: red; font-weight: bold; }
</style>
`;
}
}
Demo: load remote content with options
Story: components-sheet-variations--loaded-content-with-options · tags: components, sheet, v1, variations
A demo showcasing a sheet that loads remote content from a URL defined in the url´ or base64-urlattribute with thedefault` slot showing a loading indication.
Here, the remote content contains a script block with options that are applied to the sheet instance.
Args: id=loading-content-sheet-with-options, url=https://www.otto.de/assets-storybook/assets/content-with-options.D_Y4gyaQ.html, defaultSlot=(see snippet)
<oc-sheet-v1 id="loading-content-sheet-with-options" url="https://www.otto.de/assets-storybook/assets/content-with-options.D_Y4gyaQ.html">
<p>
<oc-spinner-v1 size="100" variant="default" class="spinner"></oc-spinner-v1>loading
content ...
</p>
${unsafeHTML(loadingErrorSlot)} ${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<p>
When loading content, use the <code>default</code> slot to show some loading indication to
the user.
</p>
<p>
After external content is loaded, it may contain a <code>script</code> block with options,
which can be aplied to the sheet instance from the exernal content.
</p>
<div style="display: flex; gap: 10px;">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=loading-content-sheet-with-options size="50">
Open sheet
</oc-button-v1>
</div>
<style>
.spinner {
float: left;
margin-right: 12px;
}
.loading-error {
padding: 8px 16px;
color: red;
font-weight: bold;
}
</style>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: load remote content with options",
parameters: {
// Snapshot contains no value
chromatic: {
disableSnapshot: true
}
},
args: {
id: "loading-content-sheet-with-options",
url: `${externalSheetContentWithOptionsUrl}`,
defaultSlot: `
<p>
<oc-spinner-v1 size="100" variant="default" class="spinner"></oc-spinner-v1>loading
content ...
</p>
`
},
render({
defaultSlot,
loadingErrorSlot,
actionsSlot,
...props
}) {
return html`
<oc-sheet-v1 ${spread(props)}>
${unsafeHTML(defaultSlot)} ${unsafeHTML(loadingErrorSlot)} ${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<p>
When loading content, use the <code>default</code> slot to show some loading indication to
the user.
</p>
<p>
After external content is loaded, it may contain a <code>script</code> block with options,
which can be aplied to the sheet instance from the exernal content.
</p>
<div style="display: flex; gap: 10px;">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=${props.id} size="50">
Open sheet
</oc-button-v1>
</div>
<style>
.spinner {
float: left;
margin-right: 12px;
}
.loading-error {
padding: 8px 16px;
color: red;
font-weight: bold;
}
</style>
`;
}
}
Demo: load remote legacy content
Story: components-sheet-variations--loaded-legacy-content · tags: components, sheet, v1, variations
A demo showcasing the ability of the sheet to deal with legacy content.
A headline embedded via a data-title attribute is transformed to a headline slot element.
Args: id=loading-legacy-content-sheet, url=https://www.otto.de/assets-storybook/assets/sheet-content.rP5OITvp.html
<oc-sheet-v1 id="loading-legacy-content-sheet" url="https://www.otto.de/assets-storybook/assets/sheet-content.rP5OITvp.html">
${unsafeHTML(defaultSlot)} ${unsafeHTML(loadingErrorSlot)} ${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<p>
This demo showcases the ability of the sheet to deal with legacy features in the context of
loaded content.
</p>
<p>
Here, the sheet headline is embedded via a data-title attribute in the content and is
transformed to a headline slot element.
</p>
<div style="display: flex; gap: 10px;">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=loading-legacy-content-sheet size="50"
>Open sheet</oc-button-v1
>
</div>
<style>
.spinner {
float: left;
margin-right: 12px;
}
.loading-error {
padding: 8px 16px;
color: red;
font-weight: bold;
}
</style>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: load remote legacy content",
parameters: {
// Snapshot contains no value
chromatic: {
disableSnapshot: true
}
},
args: {
id: "loading-legacy-content-sheet",
url: `${externalSheetContentUrl}`
},
render({
defaultSlot,
loadingErrorSlot,
actionsSlot,
...props
}) {
return html`
<oc-sheet-v1 ${spread(props)}>
${unsafeHTML(defaultSlot)} ${unsafeHTML(loadingErrorSlot)} ${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<p>
This demo showcases the ability of the sheet to deal with legacy features in the context of
loaded content.
</p>
<p>
Here, the sheet headline is embedded via a data-title attribute in the content and is
transformed to a headline slot element.
</p>
<div style="display: flex; gap: 10px;">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=${props.id} size="50"
>Open sheet</oc-button-v1
>
</div>
<style>
.spinner {
float: left;
margin-right: 12px;
}
.loading-error {
padding: 8px 16px;
color: red;
font-weight: bold;
}
</style>
`;
}
}
Demo: content loading error
Story: components-sheet-variations--content-loading-error · tags: components, sheet, v1, variations
A demo showcasing a sheet with the loading-error slot displaying a loading error.
Args: id=loading-error-sheet, headline=Sheet with content content error, url=/wrong-path/unknown-file.html, defaultSlot=(see snippet), loadingErrorSlot=(see snippet)
<oc-sheet-v1 id="loading-error-sheet" headline="Sheet with content content error" url="/wrong-path/unknown-file.html">
<p>
<oc-spinner-v1 size="100" variant="default" class="spinner"></oc-spinner-v1>loading
content ...
</p>
<div slot="loading-error" class="loading-error">
<oc-banner-v1 variant="error">Ups, something went wrong!</oc-banner-v1>
</div>
${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Content loading error</h2>
<br />
<p>
When loading content, use the <code>default</code> slot to show some loading indication to
the user.
</p>
<p>
In case of a content loading error, the content of the <code>loading-error</code> slot is
shown.
</p>
<div style="display: flex; gap: 10px;">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=loading-error-sheet size="50"
>Open sheet</oc-button-v1
>
</div>
<style>
.sheet-content-container {
padding: 8px 16px;
}
.spinner {
float: left;
margin-right: 12px;
}
.loading-error {
padding: 8px 16px;
margin-bottom: 32px;
}
</style>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: content loading error",
parameters: {
// Snapshot contains no value (sheet is closed on default)
chromatic: {
disableSnapshot: true
}
},
args: {
id: "loading-error-sheet",
headline: "Sheet with content content error",
url: "/wrong-path/unknown-file.html",
defaultSlot: `
<p>
<oc-spinner-v1 size="100" variant="default" class="spinner"></oc-spinner-v1>loading
content ...
</p>
`,
loadingErrorSlot: `
<div slot="loading-error" class="loading-error">
<oc-banner-v1 variant="error">Ups, something went wrong!</oc-banner-v1>
</div>
`
},
render({
defaultSlot,
loadingErrorSlot,
actionsSlot,
...props
}) {
return html`
<oc-sheet-v1 ${spread(props)}>
${unsafeHTML(defaultSlot)} ${unsafeHTML(loadingErrorSlot)} ${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Content loading error</h2>
<br />
<p>
When loading content, use the <code>default</code> slot to show some loading indication to
the user.
</p>
<p>
In case of a content loading error, the content of the <code>loading-error</code> slot is
shown.
</p>
<div style="display: flex; gap: 10px;">
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=${props.id} size="50"
>Open sheet</oc-button-v1
>
</div>
<style>
.sheet-content-container {
padding: 8px 16px;
}
.spinner {
float: left;
margin-right: 12px;
}
.loading-error {
padding: 8px 16px;
margin-bottom: 32px;
}
</style>
`;
}
}
Demo: custom background color
Story: components-sheet-variations--custom-background · tags: components, sheet, v1, variations
A demo showcasing a sheet with a custom background color in the default slot and action buttons in the actions slot.
Args: id=custom-background, headline=Sheet with custom background, full-height=true, no-content-padding=true, --content-background-color=var(--oc-semantic-color-frame-background), defaultSlot=(see snippet), actionsSlot=(see snippet)
<oc-sheet-v1 class="${className}" id="custom-background" headline="Sheet with custom background" full-height no-content-padding style="--content-background-color: var(--oc-semantic-color-frame-background)">
<div class="oc-copy-100">
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam amet architecto
aspernatur assumenda eveniet expedita libero modi odit optio placeat, quae sapiente sed
sequi tempore vel. Ipsam iure qui quis.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam amet architecto
aspernatur assumenda eveniet expedita libero modi odit optio placeat, quae sapiente sed
sequi tempore vel. Ipsam iure qui quis.
</p>
</div> ${unsafeHTML(loadingErrorSlot)}
<div slot="actions" style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<oc-button-v1 variant="secondary">Label</oc-button-v1>
<oc-button-v1 variant="primary">Label</oc-button-v1>
</div>
</oc-sheet-v1>
<p>To set a custom background color, apply styles to the sheet content.</p>
<p>
The <code>full-height</code> option can be applied additionally to make the content take all
the available space.
</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=custom-background size="50"
>Open sheet</oc-button-v1
>
</p>
${css}
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: custom background color",
args: {
id: "custom-background",
headline: "Sheet with custom background",
"full-height": true,
"no-content-padding": true,
"--content-background-color": "var(--oc-semantic-color-frame-background)",
defaultSlot: `
<div class="oc-copy-100">
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam amet architecto
aspernatur assumenda eveniet expedita libero modi odit optio placeat, quae sapiente sed
sequi tempore vel. Ipsam iure qui quis.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam amet architecto
aspernatur assumenda eveniet expedita libero modi odit optio placeat, quae sapiente sed
sequi tempore vel. Ipsam iure qui quis.
</p>
</div>`,
actionsSlot: `
<div slot="actions" style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<oc-button-v1 variant="secondary">Label</oc-button-v1>
<oc-button-v1 variant="primary">Label</oc-button-v1>
</div>`
},
render({
defaultSlot,
loadingErrorSlot,
actionsSlot,
...props
}) {
const {
css,
className
} = cssVariablesExample(props);
return html`
<oc-sheet-v1 class="${className}" ${spread(props)}>
${unsafeHTML(defaultSlot)} ${unsafeHTML(loadingErrorSlot)} ${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<p>To set a custom background color, apply styles to the sheet content.</p>
<p>
The <code>full-height</code> option can be applied additionally to make the content take all
the available space.
</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id=${props.id} size="50"
>Open sheet</oc-button-v1
>
</p>
${css}
`;
}
}
Demo: create sheet on click
Story: components-sheet-variations--create-sheet-on-click · tags: components, sheet, v1, variations
Multiple demos showcasing how to create sheets on click by using data attributes.
<p>Trigger the creation of a sheet by putting certain data attributes on any HTML element:</p>
<br />
<p>
<textarea
style="width: 100%; height: 8rem; resize:none; font-family: Monospace; border:0;"
aria-label="example"
disabled
>
<p
data-oc-sheet-v1-create
data-oc-sheet-v1-create.id="sheet-usk18
data-oc-sheet-v1-create.url="${externalSheetContentUrl}"
data-oc-sheet-v1-create.full-height
>*** click here ***</p>
</textarea>
</p>
<oc-button-v1
size="50"
data-oc-sheet-v1-create
data-oc-sheet-v1-create.id="sheet-usk18"
data-oc-sheet-v1-create.url="${externalSheetContentUrl}"
data-oc-sheet-v1-create.full-height
>
*** click here ***
</oc-button-v1>
<br />
<hr />
<br />
<p>
You can also set <code>extra-data</code> containing arbitrary serializable data to be reused
on sheet activation. This data is persisted, and then passed to various sheets events for
further usage. The <code>extra-data</code> must be set while the sheet instance is hidden.
</p>
<br />
<p>
<textarea
style="width: 100%; height: 8rem; resize:none; font-family: Monospace; border:0;"
aria-label="example"
disabled
>
<p
data-oc-sheet-v1-create
data-oc-sheet-v1-create.url="${externalSheetContentUrl}"
data-oc-sheet-v1-create.full-height
data-oc-sheet-v1-create.extra-data='{"myData": "myValue"}'
>*** click here ***</p>
</textarea>
</p>
<oc-button-v1
size="50"
data-oc-sheet-v1-create
data-oc-sheet-v1-create.url="${externalSheetContentUrl}"
data-oc-sheet-v1-create.full-height
data-oc-sheet-v1-create.extra-data='{"myData": "myValue"}'
>
*** click here ***
</oc-button-v1>
<br />
<hr />
<br />
<p>You can also pass in the whole configuration as a stringified JSON object.</p>
<p>
<b>Note:</b> When working with stringified JSON objects, use the camelCase notation for
property names!
</p>
<br />
<p>
<textarea
style="width: 100%; height: 8rem; resize:none; font-family: Monospace; border:0;"
aria-label="example"
disabled
>
<p
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
>*** click here ***</p>
</textarea>
</p>
<oc-button-v1
size="50"
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.headline='<span style="color: cyan">Custom Headline</span>'
>
*** click here ***
</oc-button-v1>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: create sheet on click",
parameters: {
chromatic: {
hideInChromatic: true
}
},
args: {},
render() {
otto.components.sheetV1.events.open.sub(console.log);
otto.components.sheetV1.events.close.sub(console.log);
return html`
<p>Trigger the creation of a sheet by putting certain data attributes on any HTML element:</p>
<br />
<p>
<textarea
style="width: 100%; height: 8rem; resize:none; font-family: Monospace; border:0;"
aria-label="example"
disabled
>
<p
data-oc-sheet-v1-create
data-oc-sheet-v1-create.id="sheet-usk18
data-oc-sheet-v1-create.url="${externalSheetContentUrl}"
data-oc-sheet-v1-create.full-height
>*** click here ***</p>
</textarea>
</p>
<oc-button-v1
size="50"
data-oc-sheet-v1-create
data-oc-sheet-v1-create.id="sheet-usk18"
data-oc-sheet-v1-create.url="${externalSheetContentUrl}"
data-oc-sheet-v1-create.full-height
>
*** click here ***
</oc-button-v1>
<br />
<hr />
<br />
<p>
You can also set <code>extra-data</code> containing arbitrary serializable data to be reused
on sheet activation. This data is persisted, and then passed to various sheets events for
further usage. The <code>extra-data</code> must be set while the sheet instance is hidden.
</p>
<br />
<p>
<textarea
style="width: 100%; height: 8rem; resize:none; font-family: Monospace; border:0;"
aria-label="example"
disabled
>
<p
data-oc-sheet-v1-create
data-oc-sheet-v1-create.url="${externalSheetContentUrl}"
data-oc-sheet-v1-create.full-height
data-oc-sheet-v1-create.extra-data='{"myData": "myValue"}'
>*** click here ***</p>
</textarea>
</p>
<oc-button-v1
size="50"
data-oc-sheet-v1-create
data-oc-sheet-v1-create.url="${externalSheetContentUrl}"
data-oc-sheet-v1-create.full-height
data-oc-sheet-v1-create.extra-data='{"myData": "myValue"}'
>
*** click here ***
</oc-button-v1>
<br />
<hr />
<br />
<p>You can also pass in the whole configuration as a stringified JSON object.</p>
<p>
<b>Note:</b> When working with stringified JSON objects, use the camelCase notation for
property names!
</p>
<br />
<p>
<textarea
style="width: 100%; height: 8rem; resize:none; font-family: Monospace; border:0;"
aria-label="example"
disabled
>
<p
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
>*** click here ***</p>
</textarea>
</p>
<oc-button-v1
size="50"
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.headline='<span style="color: cyan">Custom Headline</span>'
>
*** click here ***
</oc-button-v1>
`;
}
}
Demo: create sheet on click (advanced)
Story: components-sheet-variations--create-sheet-on-click-advanced · tags: components, sheet, v1, variations
Advanced demos showcasing how to create sheets on click by using data attributes with an additional data set.
<br />
<h2>Pass Additional Data Set</h2>
<br />
<p>
You can pass an additional data set to the created sheet instance, for example to configure
tracking with tr-v1.
</p>
<p>Keep in mind to escape the JSON content like dataContainer for trV1.</p>
<br />
<p>
<textarea
style="width: 100%; height: 8rem; resize:none; font-family: Monospace; border:0;"
aria-label="example"
disabled
>
<oc-button-v1
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.data-set="${JSON.stringify({
trV1: JSON.stringify({
ot_label: ["some", "foo"]
}),
"trV1.track": "oc-open,oc-close"
})}"
>
Open sheet with tracking
</oc-button-v1>
</textarea>
</p>
<oc-button-v1
size="50"
fit-content="true"
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.data-set="${JSON.stringify({
trV1: JSON.stringify({
ot_label: ["some", "foo"]
}),
"trV1.track": "oc-open,oc-close"
})}"
>
Open sheet with tracking
</oc-button-v1>
<br />
<hr />
<br />
<p>Pass a more complex configuration:</p>
<br />
<p>
<textarea
style="width: 100%; height: 8rem; resize:none; font-family: Monospace; border:0;"
aria-label="example"
disabled
>
<oc-button-v1
size="50"
fit-content="true"
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.data-set="${JSON.stringify({
trV1: JSON.stringify({
ot_label: ["some", "foo"]
}),
trV1OcOpen: JSON.stringify({
ot_label_foo: ["open"]
}),
"trV1OcOpen.action": JSON.stringify({
name: "open",
features: []
}),
trV1OcClose: JSON.stringify({
ot_label_foo: ["close"]
}),
"trV1OcClose.action": JSON.stringify({
name: "close",
features: []
})
})}"
>
Open sheet with complex configuration
</oc-button-v1>
</textarea>
</p>
<oc-button-v1
size="50"
fit-content="true"
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.data-set="${JSON.stringify({
trV1: JSON.stringify({
ot_label: ["some", "foo"]
}),
trV1OcOpen: JSON.stringify({
ot_label_foo: ["open"]
}),
"trV1OcOpen.action": JSON.stringify({
name: "open",
features: []
}),
trV1OcClose: JSON.stringify({
ot_label_foo: ["close"]
}),
"trV1OcClose.action": JSON.stringify({
name: "close",
features: []
})
})}"
>
Open sheet with complex configuration
</oc-button-v1>
<br />
<hr />
<br />
<p>Encode the data set with base64:</p>
<br />
<p>
<textarea
style="width: 100%; height: 8rem; resize:none; font-family: Monospace; border:0;"
aria-label="example"
disabled
>
<oc-button-v1
size="50"
fit-content="true"
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.data-set.base64="${btoa(JSON.stringify({
trV1: JSON.stringify({
ot_label: ["some", "foo"]
}),
trV1OcOpen: JSON.stringify({
ot_label_foo: ["open"]
}),
"trV1OcOpen.action": JSON.stringify({
name: "open",
features: []
}),
trV1OcClose: JSON.stringify({
ot_label_foo: ["close"]
}),
"trV1OcClose.action": JSON.stringify({
name: "close",
features: []
})
}))}"
>
Open sheet with encoded dataset
</oc-button-v1>
</textarea>
</p>
<oc-button-v1
size="50"
fit-content="true"
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.data-set.base64="${btoa(JSON.stringify({
trV1: JSON.stringify({
ot_label: ["some", "foo"]
}),
trV1OcOpen: JSON.stringify({
ot_label_foo: ["open"]
}),
"trV1OcOpen.action": JSON.stringify({
name: "open",
features: []
}),
trV1OcClose: JSON.stringify({
ot_label_foo: ["close"]
}),
"trV1OcClose.action": JSON.stringify({
name: "close",
features: []
})
}))}"
>
Open sheet with encoded dataset
</oc-button-v1>
<br />
<hr />
<br />
<p>Debug tracking events:</p>
<br />
<p>
You can see how tracking events are submitted in the console. Activate debug output to view
tracking events in the developer console:
</p>
<pre><code>
o_global.debug.activate("tracking", "debug");
</code></pre>
<p>
Click on the button and open the developer console. Due to limitations you must do this
every time the story loads.
</p>
<oc-button-v1 size="50" id="activate-tracking-debug">Activate tracking debug</oc-button-v1>
<script>
document.getElementById("activate-tracking-debug").addEventListener("click", () => {
otto.tracking.submitEvent.assignFunction((e) => console.log("tracking event", e));
o_global.debug.activate("tracking", "debug");
});
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: create sheet on click (advanced)",
parameters: {
chromatic: {
hideInChromatic: true
}
},
args: {},
render() {
return html`
<br />
<h2>Pass Additional Data Set</h2>
<br />
<p>
You can pass an additional data set to the created sheet instance, for example to configure
tracking with tr-v1.
</p>
<p>Keep in mind to escape the JSON content like dataContainer for trV1.</p>
<br />
<p>
<textarea
style="width: 100%; height: 8rem; resize:none; font-family: Monospace; border:0;"
aria-label="example"
disabled
>
<oc-button-v1
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.data-set="${JSON.stringify({
trV1: JSON.stringify({
ot_label: ["some", "foo"]
}),
"trV1.track": "oc-open,oc-close"
})}"
>
Open sheet with tracking
</oc-button-v1>
</textarea>
</p>
<oc-button-v1
size="50"
fit-content="true"
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.data-set="${JSON.stringify({
trV1: JSON.stringify({
ot_label: ["some", "foo"]
}),
"trV1.track": "oc-open,oc-close"
})}"
>
Open sheet with tracking
</oc-button-v1>
<br />
<hr />
<br />
<p>Pass a more complex configuration:</p>
<br />
<p>
<textarea
style="width: 100%; height: 8rem; resize:none; font-family: Monospace; border:0;"
aria-label="example"
disabled
>
<oc-button-v1
size="50"
fit-content="true"
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.data-set="${JSON.stringify({
trV1: JSON.stringify({
ot_label: ["some", "foo"]
}),
trV1OcOpen: JSON.stringify({
ot_label_foo: ["open"]
}),
"trV1OcOpen.action": JSON.stringify({
name: "open",
features: []
}),
trV1OcClose: JSON.stringify({
ot_label_foo: ["close"]
}),
"trV1OcClose.action": JSON.stringify({
name: "close",
features: []
})
})}"
>
Open sheet with complex configuration
</oc-button-v1>
</textarea>
</p>
<oc-button-v1
size="50"
fit-content="true"
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.data-set="${JSON.stringify({
trV1: JSON.stringify({
ot_label: ["some", "foo"]
}),
trV1OcOpen: JSON.stringify({
ot_label_foo: ["open"]
}),
"trV1OcOpen.action": JSON.stringify({
name: "open",
features: []
}),
trV1OcClose: JSON.stringify({
ot_label_foo: ["close"]
}),
"trV1OcClose.action": JSON.stringify({
name: "close",
features: []
})
})}"
>
Open sheet with complex configuration
</oc-button-v1>
<br />
<hr />
<br />
<p>Encode the data set with base64:</p>
<br />
<p>
<textarea
style="width: 100%; height: 8rem; resize:none; font-family: Monospace; border:0;"
aria-label="example"
disabled
>
<oc-button-v1
size="50"
fit-content="true"
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.data-set.base64="${btoa(JSON.stringify({
trV1: JSON.stringify({
ot_label: ["some", "foo"]
}),
trV1OcOpen: JSON.stringify({
ot_label_foo: ["open"]
}),
"trV1OcOpen.action": JSON.stringify({
name: "open",
features: []
}),
trV1OcClose: JSON.stringify({
ot_label_foo: ["close"]
}),
"trV1OcClose.action": JSON.stringify({
name: "close",
features: []
})
}))}"
>
Open sheet with encoded dataset
</oc-button-v1>
</textarea>
</p>
<oc-button-v1
size="50"
fit-content="true"
data-oc-sheet-v1-create='{"url":"${externalSheetContentUrl}","fullHeight":true}'
data-oc-sheet-v1-create.data-set.base64="${btoa(JSON.stringify({
trV1: JSON.stringify({
ot_label: ["some", "foo"]
}),
trV1OcOpen: JSON.stringify({
ot_label_foo: ["open"]
}),
"trV1OcOpen.action": JSON.stringify({
name: "open",
features: []
}),
trV1OcClose: JSON.stringify({
ot_label_foo: ["close"]
}),
"trV1OcClose.action": JSON.stringify({
name: "close",
features: []
})
}))}"
>
Open sheet with encoded dataset
</oc-button-v1>
<br />
<hr />
<br />
<p>Debug tracking events:</p>
<br />
<p>
You can see how tracking events are submitted in the console. Activate debug output to view
tracking events in the developer console:
</p>
<pre><code>
o_global.debug.activate("tracking", "debug");
</code></pre>
<p>
Click on the button and open the developer console. Due to limitations you must do this
every time the story loads.
</p>
<oc-button-v1 size="50" id="activate-tracking-debug">Activate tracking debug</oc-button-v1>
<script>
document.getElementById("activate-tracking-debug").addEventListener("click", () => {
otto.tracking.submitEvent.assignFunction((e) => console.log("tracking event", e));
o_global.debug.activate("tracking", "debug");
});
</script>
`;
}
}
Demo: create sheet with trigger element
Story: components-sheet-variations--create-sheet-with-trigger-element · tags: components, sheet, v1, variations
Demo showcasing how to create a sheet with a trigger element that opens the sheet on click.
Args: headline=Sheet with trigger element, url=https://www.otto.de/assets-storybook/assets/sheet-content.rP5OITvp.html
<oc-sheet-trigger-v1 headline="Sheet with trigger element" url="https://www.otto.de/assets-storybook/assets/sheet-content.rP5OITvp.html">
<oc-button-v1>Create sheet with trigger element</oc-button-v1>
</oc-sheet-trigger-v1>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: create sheet with trigger element",
parameters: {
chromatic: {
hideInChromatic: true
}
},
args: {
headline: "Sheet with trigger element",
url: `${externalSheetContentUrl}`
},
// argTypes: hideControlsBadge(Metadata),
render(props) {
return html`
<oc-sheet-trigger-v1 ${spread(props)}>
<oc-button-v1>Create sheet with trigger element</oc-button-v1>
</oc-sheet-trigger-v1>
`;
}
}
Demo: custom spacing title
Story: components-sheet-variations--custom-spacing-title · tags: components, sheet, v1, variations
Demo showcase for custom spaced titles.
Args: headline=Wohnen, defaultSlot=(see snippet)
<!-- Left -->
<oc-sheet-v1 class="spacing-sheet" headline="Wohnen" adaptive-alignment="left" id="left">
${unsafeHTML(headlineSlot)}
<div style="padding-left: 1.5rem;">
<div style="font-weight: 700; font-size: 16px; margin-bottom: 16px;">Möbel</div>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Sofas</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Sessel</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Betten</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Lampen</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Alle Kategorien</li>
</ul>
<div style="display: flex; align-items: center; justify-content: space-between; padding: 16px 0;">
<span>Alle anzeigen</span>
<span style="font-size: 20px;">+</span>
</div>
<div style="font-weight: 700; font-size: 16px; margin-bottom: 16px; margin-top: 24px;">Heimtextilien</div>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Gardinen</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Handtücher</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Teppiche</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Bettwäsche</li>
<li style="padding: 12px 0; color: #666;">Alle Kategorien</li>
</ul>
<div style="font-weight: 700; font-size: 16px; margin-bottom: 16px; margin-top: 24px;">Küche</div>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Küchen</li>
</ul>
</div>
${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<!-- Center -->
<oc-sheet-v1 class="spacing-sheet" headline="Wohnen" adaptive-alignment="center" id="center">
${unsafeHTML(headlineSlot)}
<div style="padding-left: 1.5rem;">
<div style="font-weight: 700; font-size: 16px; margin-bottom: 16px;">Möbel</div>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Sofas</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Sessel</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Betten</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Lampen</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Alle Kategorien</li>
</ul>
<div style="display: flex; align-items: center; justify-content: space-between; padding: 16px 0;">
<span>Alle anzeigen</span>
<span style="font-size: 20px;">+</span>
</div>
<div style="font-weight: 700; font-size: 16px; margin-bottom: 16px; margin-top: 24px;">Heimtextilien</div>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Gardinen</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Handtücher</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Teppiche</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Bettwäsche</li>
<li style="padding: 12px 0; color: #666;">Alle Kategorien</li>
</ul>
<div style="font-weight: 700; font-size: 16px; margin-bottom: 16px; margin-top: 24px;">Küche</div>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Küchen</li>
</ul>
</div>
${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<!-- Right -->
<oc-sheet-v1 class="spacing-sheet" headline="Wohnen" adaptive-alignment="right" id="right">
${unsafeHTML(headlineSlot)}
<div style="padding-left: 1.5rem;">
<div style="font-weight: 700; font-size: 16px; margin-bottom: 16px;">Möbel</div>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Sofas</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Sessel</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Betten</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Lampen</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Alle Kategorien</li>
</ul>
<div style="display: flex; align-items: center; justify-content: space-between; padding: 16px 0;">
<span>Alle anzeigen</span>
<span style="font-size: 20px;">+</span>
</div>
<div style="font-weight: 700; font-size: 16px; margin-bottom: 16px; margin-top: 24px;">Heimtextilien</div>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Gardinen</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Handtücher</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Teppiche</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Bettwäsche</li>
<li style="padding: 12px 0; color: #666;">Alle Kategorien</li>
</ul>
<div style="font-weight: 700; font-size: 16px; margin-bottom: 16px; margin-top: 24px;">Küche</div>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Küchen</li>
</ul>
</div>
${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Custom Spacing Title</h2>
<br />
<p>
By utilizing <code>--title-spacing</code> it is possible to adjust the spacing of the title,
to properly align it with the content
</p>
<div style="margin: 20px 0;">
<label for="spacing-slider" style="display: block; margin-bottom: 8px;">
Title Spacing: <span id="spacing-value">0.5rem</span>
</label>
<input
type="range"
id="spacing-slider"
min="0"
max="5"
value="0.5"
step="0.5"
style="width: 300px;"
/>
</div>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="left" size="50">
Open left aligned sheet
</oc-button-v1>
</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="center" size="50">
Open center aligned sheet
</oc-button-v1>
</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="right" size="50">
Open right aligned sheet
</oc-button-v1>
</p>
<script>
(() => {
const slider = document.getElementById("spacing-slider");
const valueDisplay = document.getElementById("spacing-value");
function updateSpacing(value) {
const spacing = value + "rem";
valueDisplay.textContent = spacing;
// Update CSS variable on all sheet elements
document.querySelectorAll(".spacing-sheet").forEach((sheet) => {
sheet.style.setProperty("--title-spacing", spacing);
});
// Update padding-left on content divs inside sheets
document.querySelectorAll(".spacing-sheet").forEach((sheet) => {
const contentDiv =
sheet.querySelector(
'[slot]:not([slot="headline"]):not([slot="actions"]):not([slot="loading-error"])',
) || sheet.querySelector('div[style*="padding-left"]');
if (contentDiv) {
contentDiv.style.paddingLeft = spacing;
}
});
}
slider.addEventListener("input", (e) => {
updateSpacing(e.target.value);
});
updateSpacing(slider.value);
})();
</script>
Story source (TypeScript, verbatim from Storybook)
{
name: "Demo: custom spacing title",
parameters: {
controls: {
disabled: true
}
},
argTypes: hideControlsBadge(Metadata),
args: {
headline: "Wohnen",
defaultSlot: `
<div style="padding-left: 1.5rem;">
<div style="font-weight: 700; font-size: 16px; margin-bottom: 16px;">Möbel</div>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Sofas</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Sessel</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Betten</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Lampen</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Alle Kategorien</li>
</ul>
<div style="display: flex; align-items: center; justify-content: space-between; padding: 16px 0;">
<span>Alle anzeigen</span>
<span style="font-size: 20px;">+</span>
</div>
<div style="font-weight: 700; font-size: 16px; margin-bottom: 16px; margin-top: 24px;">Heimtextilien</div>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Gardinen</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Handtücher</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Teppiche</li>
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Bettwäsche</li>
<li style="padding: 12px 0; color: #666;">Alle Kategorien</li>
</ul>
<div style="font-weight: 700; font-size: 16px; margin-bottom: 16px; margin-top: 24px;">Küche</div>
<ul style="list-style: none; padding: 0; margin: 0;">
<li style="padding: 12px 0; color: #666; border-bottom: 1px solid #e0e0e0;">Küchen</li>
</ul>
</div>
`
},
render({
defaultSlot,
loadingErrorSlot,
actionsSlot,
headlineSlot,
...props
}) {
return html`
<!-- Left -->
<oc-sheet-v1 class="spacing-sheet" ${spread(props)} adaptive-alignment="left" id="left">
${unsafeHTML(headlineSlot)}${unsafeHTML(defaultSlot)}${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<!-- Center -->
<oc-sheet-v1 class="spacing-sheet" ${spread(props)} adaptive-alignment="center" id="center">
${unsafeHTML(headlineSlot)}${unsafeHTML(defaultSlot)}${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<!-- Right -->
<oc-sheet-v1 class="spacing-sheet" ${spread(props)} adaptive-alignment="right" id="right">
${unsafeHTML(headlineSlot)}${unsafeHTML(defaultSlot)}${unsafeHTML(loadingErrorSlot)}${unsafeHTML(actionsSlot)}
</oc-sheet-v1>
<br />
<h2>Custom Spacing Title</h2>
<br />
<p>
By utilizing <code>--title-spacing</code> it is possible to adjust the spacing of the title,
to properly align it with the content
</p>
<div style="margin: 20px 0;">
<label for="spacing-slider" style="display: block; margin-bottom: 8px;">
Title Spacing: <span id="spacing-value">0.5rem</span>
</label>
<input
type="range"
id="spacing-slider"
min="0"
max="5"
value="0.5"
step="0.5"
style="width: 300px;"
/>
</div>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="left" size="50">
Open left aligned sheet
</oc-button-v1>
</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="center" size="50">
Open center aligned sheet
</oc-button-v1>
</p>
<p>
<oc-button-v1 data-oc-sheet-v1-open data-oc-sheet-v1-open.id="right" size="50">
Open right aligned sheet
</oc-button-v1>
</p>
<script>
(() => {
const slider = document.getElementById("spacing-slider");
const valueDisplay = document.getElementById("spacing-value");
function updateSpacing(value) {
const spacing = value + "rem";
valueDisplay.textContent = spacing;
// Update CSS variable on all sheet elements
document.querySelectorAll(".spacing-sheet").forEach((sheet) => {
sheet.style.setProperty("--title-spacing", spacing);
});
// Update padding-left on content divs inside sheets
document.querySelectorAll(".spacing-sheet").forEach((sheet) => {
const contentDiv =
sheet.querySelector(
'[slot]:not([slot="headline"]):not([slot="actions"]):not([slot="loading-error"])',
) || sheet.querySelector('div[style*="padding-left"]');
if (contentDiv) {
contentDiv.style.paddingLeft = spacing;
}
});
}
slider.addEventListener("input", (e) => {
updateSpacing(e.target.value);
});
updateSpacing(slider.value);
})();
</script>
`;
}
}
Interaction tests (SheetV1.interactions.stories.ts)
Interaction test stories (automated tests, not usage patterns).
Open And Close
Story: components-sheet-interaction-tests--open-and-close · tags: components, sheet, v1, interactions, play-fn
Story source (TypeScript, verbatim from Storybook)
{
render: OpenAndCloseStories.OpenAndClose.render,
play: OpenAndCloseStories.OpenAndClose.play
}
Stacked Instances
Story: components-sheet-interaction-tests--stacked-instances · tags: components, sheet, v1, interactions, play-fn
Story source (TypeScript, verbatim from Storybook)
{
render: StackedInstancesStories.StackedInstances.render,
play() {
// skipped because of flakiness in CI environments
if (StackedInstancesStories.StackedInstances.play) return;
}
}
External Content Load
Story: components-sheet-interaction-tests--external-content-load · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
ContentLoadStories.ExternalContentLoad
Content Loading Error
Story: components-sheet-interaction-tests--content-loading-error · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
ContentLoadStories.ContentLoadingError
Discard Content On Inactive
Story: components-sheet-interaction-tests--discard-content-on-inactive · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
ContentLoadStories.DiscardContentOnInactive
Apply External Props
Story: components-sheet-interaction-tests--apply-external-props · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
ContentLoadStories.ApplyExternalProps
Restore Focus On Close
Story: components-sheet-interaction-tests--restore-focus-on-close · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
InteractionsStories.RestoreFocusOnClose
Open Sheet On Click
Story: components-sheet-interaction-tests--open-sheet-on-click · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
ConvenienceApiStories.OpenSheetOnClick
Create Sheet On Click
Story: components-sheet-interaction-tests--create-sheet-on-click · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
ConvenienceApiStories.CreateSheetOnClick
Disable Close Interaction
Story: components-sheet-interaction-tests--disable-close-interaction · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
DisableCloseInteractionStories.DisableCloseInteraction
Reuse Persisted Props
Story: components-sheet-interaction-tests--reuse-persisted-props · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
BrowserNavigation.ReusePersistedProps
Cancel And Redirect Close Event
Story: components-sheet-interaction-tests--cancel-and-redirect-close-event · tags: components, sheet, v1, interactions, play-fn
Story source (TypeScript, verbatim from Storybook)
{
render: EventCancellation.CancelAndRedirectCloseEvent.render,
play: EventCancellation.CancelAndRedirectCloseEvent.play
}
Presentation Start And End
Story: components-sheet-interaction-tests--presentation-start-and-end · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
SheetEventsStories.PresentationStartAndEnd
Event Payloads
Story: components-sheet-interaction-tests--event-payloads · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
SheetEventsStories.EventPayloads
Content Discard Event
Story: components-sheet-interaction-tests--content-discard-event · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
SheetEventsStories.ContentDiscardEvent
Cancel Before Open
Story: components-sheet-interaction-tests--cancel-before-open · tags: components, sheet, v1, interactions
Story source (TypeScript, verbatim from Storybook)
SheetEventsStories.CancelBeforeOpen
Restore application context (v1)
Source: ./src/components/sheet/v1/Restore-application-context.mdx
Restore application context in reusable sheets
This guide explains how to restore application context in reusable sheets using the built-in event system of oc-sheet-v1.
Skip to:
- Key concepts: State, context, and content
- Understanding sheet reusability and state management
- The state restoration pattern
- Implementation example
Key concepts: State, context, and content
Understanding the distinction between these three concepts is essential for implementing state restoration correctly. Each plays a different role in the restoration flow:
- State: Your application's data that forms the foundation for the sheet's content. State and context can contain identical data, but context is not a reference to state.
- Context: A serializable snapshot of your application state that is persisted through browser history. Context is created from application state and can be used to restore application state when the sheet reopens.
- Content: The DOM elements rendered inside the sheet component. Your application is responsible for rendering content based on the current state.
The relationship between these terms: Your application state determines what content is rendered in the sheet. When you persist data as context, you create a serializable snapshot that enables state restoration when users navigate through browser history.
Understanding sheet reusability and state management
The oc-sheet-v1 component is designed as a reusable component that manages its open and closed states through browser history integration.
The sheet component does not control how DOM content is created or rendered—your application is responsible for providing and managing the sheet's content.
When users navigate using browser history (back/forward buttons), the sheet automatically reopens or closes based on its previous state. However, the sheet cannot restore your application's dynamic content or context because:
- The sheet only manages its own open/closed state in browser history
- The sheet does not store or restore your application's data or rendered content
- Your application must reconstruct the sheet content from the application context when the sheet reopens
When to use state restoration
Use the state restoration pattern when:
- Your sheet displays client-side rendered content that changes based on application state
- Users use browser back/forward buttons to re-open a reusable sheet from a previously stored context and expect to see their previous content inside the re-opened sheet component.
- Your application needs to restore its state from the previously stored context when the sheet automatically reopens
[!NOTE] If your sheet contains only static content that never changes, you don't need to implement state restoration.
The state restoration pattern
The sheet component provides an event system and extraData mechanism to persist and restore your application context through browser history.
Store serializable context data (a snapshot of your application state) using the sheet's API, and the component manages this data through the browser's history state.
Critical requirement: data serialization
All data stored in extraData must be serializable.
The sheet uses the history API and thus stores this data in the browser's history state.
This requires your data to be compatible with the history state.
Implementation example
The following example demonstrates the state restoration pattern using Svelte, but the pattern applies to any framework.
Use sheetEvents to subscribe to sheet lifecycle events and persist your application context (serializable snapshot of your state).
[!NOTE] If the sheet instance is available in the DOM when restoration occurs, you can use both sheet events and DOM events on the sheet component. If the sheet instance is not available in the DOM, use only sheet events.
<script lang="ts">
import { sheetV1 } from "@otto-ec/otto-components/sheet";
const { events: sheetEvents } = sheetV1;
const { data } = $props();
const id = "my-sheet-id";
onMount(() => {
sheetEvents.beforeOpen.subscribe((ev) => {
if (ev.id === id && ev.extraData) {
data = ev.extraData; // Restore application state from persisted context (implementation detail, depends on your application logic)
}
});
sheetEvents.open.subscribe((ev) => {
if (ev.id === id) {
ev.instance.extraData = $state.snapshot(data); // Store context (snapshot of application state) (implementation detail, depends on your application logic)
}
});
});
</script>
<oc-sheet-v1 {id} {open}>
{#if data}
<h1>Content: {data}</h1>
{/if}
</oc-sheet-v1>
How the pattern works
The sheet component provides two key events for state restoration:
sheetEvents.beforeOpen: Fires before the sheet opens.
The event may contain the extraData property from browser history if the sheet was previously opened and had context data supplied by the application.
Use this event to restore your application state before the sheet becomes visible.
This enables seamless navigation when users use the browser's back/forward buttons.
sheetEvents.open: Fires after the beforeOpen event finishes and the opening of the sheet has been initiated.
Use this event to store your current application context to ev.instance.extraData.
The sheet component persists this data to the browser's history state automatically.
Framework-specific considerations:
- The example uses Svelte's
$state.snapshot()to create a non-reactive, serializable copy of the state - This prevents storing reactive proxies in browser history, which would cause serialization errors
- Other frameworks may require similar transformations to ensure data serializability
Multiple sheets: Always check ev.id to ensure your event handlers process only events from the intended sheet when multiple sheets exist on a page.