> For the complete documentation index, see [llms.txt](https://seanime.gitbook.io/seanime-extensions/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://seanime.gitbook.io/seanime-extensions/plugins/ui/user-interface/tray.md).

# Tray

<figure><img src="/files/C3LBPm1VXD8ebL6llLvy" alt="" width="563"><figcaption><p>Example</p></figcaption></figure>

## Create a tray icon

{% hint style="info" %}
You can only create one tray icon in your plugin
{% endhint %}

```typescript
const tray = ctx.newTray({
    tooltipText: "My plugin",
    iconUrl: "https://seanime.rahim.app/logo_2.png",
    withContent: true,
    isDrawer: false, // Choose whether the tray contents are displayed in a drawer
})
```

## Add a badge

```typescript
tray.updateBadge({ number: 1, intent: "alert" })
// Remove the badge
tray.updateBadge({ number: 0 })
```

* `intent`: "alert" | "info" | "warning" | "success"

## Rendering content

{% hint style="info" %}
`withContent` should be set to 'true'.
{% endhint %}

The `state` is a mechanism to keep track of variable data over time, enabling components to re-render automatically when the data changes.

The `render()` function is used to define how content should be presented in the tray popover. It takes a callback function that returns a tree of components.

Components, like `tray.text` and `tray.button`, are reusable building blocks of the UI, each responsible for rendering a piece of the interface according to the current state.

The tray will be re-rendered anytime there is a state change even if the state isn't in the render function.

<pre class="language-typescript"><code class="lang-typescript">const count = ctx.state(0);

ctx.setInterval(() => {
<strong>    count.set(c => c + 1);
</strong>}, 1000);

tray.render(() => {
    return tray.stack({
        items: [
<strong>            tray.text(`Count: ${count.get()}`),
</strong>            // Show the button when the count reaches 5
<strong>            count.get() >= 5
</strong>                ? tray.button("Click me", { onClick: "button-clicked" })
                : trat.text("Nothing here")
        ],
    })
})
</code></pre>

## Tray events

```typescript
tray.onOpen(() => {
    // User opened the tray content
})

tray.onClose(() => {
    // User closed the tray content
})

tray.onClick(() => {
    // User clicked the tray icon
})

// Open the tray
// This will not work on the first page load or if the plugin is not pinned
tray.open()

// Close the tray
tray.close()

// Force the tray content to update
// Not useful is most cases because the tray updates when states change
tray.update()
```

## Event handlers

You can register functions to specific event triggers like `onClick` using `ctx.registerEventHandler()` in order to define custom behaviors based on user action.

<pre class="language-typescript"><code class="lang-typescript">//..

<strong>ctx.registerEventHandler("reset-counter", () => {
</strong><strong>    count.set(0)
</strong><strong>})
</strong>
tray.render(() => {
    return tray.stack({
        items: [
            tray.text(`Count: ${count.get()}`),
<strong>            tray.button("Reset counter", { onClick: "reset-counter" }),
</strong>        ],
    })
})
</code></pre>

#### Tips

You can register inline event handlers. Make sure the first argument is unique to that element.

```typescript
tray.stack(allItems.map((item) => {
    return tray.flex([
        tray.text(key),
        tray.button({ 
            label: "Open", 
            size: "sm", 
            // It takes a unique ID key as first argument!
            onClick: ctx.eventHandler(item.id, () => {
                // Do something...
            }),
            intent: "gray-subtle"
         }),
    ], { gap: 1, style: { alignItems: "center" } })
}))
```

## Base components

```typescript
tray.div([], { style: {} })
tray.stack([], { style: {} })
tray.flex([], { style: {} })
tray.text(...)
tray.anchor(...)
tray.a(...)
tray.p(...)
tray.span(...)
tray.css(...)
tray.badge(...)
tray.alert(...)
tray.img(...)
```

## Fields/Forms

Field components:

* input
* button
* select
* radioGroup
* checkbox
* switch

Use `ctx.fieldRef` to get and set a field's value synchronously.

<pre class="language-typescript"><code class="lang-typescript"><strong>const textInputRef = ctx.fieldRef&#x3C;string>("Default value")
</strong>
// When the form is submitted
ctx.registerEventHandler("submit-form", () => {
    // We can get the value of the text input
<strong>    console.log(textInputRef.current)
</strong>    
    // We can change the value of the text input
<strong>    textInputRef.setValue("")
</strong>})

tray.render(() => tray.stack([
<strong>    text.input("A text field", { fieldRef: textInputRef }),
</strong>    tray.button("Submit", { onClick: "submit-form" }),
]))
</code></pre>

### Select, RadioGroup

You can create forms easily with `ctx.fieldRef` and the available field components.

```typescript
const selectRef = ctx.fieldRef<string>()
const radioGroupRef = ctx.fieldRef<string>()

tray.render(() => tray.stack([
    tray.select("Label", { 
        placeholder: "Select...",
        options: [
            { label: "One Piece", value: "21" },
            { label: "Sakamoto Days", value: "177709" },
        ],
        fieldRef: selectRef,
    }),
    tray.radioGroup("Label", { 
        options: [
            { label: "One Piece", value: "21" },
            { label: "Sakamoto Days", value: "177709" },
        ],
        fieldRef: radioGroupRef,
    }),
]))
```

### Checkbox, Switch

```typescript
const checkboxRef = ctx.fieldRef<boolean>()
const switchRef = ctx.fieldRef<boolean>()

tray.render(() => tray.stack([
    tray.checkbox("Do something", { 
        fieldRef: checkboxRef
    }),
    tray.switch("Do something else", { 
        fieldRef: switchRef
    }),
]))
```

## Complex components

### CSS

```javascript
tray.div([
    tray.stack([
        tray.css(`
        .red { background-color: red; }
        `),
        // Red square
        tray.tooltip(tray.div([], { className: "square red relative" }), { text: "Test tooltip" }),
    ]),
    tray.stack([
        // No red
        tray.tooltip(tray.div([], { className: "square red relative" }), { text: "Test tooltip" }),
    ]),
])
```

### Tabs, Dropdown, Modal

```javascript
tray.tabs([
    tray.tabsList([
        tray.tabsTrigger(tray.span("Item 1"), { value: "1" }),
        tray.tabsTrigger(tray.span("Item 2"), { value: "2" }),
    ]),
    tray.tabsContent([
        tray.text("Hello, World!"),
        tray.a([
            tray.span("A "),
            tray.span("link", { className: "font-bold" }),
        ], { href: "#" }),
        tray.p([
            tray.span("This is a paragraph."),
        ]),
        tray.modal({
            trigger: tray.button("Open modal"),
            open: modalOpen.get(),
            onOpenChange: ctx.eventHandler("modal-open-change", ({ open }) => {
                console.log(open)
                modalOpen.set(open)
            }),
            items: [
                tray.text("Hello, World!"),
            ],
        }),
        tray.dropdownMenu({
            trigger: tray.button("Open dropdown"),
            items: [
                tray.dropdownMenuItem(tray.span("Item 1")),
                tray.dropdownMenuItem(tray.span("Item 2")),
                tray.dropdownMenuItem(tray.span("Item 3")),
            ],
        }),
    ], { value: "1" }),
    tray.tabsContent([
        tray.text("Item 2 content"),
    ], { value: "2" }),
], { defaultValue: "1" }),
```
