From f689bd00c25bbfbc90c9ba4a2885026981db9841 Mon Sep 17 00:00:00 2001 From: Sara Date: Mon, 17 Mar 2025 18:21:45 -0400 Subject: [PATCH] Add Stimulus data-action examples to Dialog docs --- docs/pages/components/drawer.md | 943 +++++++++++++++++--------------- 1 file changed, 489 insertions(+), 454 deletions(-) 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. -:::