-
Notifications
You must be signed in to change notification settings - Fork 822
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Widget collapsible #2989
Merged
Merged
Widget collapsible #2989
Changes from 24 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
6d827ff
Collapsible container widget.
7850728
Expose collapsible widget.
9e81a39
Add collapsible container example
489641b
Rename member variables as label and apply formatting
ad59a98
Apply hover effect
7525a75
Apply formatting
3909a0e
Add collapsible construction example with children.
7e27c82
Wrap contents within Container and move _collapsed flag to Collapsibl…
e0272f5
Add collapsible example that is expanded by default.
0ac8fc4
Update collapsed property to be reactive
934a240
Add footer to collapse and expand all with bound keys.
156cdd9
Expose summary property of Collapsible
3c36c8a
Assign ids of ollapsed, expanded label instead of classes
2e1adc4
Add unit tests of Collapsible
252288b
Rename class Summary to Title
6e4816e
Rename variables of expanded/collapsed symbols and add it to arguments..
3ddca8b
Add documentation for Collapsible
99ce8db
Update symbol ids of Collapsible title
a5eb5af
Update src/textual/widgets/_collapsible.py
YooSunYoung 3fba4a7
Sort module names in alphabetical order
eb1e2a7
Clarify that collapsible is non-focusable in documentation.
e247d96
Add version hint
b8df854
Fix documentation of Collapsible.
6236ccc
Add snapshot test for collapsible widget
8f7c700
Stop on click event from Collapsible.
581d9c4
Handle Title.Toggle event to prevent event in Contents from propagati…
6bd3a5d
Update Collapsible default css to have 1 fraction of width instead of…
d3cddb0
Update Collapsible custom symbol snapshot
8b49b96
Add Collapsible custom symbol snapshot as an example
156533b
Update docs/widgets/collapsible.md
YooSunYoung 2766a6c
Update src/textual/widgets/_collapsible.py
YooSunYoung 75eb9bd
Fix typo in Collapsible docs
6f5ec39
Rework collapsible documentation.
rodrigogiraoserrao a166ec2
Merge branch 'main' into widget-collapsible
rodrigogiraoserrao File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
from textual.app import App, ComposeResult | ||
from textual.widgets import Collapsible, Footer, Label, Markdown | ||
|
||
LETO = """ | ||
# Duke Leto I Atreides | ||
|
||
Head of House Atreides. | ||
""" | ||
|
||
JESSICA = """ | ||
# Lady Jessica | ||
|
||
Bene Gesserit and concubine of Leto, and mother of Paul and Alia. | ||
""" | ||
|
||
PAUL = """ | ||
# Paul Atreides | ||
|
||
Son of Leto and Jessica. | ||
""" | ||
|
||
|
||
class CollapsibleApp(App): | ||
"""An example of colllapsible container.""" | ||
|
||
BINDINGS = [ | ||
("c", "collapse_or_expand(True)", "Collapse All"), | ||
("e", "collapse_or_expand(False)", "Expand All"), | ||
] | ||
|
||
def compose(self) -> ComposeResult: | ||
"""Compose app with collapsible containers.""" | ||
yield Footer() | ||
with Collapsible(collapsed=False, title="Leto"): | ||
yield Label(LETO) | ||
yield Collapsible(Markdown(JESSICA), collapsed=False, title="Jessica") | ||
with Collapsible(collapsed=True, title="Paul"): | ||
yield Markdown(PAUL) | ||
|
||
def action_collapse_or_expand(self, collapse: bool) -> None: | ||
for child in self.walk_children(Collapsible): | ||
child.collapsed = collapse | ||
|
||
|
||
if __name__ == "__main__": | ||
app = CollapsibleApp() | ||
app.run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# Collapsible | ||
|
||
!!! tip "Added in version 0.33" | ||
|
||
Collapsible contents with title. | ||
|
||
rodrigogiraoserrao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- [ ] Focusable | ||
- [x] Container | ||
|
||
This widget wraps other widgets as `Contents` and control the visibility. | ||
|
||
## Composing | ||
|
||
There are two ways to wrap other widgets. | ||
You can pass them as positional arguments to the [Collapsible][textual.widgets.Collapsible] constructor: | ||
|
||
```python | ||
def compose(self) -> ComposeResult: | ||
yield Collapsible(Label("Verbose sentence to show by default.")) | ||
|
||
``` | ||
|
||
Alternatively you can compose other widgets under the context manager: | ||
|
||
```python | ||
def compose(self) -> ComposeResult: | ||
with Collapsible(): | ||
yield Label("Verbose sentence to show by default.") | ||
|
||
``` | ||
|
||
## Title | ||
|
||
`Collapsible` can have a custom title instead of "Toggle" by `title` argument of the constructor: | ||
|
||
```python | ||
def compose(self) -> ComposeResult: | ||
with Collapsible(title="An interesting story."): | ||
yield Label("Interesting but verbose story.") | ||
|
||
``` | ||
|
||
## Collapse/Expand Symbols | ||
|
||
`Collapsible` can have different symbol(label)s for each expanded/collapsed status. | ||
|
||
```python | ||
def compose(self) -> ComposeResult: | ||
with Collapsible(title="", collapsed_symbol="► Show more", expanded_symbol="▼ Close"): | ||
yield Label("Many words.") | ||
|
||
``` | ||
|
||
## Collapse/Expand | ||
|
||
Initial status of `collapsed` can be set by the `collapsed` argument of the constructor: | ||
|
||
```python | ||
def compose(self) -> ComposeResult: | ||
with Collapsible(title="Contents 1", collapsed=False): | ||
yield Label("Short sentence to show by default.") | ||
|
||
with Collapsible(title="Contents 2", collapsed=True): # Default is True | ||
yield Label("Verbose unecessary sentence to show by default.") | ||
``` | ||
|
||
## Example | ||
|
||
The following example contains three `Collapsible`s. | ||
|
||
=== "Output" | ||
|
||
```{.textual path="docs/examples/widgets/collapsible.py"} | ||
``` | ||
|
||
=== "collapsible.py" | ||
|
||
```python | ||
--8<-- "docs/examples/widgets/collapsible.py" | ||
``` | ||
|
||
## Reactive attributes | ||
|
||
| Name | Type | Default | Description | | ||
| ----------- | ------ | ------- | -------------------------------------------------------------- | | ||
| `collapsed` | `bool` | `True` | Invisibility of `Contents`. Set it `False` to show `Contents`. | | ||
|
||
## Messages | ||
|
||
- [Collapsible.Summary.Toggle][Collapsible.Summary.Toggle] | ||
YooSunYoung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## See also | ||
|
||
<!-- TODO: Add Accordion widgets later --> | ||
|
||
--- | ||
|
||
|
||
::: textual.widgets.Collapsible | ||
options: | ||
heading_level: 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
from __future__ import annotations | ||
|
||
from textual.widget import Widget | ||
|
||
from .. import events, on | ||
from ..app import ComposeResult | ||
from ..containers import Container, Horizontal | ||
from ..message import Message | ||
from ..reactive import reactive | ||
from ..widget import Widget | ||
from ..widgets import Label | ||
|
||
__all__ = ["Collapsible"] | ||
|
||
|
||
class Collapsible(Widget): | ||
"""A collapsible container.""" | ||
|
||
collapsed = reactive(True) | ||
|
||
DEFAULT_CSS = """ | ||
Collapsible { | ||
width: 100%; | ||
height: auto; | ||
} | ||
""" | ||
|
||
class Title(Horizontal): | ||
DEFAULT_CSS = """ | ||
Title { | ||
width: 100%; | ||
height: auto; | ||
} | ||
|
||
Title:hover { | ||
background: grey; | ||
} | ||
|
||
Title .label { | ||
padding: 0 0 0 1; | ||
} | ||
|
||
Title #collapsed-symbol { | ||
display:none; | ||
} | ||
|
||
Title.-collapsed #expanded-symbol { | ||
display:none; | ||
} | ||
|
||
Title.-collapsed #collapsed-symbol { | ||
display:block; | ||
} | ||
""" | ||
|
||
def __init__( | ||
self, | ||
*, | ||
label: str, | ||
collapsed_symbol: str, | ||
expanded_symbol: str, | ||
name: str | None = None, | ||
id: str | None = None, | ||
classes: str | None = None, | ||
disabled: bool = False, | ||
) -> None: | ||
super().__init__(name=name, id=id, classes=classes, disabled=disabled) | ||
self.collapsed_symbol = collapsed_symbol | ||
self.expanded_symbol = expanded_symbol | ||
self.label = label | ||
|
||
class Toggle(Message): | ||
"""Request toggle.""" | ||
|
||
async def _on_click(self, event: events.Click) -> None: | ||
"""Inform ancestor we want to toggle.""" | ||
self.post_message(self.Toggle()) | ||
rodrigogiraoserrao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def compose(self) -> ComposeResult: | ||
"""Compose right/down arrow and label.""" | ||
yield Label(self.expanded_symbol, classes="label", id="expanded-symbol") | ||
yield Label(self.collapsed_symbol, classes="label", id="collapsed-symbol") | ||
yield Label(self.label, classes="label") | ||
|
||
class Contents(Container): | ||
DEFAULT_CSS = """ | ||
Contents { | ||
width: 100%; | ||
height: auto; | ||
padding: 0 0 0 3; | ||
} | ||
|
||
Contents.-collapsed { | ||
display: none; | ||
} | ||
""" | ||
|
||
def __init__( | ||
self, | ||
*children: Widget, | ||
title: str = "Toggle", | ||
collapsed: bool = True, | ||
collapsed_symbol: str = "►", | ||
expanded_symbol: str = "▼", | ||
name: str | None = None, | ||
id: str | None = None, | ||
classes: str | None = None, | ||
disabled: bool = False, | ||
) -> None: | ||
"""Initialize a Collapsible widget. | ||
|
||
Args: | ||
*children: Contents that will be collapsed/expanded. | ||
title: Title of the collapsed/expanded contents. | ||
collapsed: Default status of the contents. | ||
collapsed_symbol: Collapsed symbol before the title. | ||
expanded_symbol: Expanded symbol before the title. | ||
name: The name of the collapsible. | ||
id: The ID of the collapsible in the DOM. | ||
classes: The CSS classes of the collapsible. | ||
disabled: Whether the collapsible is disabled or not. | ||
""" | ||
self._title = self.Title( | ||
label=title, | ||
collapsed_symbol=collapsed_symbol, | ||
expanded_symbol=expanded_symbol, | ||
) | ||
self._contents_list: list[Widget] = list(children) | ||
super().__init__(name=name, id=id, classes=classes, disabled=disabled) | ||
self.collapsed = collapsed | ||
|
||
@on(Title.Toggle) | ||
def _update_collapsed(self) -> None: | ||
self.collapsed = not self.collapsed | ||
|
||
def watch_collapsed(self) -> None: | ||
for child in self._nodes: | ||
child.set_class(self.collapsed, "-collapsed") | ||
|
||
def compose(self) -> ComposeResult: | ||
yield from ( | ||
child.set_class(self.collapsed, "-collapsed") | ||
for child in ( | ||
self._title, | ||
self.Contents(*self._contents_list), | ||
) | ||
) | ||
|
||
def compose_add_child(self, widget: Widget) -> None: | ||
"""When using the context manager compose syntax, we want to attach nodes to the contents. | ||
|
||
Args: | ||
widget: A Widget to add. | ||
""" | ||
self._contents_list.append(widget) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You followed the structure of the documentation well and added good examples.
However, I'd like to polish the English sentences a bit more.
Would you like to have another go at them, or would you prefer I go over them and make concrete change suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you...!
I fixed some weird sentences but it'd be better if you make suggestions as well : D...!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're on the right track!
I was also looking at your sections where you explain how to change the title, the
collapsed
state, etc.Now that you have the tests that cover everything, you also have apps that show how all of that is done, correct?
Take a look at the
Button
documentation: https://textual.textualize.io/widgets/button/#exampleThe “Example” section has a couple of tabs with an app screenshot and the code for an app.
That's created by this syntax:
textual/docs/widgets/button.md
Lines 15 to 30 in c1b611b
Now that you have example apps for all of that (because of the screenshot tests) you can also add them to the documentation directly!
So, you can essentially move your sections inside the example, and automatically include screenshots of the apps so that people see the changes.
How does this sound?
If this sounds like a good idea, you can try to do it.
You can use the command
make docs-serve-offline
to build the documentation and see it live, which should help you make sure the screenshots are ending up where they need to be.Finally, inside that funny syntax
{.textual path="..."}
you can also addpress="key1,key2,..."
to press keys before the screenshot.For example,
{.textual path="path/to/my/app.py" press="e,a,enter"}
would press the keys E, A, Enter before taking the screenshot.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I understood what you mean! I will re-organize them again after work.... thanks...!