mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'main' into add-containers
[skip ci]
This commit is contained in:
2
docs/api/tabs.md
Normal file
2
docs/api/tabs.md
Normal file
@@ -0,0 +1,2 @@
|
||||
::: textual.widgets.Tab
|
||||
::: textual.widgets.Tabs
|
||||
142
docs/blog/images/loading_indicator.svg
Normal file
142
docs/blog/images/loading_indicator.svg
Normal file
@@ -0,0 +1,142 @@
|
||||
<svg class="rich-terminal" viewBox="0 0 897 562.4" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Generated with Rich https://www.textualize.io -->
|
||||
<style>
|
||||
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
src: local("FiraCode-Regular"),
|
||||
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
|
||||
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
src: local("FiraCode-Bold"),
|
||||
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
|
||||
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
|
||||
font-style: bold;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.terminal-2562693105-matrix {
|
||||
font-family: Fira Code, monospace;
|
||||
font-size: 20px;
|
||||
line-height: 24.4px;
|
||||
font-variant-east-asian: full-width;
|
||||
}
|
||||
|
||||
.terminal-2562693105-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
font-family: arial;
|
||||
}
|
||||
|
||||
.terminal-2562693105-r1 { fill: #c5c8c6 }
|
||||
.terminal-2562693105-r2 { fill: #0d5083 }
|
||||
.terminal-2562693105-r3 { fill: #0763aa }
|
||||
.terminal-2562693105-r4 { fill: #0579d5 }
|
||||
.terminal-2562693105-r5 { fill: #348ceb }
|
||||
.terminal-2562693105-r6 { fill: #1a2832 }
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<clipPath id="terminal-2562693105-clip-terminal">
|
||||
<rect x="0" y="0" width="877.4" height="511.4" />
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-0">
|
||||
<rect x="0" y="1.5" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-1">
|
||||
<rect x="0" y="25.9" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-2">
|
||||
<rect x="0" y="50.3" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-3">
|
||||
<rect x="0" y="74.7" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-4">
|
||||
<rect x="0" y="99.1" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-5">
|
||||
<rect x="0" y="123.5" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-6">
|
||||
<rect x="0" y="147.9" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-7">
|
||||
<rect x="0" y="172.3" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-8">
|
||||
<rect x="0" y="196.7" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-9">
|
||||
<rect x="0" y="221.1" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-10">
|
||||
<rect x="0" y="245.5" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-11">
|
||||
<rect x="0" y="269.9" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-12">
|
||||
<rect x="0" y="294.3" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-13">
|
||||
<rect x="0" y="318.7" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-14">
|
||||
<rect x="0" y="343.1" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-15">
|
||||
<rect x="0" y="367.5" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-16">
|
||||
<rect x="0" y="391.9" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-17">
|
||||
<rect x="0" y="416.3" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-18">
|
||||
<rect x="0" y="440.7" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
<clipPath id="terminal-2562693105-line-19">
|
||||
<rect x="0" y="465.1" width="878.4" height="24.65"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="895" height="560.4" rx="8"/><text class="terminal-2562693105-title" fill="#c5c8c6" text-anchor="middle" x="447" y="27">LoadingApp</text>
|
||||
<g transform="translate(26,22)">
|
||||
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
|
||||
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
|
||||
<circle cx="44" cy="0" r="7" fill="#28c840"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(9, 41)" clip-path="url(#terminal-2562693105-clip-terminal)">
|
||||
<rect fill="#1e1e1e" x="0" y="1.5" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="25.9" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="50.3" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="74.7" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="99.1" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="123.5" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="147.9" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="172.3" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="196.7" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="221.1" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="245.5" width="378.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="378.2" y="245.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="402.6" y="245.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="427" y="245.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="451.4" y="245.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="475.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="488" y="245.5" width="390.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="269.9" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="294.3" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="318.7" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="343.1" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="367.5" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="391.9" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="416.3" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="440.7" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="465.1" width="878.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#1e1e1e" x="0" y="489.5" width="878.4" height="24.65" shape-rendering="crispEdges"/>
|
||||
<g class="terminal-2562693105-matrix">
|
||||
<text class="terminal-2562693105-r1" x="878.4" y="20" textLength="12.2" clip-path="url(#terminal-2562693105-line-0)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="44.4" textLength="12.2" clip-path="url(#terminal-2562693105-line-1)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="68.8" textLength="12.2" clip-path="url(#terminal-2562693105-line-2)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="93.2" textLength="12.2" clip-path="url(#terminal-2562693105-line-3)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="117.6" textLength="12.2" clip-path="url(#terminal-2562693105-line-4)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="142" textLength="12.2" clip-path="url(#terminal-2562693105-line-5)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="166.4" textLength="12.2" clip-path="url(#terminal-2562693105-line-6)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="190.8" textLength="12.2" clip-path="url(#terminal-2562693105-line-7)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="215.2" textLength="12.2" clip-path="url(#terminal-2562693105-line-8)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="239.6" textLength="12.2" clip-path="url(#terminal-2562693105-line-9)">
|
||||
</text><text class="terminal-2562693105-r2" x="378.2" y="264" textLength="24.4" clip-path="url(#terminal-2562693105-line-10)">● </text><text class="terminal-2562693105-r3" x="402.6" y="264" textLength="24.4" clip-path="url(#terminal-2562693105-line-10)">● </text><text class="terminal-2562693105-r4" x="427" y="264" textLength="24.4" clip-path="url(#terminal-2562693105-line-10)">● </text><text class="terminal-2562693105-r5" x="451.4" y="264" textLength="24.4" clip-path="url(#terminal-2562693105-line-10)">● </text><text class="terminal-2562693105-r6" x="475.8" y="264" textLength="12.2" clip-path="url(#terminal-2562693105-line-10)">●</text><text class="terminal-2562693105-r1" x="878.4" y="264" textLength="12.2" clip-path="url(#terminal-2562693105-line-10)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="288.4" textLength="12.2" clip-path="url(#terminal-2562693105-line-11)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="312.8" textLength="12.2" clip-path="url(#terminal-2562693105-line-12)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="337.2" textLength="12.2" clip-path="url(#terminal-2562693105-line-13)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="361.6" textLength="12.2" clip-path="url(#terminal-2562693105-line-14)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="386" textLength="12.2" clip-path="url(#terminal-2562693105-line-15)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="410.4" textLength="12.2" clip-path="url(#terminal-2562693105-line-16)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="434.8" textLength="12.2" clip-path="url(#terminal-2562693105-line-17)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="459.2" textLength="12.2" clip-path="url(#terminal-2562693105-line-18)">
|
||||
</text><text class="terminal-2562693105-r1" x="878.4" y="483.6" textLength="12.2" clip-path="url(#terminal-2562693105-line-19)">
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
183
docs/blog/images/tabs_widget.svg
Normal file
183
docs/blog/images/tabs_widget.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 34 KiB |
39
docs/blog/posts/release0-15-0.md
Normal file
39
docs/blog/posts/release0-15-0.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
draft: false
|
||||
date: 2023-03-13
|
||||
categories:
|
||||
- Release
|
||||
title: "Textual 0.15.0 adds a tabs widget"
|
||||
authors:
|
||||
- willmcgugan
|
||||
---
|
||||
|
||||
# Textual 0.15.0 adds a tabs widget
|
||||
|
||||
We've just pushed Textual 0.15.0, only 4 days after the previous version. That's a little faster than our typical release cadence of 1 to 2 weeks.
|
||||
|
||||
What's new in this release?
|
||||
|
||||
<!-- more -->
|
||||
|
||||
The highlight of this release is a new [Tabs](./widgets/../../../widgets/tabs.md) widget to display tabs which can be navigated much like tabs in a browser. Here's a screenshot:
|
||||
|
||||
<div>
|
||||
--8<-- "docs/blog/images/tabs_widget.svg"
|
||||
</div>
|
||||
|
||||
In a future release, this will be combined with the [ContentSwitcher](../../widgets/content_switcher.md) widget to create a traditional tabbed dialog. Although Tabs is still useful as a standalone widgets.
|
||||
|
||||
!!! tip
|
||||
|
||||
I like to tweet progress with widgets on Twitter. See the [#textualtabs](https://twitter.com/search?q=%23textualtabs&src=typeahead_click) hashtag which documents progress on this widget.
|
||||
|
||||
Also in this release is a new [LoadingIndicator](./../../widgets/loading_indicator.md) widget to display a simple animation while waiting for data. Here's a screenshot:
|
||||
|
||||
<div>
|
||||
--8<-- "docs/blog/images/loading_indicator.svg"
|
||||
</div>
|
||||
|
||||
As always, see the [release notes](https://github.com/Textualize/textual/releases/tag/v0.15.0) for the full details on this update.
|
||||
|
||||
If you want to talk about these widgets, or anything else Textual related, join us on our [Discord server](https://discord.gg/Enf6Z3qhVr).
|
||||
@@ -1,5 +1,5 @@
|
||||
from textual.app import App
|
||||
from textual import events
|
||||
from textual.app import App
|
||||
|
||||
|
||||
class ActionsApp(App):
|
||||
@@ -8,7 +8,7 @@ class ActionsApp(App):
|
||||
|
||||
async def on_key(self, event: events.Key) -> None:
|
||||
if event.key == "r":
|
||||
await self.action("set_background('red')")
|
||||
await self.run_action("set_background('red')")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
82
docs/examples/widgets/tabs.py
Normal file
82
docs/examples/widgets/tabs.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Footer, Label, Tabs
|
||||
|
||||
NAMES = [
|
||||
"Paul Atreidies",
|
||||
"Duke Leto Atreides",
|
||||
"Lady Jessica",
|
||||
"Gurney Halleck",
|
||||
"Baron Vladimir Harkonnen",
|
||||
"Glossu Rabban",
|
||||
"Chani",
|
||||
"Silgar",
|
||||
]
|
||||
|
||||
|
||||
class TabsApp(App):
|
||||
"""Demonstrates the Tabs widget."""
|
||||
|
||||
CSS = """
|
||||
Tabs {
|
||||
dock: top;
|
||||
}
|
||||
Screen {
|
||||
align: center middle;
|
||||
}
|
||||
Label {
|
||||
margin:1 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: $panel;
|
||||
border: tall $primary;
|
||||
content-align: center middle;
|
||||
}
|
||||
"""
|
||||
|
||||
BINDINGS = [
|
||||
("a", "add", "Add tab"),
|
||||
("r", "remove", "Remove active tab"),
|
||||
("c", "clear", "Clear tabs"),
|
||||
]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Tabs(NAMES[0])
|
||||
yield Label()
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""Focus the tabs when the app starts."""
|
||||
self.query_one(Tabs).focus()
|
||||
|
||||
def on_tabs_tab_activated(self, event: Tabs.TabActivated) -> None:
|
||||
"""Handle TabActivated message sent by Tabs."""
|
||||
label = self.query_one(Label)
|
||||
if event.tab is None:
|
||||
# When the tabs are cleared, event.tab will be None
|
||||
label.visible = False
|
||||
else:
|
||||
label.visible = True
|
||||
label.update(event.tab.label)
|
||||
|
||||
def action_add(self) -> None:
|
||||
"""Add a new tab."""
|
||||
tabs = self.query_one(Tabs)
|
||||
# Cycle the names
|
||||
NAMES[:] = [*NAMES[1:], NAMES[0]]
|
||||
tabs.add_tab(NAMES[0])
|
||||
|
||||
def action_remove(self) -> None:
|
||||
"""Remove active tab."""
|
||||
tabs = self.query_one(Tabs)
|
||||
active_tab = tabs.active_tab
|
||||
if active_tab is not None:
|
||||
tabs.remove_tab(active_tab.id)
|
||||
|
||||
def action_clear(self) -> None:
|
||||
"""Clear the tabs."""
|
||||
self.query_one(Tabs).clear()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = TabsApp()
|
||||
app.run()
|
||||
@@ -20,13 +20,13 @@ The `action_set_background` method is an action which sets the background of the
|
||||
|
||||
Although it is possible (and occasionally useful) to call action methods in this way, they are intended to be parsed from an _action string_. For instance, the string `"set_background('red')"` is an action string which would call `self.action_set_background('red')`.
|
||||
|
||||
The following example replaces the immediate call with a call to [action()][textual.widgets.Widget.action] which parses an action string and dispatches it to the appropriate method.
|
||||
The following example replaces the immediate call with a call to [run_action()][textual.widgets.Widget.run_action] which parses an action string and dispatches it to the appropriate method.
|
||||
|
||||
```python title="actions02.py" hl_lines="9-11"
|
||||
--8<-- "docs/examples/guide/actions/actions02.py"
|
||||
```
|
||||
|
||||
Note that the `action()` method is a coroutine so `on_key` needs to be prefixed with the `async` keyword.
|
||||
Note that the `run_action()` method is a coroutine so `on_key` needs to be prefixed with the `async` keyword.
|
||||
|
||||
You will not typically need this in a real app as Textual will run actions in links or key bindings. Before we discuss these, let's have a closer look at the syntax for action strings.
|
||||
|
||||
@@ -36,7 +36,7 @@ Action strings have a simple syntax, which for the most part replicates Python's
|
||||
|
||||
!!! important
|
||||
|
||||
As much as they *look* like Python code, Textual does **not** call Python's `eval` function or similar to compile action strings.
|
||||
As much as they *look* like Python code, Textual does **not** call Python's `eval` function to compile action strings.
|
||||
|
||||
Action strings have the following format:
|
||||
|
||||
@@ -50,7 +50,7 @@ Action strings have the following format:
|
||||
|
||||
### Parameters
|
||||
|
||||
If the action string contains parameters, these must be valid Python literals. Which means you can include numbers, strings, dicts, lists etc. but you can't include variables or references to any other python symbol.
|
||||
If the action string contains parameters, these must be valid Python literals. Which means you can include numbers, strings, dicts, lists etc. but you can't include variables or references to any other Python symbols.
|
||||
|
||||
Consequently `"set_background('blue')"` is a valid action string, but `"set_background(new_color)"` is not — because `new_color` is a variable and not a literal.
|
||||
|
||||
|
||||
@@ -185,6 +185,16 @@ A on / off control, inspired by toggle buttons.
|
||||
```{.textual path="docs/examples/widgets/switch.py"}
|
||||
```
|
||||
|
||||
## Tabs
|
||||
|
||||
A row of tabs you can select with the mouse or navigate with keys.
|
||||
|
||||
[Tabs reference](./widgets/tabs.md){ .md-button .md-button--primary }
|
||||
|
||||
```{.textual path="docs/examples/widgets/tabs.py" press="a,a,a,a,right,right"}
|
||||
```
|
||||
|
||||
|
||||
## TextLog
|
||||
|
||||
Display and update text in a scrolling panel.
|
||||
|
||||
@@ -29,7 +29,7 @@ The example below shows check boxes in various states.
|
||||
## Reactive Attributes
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|---------|--------|---------|----------------------------|
|
||||
| ------- | ------ | ------- | -------------------------- |
|
||||
| `value` | `bool` | `False` | The value of the checkbox. |
|
||||
|
||||
## Bindings
|
||||
|
||||
@@ -38,7 +38,7 @@ The example below shows how you might create a simple form using two `Input` wid
|
||||
|
||||
## Bindings
|
||||
|
||||
The input widget defines directly the following bindings:
|
||||
The Input widget defines the following bindings:
|
||||
|
||||
::: textual.widgets.Input.BINDINGS
|
||||
options:
|
||||
|
||||
75
docs/widgets/tabs.md
Normal file
75
docs/widgets/tabs.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Tabs
|
||||
|
||||
Displays a number of tab headers which may be activated with a click or navigated with cursor keys.
|
||||
|
||||
- [x] Focusable
|
||||
- [ ] Container
|
||||
|
||||
Construct a `Tabs` widget with strings or [Text][rich.text.Text] objects as positional arguments, which will set the labels in the tabs. Here's an example with three tabs:
|
||||
|
||||
```python
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Tabs("First tab", "Second tab", Text.from_markup("[u]Third[/u] tab"))
|
||||
```
|
||||
|
||||
This will create [Tab][textual.widgets.Tab] widgets internally, with auto-incrementing `id` attributes (`"tab-1"`, `"tab-2"` etc).
|
||||
You can also supply `Tab` objects directly in the constructor, which will allow you to explicitly set an `id`. Here's an example:
|
||||
|
||||
```python
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Tabs(
|
||||
Tab("First tab", id="one"),
|
||||
Tab("Second tab", id="two"),
|
||||
)
|
||||
```
|
||||
|
||||
When the user switches to a tab by clicking or pressing keys, then `Tabs` will send a [Tabs.TabActivated][textual.widgets.Tabs.TabActivated] message which contains the `tab` that was activated.
|
||||
You can then use `event.tab.id` attribute to perform any related actions.
|
||||
|
||||
## Clearing tabs
|
||||
|
||||
Clear tabs by calling the [clear][textual.widgets.Tabs.clear] method. Clearing the tabs will send a [Tabs.TabActivated][textual.widgets.Tabs.TabActivated] message with the `tab` attribute set to `None`.
|
||||
|
||||
## Adding tabs
|
||||
|
||||
Tabs may be added dynamically with the [add_tab][textual.widgets.Tabs.add_tab] method, which accepts strings, [Text][rich.text.Text], or [Tab][textual.widgets.Tab] objects.
|
||||
|
||||
## Example
|
||||
|
||||
The following example adds a `Tabs` widget above a text label. Press ++a++ to add a tab, ++c++ to clear the tabs.
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/widgets/tabs.py" press="a,a,a,a,right,right"}
|
||||
```
|
||||
|
||||
=== "tabs.py"
|
||||
|
||||
```python
|
||||
--8<-- "docs/examples/widgets/tabs.py"
|
||||
```
|
||||
|
||||
|
||||
## Reactive Attributes
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| -------- | ----- | ------- | ---------------------------------------------------------------------------------- |
|
||||
| `active` | `str` | `""` | The ID of the active tab. Set this attribute to a tab ID to change the active tab. |
|
||||
|
||||
|
||||
## Messages
|
||||
|
||||
### ::: textual.widgets.Tabs.TabActivated
|
||||
|
||||
## Bindings
|
||||
|
||||
The Tabs widget defines the following bindings:
|
||||
|
||||
::: textual.widgets.Tabs.BINDINGS
|
||||
options:
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
## See Also
|
||||
|
||||
- [Tabs](../api/tabs.md) code reference
|
||||
Reference in New Issue
Block a user