diff --git a/docs/pages/components/drawer.md b/docs/pages/components/drawer.md
index f37180a5..a56a0331 100644
--- a/docs/pages/components/drawer.md
+++ b/docs/pages/components/drawer.md
@@ -12,35 +12,41 @@ layout: component
```html:preview
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- Close
+ Cancel
+ Save
-Open Drawer
+Open drawer
```
```pug:slim
-sl-drawer.drawer-overview label="Drawer"
+sl-drawer.drawer-overview label="Drawer" class="drawer-overview"
| Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- sl-button slot="footer" variant="primary" Close
-sl-button Open Drawer
+ sl-button id="cancel" slot="footer" variant="default" Cancel
+ sl-button id="save" slot="footer" variant="primary" Save
+sl-button Open drawer
javascript:
- const drawer = document.querySelector(.drawer-overview);
+ const drawer = document.querySelector('.drawer-overview');
const openButton = drawer.nextElementSibling;
- const closeButton = drawer.querySelector(sl-button[variant=primary]);
+ const closeButton = drawer.querySelector('sl-button#cancel');
+ const saveButton = drawer.querySelector('sl-button#save');
openButton.addEventListener(click, () => drawer.show());
closeButton.addEventListener(click, () => drawer.hide());
+ saveButton.addEventListener(click, () => drawer.hide());
```
```jsx:react
@@ -60,13 +66,13 @@ const App = () => {
- setOpen(true)}>Open Drawer
+ setOpen(true)}>Open drawer
>
);
};
```
-### Slide in From Start
+
+
+### Using with UI Drawer Component and Stimulus
+
+To open a [UI::Drawer::Component](https://os.teamshares.com/teamshares/lookbook/inspect/ui/drawer/default) (which wraps the `sl-drawer`):
+
+```pug:slim
+ div id="my-drawer-id"
+ = render UI::Drawer::Component.new(title: "My drawer title") do |d|
+ - d.with_trigger
+ sl-button Open my drawer
+```
+
+Add `slot="footer"` to buttons to take advantage of the drawer's built-in placement for footer buttons.
+
+Use the following Stimulus `data-action` attributes to close the drawer and also submit Simple Form/ts_form_for forms in the body of the drawer:
+
+```pug:slim
+ sl-button[
+ slot="footer"
+ type="button"
+ data-action="click->components--ui--drawer#closeDrawer"
+ ] Cancel
+ sl-button[
+ slot="footer"
+ variant="primary"
+ type="button"
+ data-action="click->components--ui--drawer#submitForm"
+ ] Submit
+```
+
+### Opening from Dropdown Menu
+
+When opening a drawer from a `sl-dropdown` menu item triggered by a `sl-button`, wrap the button in a `div` and add `slot="trigger"` to the `div` rather than the button to prevent the drawer from skipping when opening.
+
+```html:preview
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Close
+
+
+
+
Dropdown
+
+ Open drawer
+
+
+
+
+```
+
+```pug:slim
+sl-drawer.drawer-dropdown label="Drawer"
+ | Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ sl-button slot="footer" variant="primary" Close
+
+sl-dropdown
+ div slot="trigger"
+ sl-button caret=true Dropdown
+ sl-menu
+ sl-menu-item.open-link Open drawer
+
+javascript:
+ const drawer = document.querySelector(.drawer-dropdown);
+ const openButton = drawer.querySelector(.open-link);
+ const closeButton = drawer.querySelector(sl-button[variant=primary]);
+
+ openButton.addEventListener(click, () => drawer.show());
+ closeButton.addEventListener(click, () => drawer.hide());
+```
+
+```jsx:react
+import { useState } from 'react';
+import SlButton from '@teamshares/shoelace/dist/react/button';
+import SlDrawer from '@teamshares/shoelace/dist/react/drawer';
+
+const App = () => {
+ const [open, setOpen] = useState(false);
+
+ return (
+ <>
+ setOpen(false)}>
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ setOpen(false)}>
+ Close
+
+
+
+ setOpen(true)}>Open drawer
>
);
};
```
+### Custom Size
+
+Use the `--size` custom property to set the drawer's size. This will be applied to the drawer's width or height depending on its `placement`.
+
+```html:preview
+
+ This drawer is always 50% of the viewport.
+ Close
+
+
+Open drawer
+
+
+```
+
+```pug:slim
+sl-drawer.drawer-custom-size label="Drawer" style="--size: 50vw;"
+ | This drawer is always 50% of the viewport.
+ sl-button slot="footer" variant="primary" Close
+sl-button Open drawer
+
+javascript:
+ const drawer = document.querySelector(.drawer-custom-size);
+ const openButton = drawer.nextElementSibling;
+ const closeButton = drawer.querySelector(sl-button[variant=primary]);
+
+ openButton.addEventListener(click, () => drawer.show());
+ closeButton.addEventListener(click, () => drawer.hide());
+```
+
+{% raw %}
+
+```jsx:react
+import { useState } from 'react';
+import SlButton from '@teamshares/shoelace/dist/react/button';
+import SlDrawer from '@teamshares/shoelace/dist/react/drawer';
+
+const App = () => {
+ const [open, setOpen] = useState(false);
+
+ return (
+ <>
+ setOpen(false)} style={{ '--size': '50vw' }}>
+ This drawer is always 50% of the viewport.
+ setOpen(false)}>
+ Close
+
+
+
+ setOpen(true)}>Open drawer
+ >
+ );
+};
+```
+
+{% endraw %}
+
+### Scrolling
+
+By design, a drawer's height will never exceed 100% of its container. As such, drawers will not scroll with the page to ensure the header and footer are always accessible to the user.
+
+```html:preview
+
+
+
Scroll down and give it a try! 👇
+
+ Close
+
+
+Open drawer
+
+
+```
+
+```pug:slim
+sl-drawer.drawer-scrolling label="Drawer"
+ div style="height: 150vh; border: dashed 2px var(--sl-color-neutral-200); padding: 0 1rem;"
+ p Scroll down and give it a try! 👇
+ sl-button slot="footer" variant="primary" Close
+sl-button Open drawer
+
+javascript:
+ const drawer = document.querySelector(.drawer-scrolling);
+ const openButton = drawer.nextElementSibling;
+ const closeButton = drawer.querySelector(sl-button[variant=primary]);
+
+ openButton.addEventListener(click, () => drawer.show());
+ closeButton.addEventListener(click, () => drawer.hide());
+```
+
+{% raw %}
+
+```jsx:react
+import { useState } from 'react';
+import SlButton from '@teamshares/shoelace/dist/react/button';
+import SlDrawer from '@teamshares/shoelace/dist/react/drawer';
+
+const App = () => {
+ const [open, setOpen] = useState(false);
+
+ return (
+ <>
+ setOpen(false)}>
+
+
Scroll down and give it a try! 👇
+
+ setOpen(false)}>
+ Close
+
+
+
+ setOpen(true)}>Open drawer
+ >
+ );
+};
+```
+
+{% endraw %}
+
+### Header Actions
+
+The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
+
+```html:preview
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Close
+
+
+Open drawer
+
+
+```
+
+```pug:slim
+sl-drawer.drawer-header-actions label="Drawer"
+ sl-icon-button.new-window slot="header-actions" name="arrow-top-right-on-square"
+ | Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ sl-button slot="footer" variant="primary" Close
+sl-button Open drawer
+
+javascript:
+ const drawer = document.querySelector(.drawer-header-actions);
+ const openButton = drawer.nextElementSibling;
+ const closeButton = drawer.querySelector(sl-button[variant=primary]);
+ const newWindowButton = drawer.querySelector(.new-window);
+
+ openButton.addEventListener(click, () => drawer.show());
+ closeButton.addEventListener(click, () => drawer.hide());
+ newWindowButton.addEventListener(click, () => window.open(location.href));
+```
+
+```jsx:react
+import { useState } from 'react';
+import SlButton from '@teamshares/shoelace/dist/react/button';
+import SlDrawer from '@teamshares/shoelace/dist/react/drawer';
+import SlIconButton from '@teamshares/shoelace/dist/react/icon-button';
+
+const App = () => {
+ const [open, setOpen] = useState(false);
+
+ return (
+ <>
+ setOpen(false)}>
+ window.open(location.href)}
+ />
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ setOpen(false)}>
+ Close
+
+
+
+ setOpen(true)}>Open drawer
+ >
+ );
+};
+```
+
+### Preventing the Drawer from Closing
+
+By default, drawers will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
+
+To keep the drawer open in such cases, you can cancel the `sl-request-close` event. When canceled, the drawer will remain open and pulse briefly to draw the user's attention to it.
+
+You can use `event.detail.source` to determine what triggered the request to close. This example prevents the drawer from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
+
+```html:preview
+
+ This drawer will not close when you click on the overlay.
+ Close
+
+
+Open drawer
+
+
+```
+
+```pug:slim
+sl-drawer.drawer-deny-close label="Drawer"
+ | This drawer will not close when you click on the overlay.
+ sl-button slot="footer" variant="primary" Close
+sl-button Open drawer
+
+javascript:
+ const drawer = document.querySelector(.drawer-deny-close);
+ const openButton = drawer.nextElementSibling;
+ const closeButton = drawer.querySelector(sl-button[variant=primary]);
+
+ openButton.addEventListener(click, () => drawer.show());
+ closeButton.addEventListener(click, () => drawer.hide());
+
+ // Prevent the drawer from closing when the user clicks on the overlay
+ drawer.addEventListener(sl-request-close, event => {
+ if (event.detail.source === overlay) {
+ event.preventDefault();
+ }
+ });
+```
+
+```jsx:react
+import { useState } from 'react';
+import SlButton from '@teamshares/shoelace/dist/react/button';
+import SlDrawer from '@teamshares/shoelace/dist/react/drawer';
+
+const App = () => {
+ const [open, setOpen] = useState(false);
+
+ // Prevent the drawer from closing when the user clicks on the overlay
+ function handleRequestClose(event) {
+ if (event.detail.source === 'overlay') {
+ event.preventDefault();
+ }
+ }
+
+ return (
+ <>
+ setOpen(false)}>
+ This drawer will not close when you click on the overlay.
+ setOpen(false)}>
+ Save & Close
+
+
+
+ setOpen(true)}>Open drawer
+ >
+ );
+};
+```
+
+### Customizing Initial Focus
+
+By default, the drawer's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the drawer. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
+
+```html:preview
+
+
+ Close
+
+
+Open drawer
+
+
+```
+
+```pug:slim
+sl-drawer.drawer-focus label="Drawer"
+ sl-input autofocus=true placeholder="I will have focus when the drawer is opened"
+ sl-button slot="footer" variant="primary" Close
+sl-button Open drawer
+
+javascript:
+ const drawer = document.querySelector(.drawer-focus);
+ const input = drawer.querySelector(sl-input);
+ const openButton = drawer.nextElementSibling;
+ const closeButton = drawer.querySelector(sl-button[variant=primary]);
+
+ openButton.addEventListener(click, () => drawer.show());
+ closeButton.addEventListener(click, () => drawer.hide());
+```
+
+```jsx:react
+import { useState } from 'react';
+import SlButton from '@teamshares/shoelace/dist/react/button';
+import SlDrawer from '@teamshares/shoelace/dist/react/drawer';
+import SlInput from '@teamshares/shoelace/dist/react/input';
+
+const App = () => {
+ const [open, setOpen] = useState(false);
+
+ return (
+ <>
+ setOpen(false)}>
+
+ setOpen(false)}>
+ Close
+
+
+
+ setOpen(true)}>Open drawer
+ >
+ );
+};
+```
+
+:::tip
+You can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.
+:::
+
### Contained to an Element
By default, drawers slide out of their [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport. To make a drawer slide out of a parent element, add the `contained` attribute to the drawer and apply `position: relative` to its parent.
@@ -333,444 +802,10 @@ const App = () => {
- setOpen(true)}>Open Drawer
+ setOpen(true)}>Open drawer
>
);
};
```
{% endraw %}
-
-### Opening from Dropdown Menu
-
-When opening a drawer from a `sl-dropdown` menu item triggered by a `sl-button`, wrap the button in a `div` and add `slot="trigger"` to the `div` rather than the button to prevent the drawer from skipping when opening.
-
-```html:preview
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- Close
-
-
-
-
Dropdown
-
- Open drawer
-
-
-
-
-```
-
-```pug:slim
-sl-drawer.drawer-overview label="Drawer"
- | Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- sl-button slot="footer" variant="primary" Close
-
-sl-dropdown
- div slot="trigger"
- sl-button caret=true Dropdown
- sl-menu
- sl-menu-item.open-link Open drawer
-
-javascript:
- const drawer = document.querySelector(.drawer-overview);
- const openButton = drawer.querySelector(.open-link);
- const closeButton = drawer.querySelector(sl-button[variant=primary]);
-
- openButton.addEventListener(click, () => drawer.show());
- closeButton.addEventListener(click, () => drawer.hide());
-```
-
-```jsx:react
-import { useState } from 'react';
-import SlButton from '@teamshares/shoelace/dist/react/button';
-import SlDrawer from '@teamshares/shoelace/dist/react/drawer';
-
-const App = () => {
- const [open, setOpen] = useState(false);
-
- return (
- <>
- setOpen(false)}>
- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- setOpen(false)}>
- Close
-
-
-
- setOpen(true)}>Open Drawer
- >
- );
-};
-```
-
-### Custom Size
-
-Use the `--size` custom property to set the drawer's size. This will be applied to the drawer's width or height depending on its `placement`.
-
-```html:preview
-
- This drawer is always 50% of the viewport.
- Close
-
-
-Open Drawer
-
-
-```
-
-```pug:slim
-sl-drawer.drawer-custom-size label="Drawer" style="--size: 50vw;"
- | This drawer is always 50% of the viewport.
- sl-button slot="footer" variant="primary" Close
-sl-button Open Drawer
-
-javascript:
- const drawer = document.querySelector(.drawer-custom-size);
- const openButton = drawer.nextElementSibling;
- const closeButton = drawer.querySelector(sl-button[variant=primary]);
-
- openButton.addEventListener(click, () => drawer.show());
- closeButton.addEventListener(click, () => drawer.hide());
-```
-
-{% raw %}
-
-```jsx:react
-import { useState } from 'react';
-import SlButton from '@teamshares/shoelace/dist/react/button';
-import SlDrawer from '@teamshares/shoelace/dist/react/drawer';
-
-const App = () => {
- const [open, setOpen] = useState(false);
-
- return (
- <>
- setOpen(false)} style={{ '--size': '50vw' }}>
- This drawer is always 50% of the viewport.
- setOpen(false)}>
- Close
-
-
-
- setOpen(true)}>Open Drawer
- >
- );
-};
-```
-
-{% endraw %}
-
-### Scrolling
-
-By design, a drawer's height will never exceed 100% of its container. As such, drawers will not scroll with the page to ensure the header and footer are always accessible to the user.
-
-```html:preview
-
-
-
Scroll down and give it a try! 👇
-
- Close
-
-
-Open Drawer
-
-
-```
-
-```pug:slim
-sl-drawer.drawer-scrolling label="Drawer"
- div style="height: 150vh; border: dashed 2px var(--sl-color-neutral-200); padding: 0 1rem;"
- p Scroll down and give it a try! 👇
- sl-button slot="footer" variant="primary" Close
-sl-button Open Drawer
-
-javascript:
- const drawer = document.querySelector(.drawer-scrolling);
- const openButton = drawer.nextElementSibling;
- const closeButton = drawer.querySelector(sl-button[variant=primary]);
-
- openButton.addEventListener(click, () => drawer.show());
- closeButton.addEventListener(click, () => drawer.hide());
-```
-
-{% raw %}
-
-```jsx:react
-import { useState } from 'react';
-import SlButton from '@teamshares/shoelace/dist/react/button';
-import SlDrawer from '@teamshares/shoelace/dist/react/drawer';
-
-const App = () => {
- const [open, setOpen] = useState(false);
-
- return (
- <>
- setOpen(false)}>
-
-
Scroll down and give it a try! 👇
-
- setOpen(false)}>
- Close
-
-
-
- setOpen(true)}>Open Drawer
- >
- );
-};
-```
-
-{% endraw %}
-
-### Header Actions
-
-The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed.
-
-```html:preview
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- Close
-
-
-Open Drawer
-
-
-```
-
-```pug:slim
-sl-drawer.drawer-header-actions label="Drawer"
- sl-icon-button.new-window slot="header-actions" name="arrow-top-right-on-square"
- | Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- sl-button slot="footer" variant="primary" Close
-sl-button Open Drawer
-
-javascript:
- const drawer = document.querySelector(.drawer-header-actions);
- const openButton = drawer.nextElementSibling;
- const closeButton = drawer.querySelector(sl-button[variant=primary]);
- const newWindowButton = drawer.querySelector(.new-window);
-
- openButton.addEventListener(click, () => drawer.show());
- closeButton.addEventListener(click, () => drawer.hide());
- newWindowButton.addEventListener(click, () => window.open(location.href));
-```
-
-```jsx:react
-import { useState } from 'react';
-import SlButton from '@teamshares/shoelace/dist/react/button';
-import SlDrawer from '@teamshares/shoelace/dist/react/drawer';
-import SlIconButton from '@teamshares/shoelace/dist/react/icon-button';
-
-const App = () => {
- const [open, setOpen] = useState(false);
-
- return (
- <>
- setOpen(false)}>
- window.open(location.href)}
- />
- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- setOpen(false)}>
- Close
-
-
-
- setOpen(true)}>Open Drawer
- >
- );
-};
-```
-
-### Preventing the Drawer from Closing
-
-By default, drawers will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur.
-
-To keep the drawer open in such cases, you can cancel the `sl-request-close` event. When canceled, the drawer will remain open and pulse briefly to draw the user's attention to it.
-
-You can use `event.detail.source` to determine what triggered the request to close. This example prevents the drawer from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it.
-
-```html:preview
-
- This drawer will not close when you click on the overlay.
- Close
-
-
-Open Drawer
-
-
-```
-
-```pug:slim
-sl-drawer.drawer-deny-close label="Drawer"
- | This drawer will not close when you click on the overlay.
- sl-button slot="footer" variant="primary" Close
-sl-button Open Drawer
-
-javascript:
- const drawer = document.querySelector(.drawer-deny-close);
- const openButton = drawer.nextElementSibling;
- const closeButton = drawer.querySelector(sl-button[variant=primary]);
-
- openButton.addEventListener(click, () => drawer.show());
- closeButton.addEventListener(click, () => drawer.hide());
-
- // Prevent the drawer from closing when the user clicks on the overlay
- drawer.addEventListener(sl-request-close, event => {
- if (event.detail.source === overlay) {
- event.preventDefault();
- }
- });
-```
-
-```jsx:react
-import { useState } from 'react';
-import SlButton from '@teamshares/shoelace/dist/react/button';
-import SlDrawer from '@teamshares/shoelace/dist/react/drawer';
-
-const App = () => {
- const [open, setOpen] = useState(false);
-
- // Prevent the drawer from closing when the user clicks on the overlay
- function handleRequestClose(event) {
- if (event.detail.source === 'overlay') {
- event.preventDefault();
- }
- }
-
- return (
- <>
- setOpen(false)}>
- This drawer will not close when you click on the overlay.
- setOpen(false)}>
- Save & Close
-
-
-
- setOpen(true)}>Open Drawer
- >
- );
-};
-```
-
-### Customizing Initial Focus
-
-By default, the drawer's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the drawer. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
-
-```html:preview
-
-
- Close
-
-
-Open Drawer
-
-
-```
-
-```pug:slim
-sl-drawer.drawer-focus label="Drawer"
- sl-input autofocus=true placeholder="I will have focus when the drawer is opened"
- sl-button slot="footer" variant="primary" Close
-sl-button Open Drawer
-
-javascript:
- const drawer = document.querySelector(.drawer-focus);
- const input = drawer.querySelector(sl-input);
- const openButton = drawer.nextElementSibling;
- const closeButton = drawer.querySelector(sl-button[variant=primary]);
-
- openButton.addEventListener(click, () => drawer.show());
- closeButton.addEventListener(click, () => drawer.hide());
-```
-
-```jsx:react
-import { useState } from 'react';
-import SlButton from '@teamshares/shoelace/dist/react/button';
-import SlDrawer from '@teamshares/shoelace/dist/react/drawer';
-import SlInput from '@teamshares/shoelace/dist/react/input';
-
-const App = () => {
- const [open, setOpen] = useState(false);
-
- return (
- <>
- setOpen(false)}>
-
- setOpen(false)}>
- Close
-
-
-
- setOpen(true)}>Open Drawer
- >
- );
-};
-```
-
-:::tip
-You can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.
-:::