Drawer
A bottom panel for a short task.
<drawer.Drawer id="demo-drawer" title="Share this page" description="Choose a destination.">
<:footer><button.Button>Copy link</button.Button></:footer>
<:trigger><button.Button variant={button.Outline}>Open drawer</button.Button></:trigger>
Drawer body.
</drawer.Drawer>Installation
Run the command in the app module.
gx add drawerThe command also installs button.
The command writes these files.
| File | Path in the app |
|---|---|
Drawer.gx |
ui/drawer/Drawer.gx |
Drawer.fixtures.go |
ui/drawer/Drawer.fixtures.go |
styles.go |
ui/drawer/styles.go |
Install button first.
Copy each file to its path in the app. Change each import of a registry package to the path of that package in the app.
package drawer
props {
// Id is the id of the dialog element.
// The trigger points at it, so it must be unique on the page.
Id string
// Side sets the edge the drawer slides from: Bottom, Top, Right or Left.
Side Side = Bottom
// Title is the heading of the drawer. An empty value renders no heading.
Title string = ""
// Description is the text below the title. An empty value renders no description.
Description string = ""
// Trigger is the control that opens the drawer.
Trigger gx.Node = nil
// Footer is the content of the drawer footer. A nil value renders no footer.
Footer gx.Node = nil
// Open renders the drawer open. The default is closed.
Open bool = false
// Class adds classes to the dialog element.
Class string = ""
// Children is the content of the drawer body.
Children gx.Node = nil
// Attrs adds HTML attributes to the dialog element.
Attrs gx.Attrs = nil
}
<span class="contents">
if p.Trigger != nil {
<span data-gx-open={"#" + p.Id} class="contents">{p.Trigger}</span>
}
<dialog id={p.Id} open={p.Open} data-gx-dismiss data-gx-trap data-side={p.side()} class={gx.Cx("group/drawer-content fixed m-0 max-h-none max-w-none flex-col border-border bg-background text-foreground outline-none open:flex transition-[translate,overlay,display] transition-discrete duration-300 ease-in-out open:duration-500 motion-reduce:transition-none backdrop:bg-black/50 backdrop:opacity-0 backdrop:transition-[opacity,overlay,display] backdrop:transition-discrete backdrop:duration-200 open:backdrop:opacity-100 starting:open:backdrop:opacity-0 motion-reduce:backdrop:transition-none", p.sideClass(), p.Class)} {...p.Attrs}>
<div class="mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full bg-muted group-data-[side=bottom]/drawer-content:block"></div>
if p.Title != "" || p.Description != "" {
<div class="flex flex-col gap-0.5 p-4 group-data-[side=bottom]/drawer-content:text-center group-data-[side=top]/drawer-content:text-center md:gap-1.5 md:text-left">
if p.Title != "" {
<h2 class="font-semibold text-foreground">{p.Title}</h2>
}
if p.Description != "" {
<p class="text-sm text-muted-foreground">{p.Description}</p>
}
</div>
}
if p.Children != nil {
<div class="overflow-y-auto p-4 pt-0 text-sm">{p.Children}</div>
}
if p.Footer != nil {
<div class="mt-auto flex flex-col gap-2 p-4">{p.Footer}</div>
}
</dialog>
</span>package drawer
import (
"github.com/alternayte/gx"
"github.com/alternayte/gx/registry/button"
)
var DrawerFixtures = gx.Fixtures[DrawerProps]{
"Default": {
Id: "demo-drawer",
Title: "Share this page",
Description: "Choose a destination.",
Children: gx.Text("Drawer body."),
Footer: button.Button(button.ButtonProps{Children: gx.Text("Copy link")}),
Trigger: button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("Open drawer")}),
},
}package drawer
import "github.com/alternayte/gx"
// Side is the edge a drawer slides from.
type Side string
// The sides of drawer.Drawer.
const (
Bottom Side = "bottom"
Top Side = "top"
Right Side = "right"
Left Side = "left"
)
// A closed drawer rests off its edge; open moves it in, and starting: gives
// the first frame of the slide.
var sideClass = gx.Enum[Side]{
Bottom: "inset-x-0 bottom-0 top-auto mt-24 h-auto max-h-[80vh] w-full translate-y-full rounded-t-lg border-t open:translate-y-0 starting:open:translate-y-full",
Top: "inset-x-0 top-0 bottom-auto mb-24 h-auto max-h-[80vh] w-full -translate-y-full rounded-b-lg border-b open:translate-y-0 starting:open:-translate-y-full",
Right: "inset-y-0 right-0 left-auto h-full w-3/4 translate-x-full border-l open:translate-x-0 starting:open:translate-x-full sm:max-w-sm",
Left: "inset-y-0 left-0 right-auto h-full w-3/4 -translate-x-full border-r open:translate-x-0 starting:open:-translate-x-full sm:max-w-sm",
}
// side returns the data-side value; a zero value is Bottom.
func (p DrawerProps) side() string {
if p.Side == "" {
return string(Bottom)
}
return string(p.Side)
}
// sideClass returns the classes of one drawer side.
func (p DrawerProps) sideClass() string {
return sideClass[Side(p.side())]
}The theme must define these tokens: --background, --foreground, --border, --muted, --muted-foreground, --ring.
Usage
<drawer.Drawer id="share" title="Share this page">
<:trigger><button.Button variant={button.Outline}>Share</button.Button></:trigger>
<p>Choose a destination.</p>
</drawer.Drawer>Examples
Default
<drawer.Drawer id="demo-drawer" title="Share this page" description="Choose a destination.">
<:footer><button.Button>Copy link</button.Button></:footer>
<:trigger><button.Button variant={button.Outline}>Open drawer</button.Button></:trigger>
Drawer body.
</drawer.Drawer>API reference
A tag sets a prop by its name with a lower-case first letter: Class is class.
drawer.Drawer
| Prop | Type | Default | Description |
|---|---|---|---|
Id |
string |
Required | Id is the id of the dialog element. The trigger points at it, so it must be unique on the page. |
Side |
Side |
Bottom |
Side sets the edge the drawer slides from: Bottom, Top, Right or Left. |
Title |
string |
"" |
Title is the heading of the drawer. An empty value renders no heading. |
Description |
string |
"" |
Description is the text below the title. An empty value renders no description. |
Trigger |
gx.Node |
nil |
Trigger is the control that opens the drawer. |
Footer |
gx.Node |
nil |
Footer is the content of the drawer footer. A nil value renders no footer. |
Open |
bool |
false |
Open renders the drawer open. The default is closed. |
Class |
string |
"" |
Class adds classes to the dialog element. |
Children |
gx.Node |
nil |
Children is the content of the drawer body. |
Attrs |
gx.Attrs |
nil |
Attrs adds HTML attributes to the dialog element. |
Do and do not
Do
- Use a drawer for a short, focused task on a small screen.
- Keep the footer actions in one column.
Don't
- Do not put a long form in a drawer.
- Do not nest a drawer in a sheet.
Keyboard
| Key | Action |
|---|---|
| Enter, Space | Opens the drawer from the trigger. |
| Tab | Cycles through the controls of the drawer. |
| Escape | Closes the drawer. |