Sheet
A panel that slides in from an edge.
<sheet.Sheet id="demo-sheet" title="Filters" description="Narrow the result set.">
<:trigger><button.Button variant={button.Outline}>Open sheet</button.Button></:trigger>
Sheet body.
</sheet.Sheet>Installation
Install button and icons 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 sheet
import "github.com/alternayte/gx/registry/icons"
props {
// Id is the id of the dialog element. The trigger opens the sheet by this id.
Id string
// Side sets the edge of the screen that the sheet slides from: Right, Left, Top or Bottom.
Side Side = Right
// Title is the heading of the sheet. 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 element that opens the sheet on a click. A nil value renders no trigger.
Trigger gx.Node = nil
// Footer is the content at the bottom of the sheet, usually the action buttons.
Footer gx.Node = nil
// Open renders the sheet open on the first render.
Open bool = false
// Class adds classes to the dialog element.
Class string = ""
// Children is the content of the sheet.
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 class={gx.Cx("fixed m-0 max-h-none max-w-none flex-col gap-4 border-border bg-background text-foreground shadow-lg 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}>
if p.Title != "" || p.Description != "" {
<div class="flex flex-col gap-1.5 p-4">
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="flex-1 overflow-y-auto px-4 text-sm">{p.Children}</div>
}
if p.Footer != nil {
<div class="mt-auto flex flex-col gap-2 p-4">{p.Footer}</div>
}
<button type="button" data-gx-close class="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<icons.X class="size-4" />
<span class="sr-only">Close</span>
</button>
</dialog>
</span>package sheet
import (
"github.com/alternayte/gx"
"github.com/alternayte/gx/registry/button"
)
var SheetFixtures = gx.Fixtures[SheetProps]{
"Right": {
Id: "demo-sheet",
Title: "Filters",
Description: "Narrow the result set.",
Children: gx.Text("Sheet body."),
Trigger: button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("Open sheet")}),
},
"Bottom": {
Id: "demo-sheet-bottom",
Side: Bottom,
Title: "Filters",
Trigger: button.Button(button.ButtonProps{Variant: button.Outline, Children: gx.Text("Open bottom sheet")}),
},
}package sheet
import "github.com/alternayte/gx"
// Side is the edge a sheet slides from.
type Side string
// The sides of sheet.Sheet.
const (
Right Side = "right"
Left Side = "left"
Top Side = "top"
Bottom Side = "bottom"
)
// A closed sheet rests off its edge; open moves it in, and starting: gives
// the first frame of the slide.
var sideClass = gx.Enum[Side]{
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",
Top: "inset-x-0 top-0 bottom-auto h-auto w-full -translate-y-full border-b open:translate-y-0 starting:open:-translate-y-full",
Bottom: "inset-x-0 bottom-0 top-auto h-auto w-full translate-y-full border-t open:translate-y-0 starting:open:translate-y-full",
}
// sideClass returns the classes of one sheet side; a zero value is Right.
func (p SheetProps) sideClass() string {
if p.Side == "" {
return sideClass[Right]
}
return sideClass[p.Side]
}The theme must define these tokens: --background, --foreground, --border, --muted, --muted-foreground, --ring.
Usage
<sheet.Sheet id="filters" title="Filters" side={sheet.Right}>
<:trigger><button.Button variant={button.Outline}>Filters</button.Button></:trigger>
<p>Filter controls.</p>
</sheet.Sheet>Examples
Right
<sheet.Sheet id="demo-sheet" title="Filters" description="Narrow the result set.">
<:trigger><button.Button variant={button.Outline}>Open sheet</button.Button></:trigger>
Sheet body.
</sheet.Sheet>Bottom
<sheet.Sheet id="demo-sheet-bottom" side={sheet.Bottom} title="Filters">
<:trigger><button.Button variant={button.Outline}>Open bottom sheet</button.Button></:trigger>
</sheet.Sheet>API reference
A tag sets a prop by its name with a lower-case first letter: Class is class.
sheet.Sheet
| Prop | Type | Default | Description |
|---|---|---|---|
Id |
string |
Required | Id is the id of the dialog element. The trigger opens the sheet by this id. |
Side |
Side |
Right |
Side sets the edge of the screen that the sheet slides from: Right, Left, Top or Bottom. |
Title |
string |
"" |
Title is the heading of the sheet. 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 element that opens the sheet on a click. A nil value renders no trigger. |
Footer |
gx.Node |
nil |
Footer is the content at the bottom of the sheet, usually the action buttons. |
Open |
bool |
false |
Open renders the sheet open on the first render. |
Class |
string |
"" |
Class adds classes to the dialog element. |
Children |
gx.Node |
nil |
Children is the content of the sheet. |
Attrs |
gx.Attrs |
nil |
Attrs adds HTML attributes to the dialog element. |
Do and do not
Do
- Use the right side for a detail panel and the bottom for a small action sheet.
- Keep one main task in the sheet.
Don't
- Do not open a sheet over another overlay.
- Do not use a sheet for a destructive confirmation. Use an alert dialog.
Keyboard
| Key | Action |
|---|---|
| Enter, Space | Opens the sheet from the trigger. |
| Tab | Cycles through the controls of the sheet. |
| Escape | Closes the sheet. |