csdl-explore 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- csdl_explore-0.1.0/.claude/skills/textual/SKILL.md +240 -0
- csdl_explore-0.1.0/.claude/skills/textual/references/css.md +255 -0
- csdl_explore-0.1.0/.claude/skills/textual/references/examples.md +359 -0
- csdl_explore-0.1.0/.claude/skills/textual/references/widgets.md +132 -0
- csdl_explore-0.1.0/.gitignore +207 -0
- csdl_explore-0.1.0/.superpowers/brainstorm/1839-1774958493/content/color-direction.html +61 -0
- csdl_explore-0.1.0/.superpowers/brainstorm/1839-1774958493/content/waiting.html +3 -0
- csdl_explore-0.1.0/.superpowers/brainstorm/1839-1774958493/content/welcome-mockup.html +71 -0
- csdl_explore-0.1.0/.superpowers/brainstorm/1839-1774958493/state/server-stopped +1 -0
- csdl_explore-0.1.0/.superpowers/brainstorm/1839-1774958493/state/server.pid +1 -0
- csdl_explore-0.1.0/CLAUDE.md +102 -0
- csdl_explore-0.1.0/LICENSE +21 -0
- csdl_explore-0.1.0/PKG-INFO +350 -0
- csdl_explore-0.1.0/README.md +322 -0
- csdl_explore-0.1.0/docs/plans/2026-02-08-navigation-graph-design.md +278 -0
- csdl_explore-0.1.0/docs/plans/2026-03-05-query-cli-command-design.md +146 -0
- csdl_explore-0.1.0/docs/plans/2026-03-05-query-command-implementation.md +344 -0
- csdl_explore-0.1.0/docs/plans/2026-03-10-cli-improvements.md +907 -0
- csdl_explore-0.1.0/docs/superpowers/plans/2026-03-31-visual-refresh.md +754 -0
- csdl_explore-0.1.0/docs/superpowers/specs/2026-03-31-visual-refresh-design.md +103 -0
- csdl_explore-0.1.0/pyproject.toml +44 -0
- csdl_explore-0.1.0/samples/picklist.json +666 -0
- csdl_explore-0.1.0/samples/test001.edmx.xml +1150 -0
- csdl_explore-0.1.0/samples/test002.edmx.xml +268 -0
- csdl_explore-0.1.0/samples/test003.edmx.xml +912 -0
- csdl_explore-0.1.0/samples/test004.edmx.xml +1541 -0
- csdl_explore-0.1.0/src/csdl_explore/__init__.py +7 -0
- csdl_explore-0.1.0/src/csdl_explore/app.py +500 -0
- csdl_explore-0.1.0/src/csdl_explore/cli.py +812 -0
- csdl_explore-0.1.0/src/csdl_explore/exit_codes.py +7 -0
- csdl_explore-0.1.0/src/csdl_explore/explorer.py +330 -0
- csdl_explore-0.1.0/src/csdl_explore/formatters.py +641 -0
- csdl_explore-0.1.0/src/csdl_explore/parser.py +288 -0
- csdl_explore-0.1.0/src/csdl_explore/repl.py +627 -0
- csdl_explore-0.1.0/src/csdl_explore/sap_client.py +382 -0
- csdl_explore-0.1.0/src/csdl_explore/themes.py +83 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/__init__.py +37 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/auth_modal.py +169 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/connection_panel.py +189 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/details_tab.py +109 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/entity_pane.py +61 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/entity_pane.tcss +2 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/entity_tree.py +138 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/filter_bar.py +24 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/filterable_table.py +87 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/global_search.py +193 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/graph_tab.py +70 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/json_viewer_modal.py +112 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/nav_graph.py +443 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/picklist_entities_tab.py +82 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/picklist_impact_tab.py +78 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/picklist_overview_tab.py +66 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/picklist_pane.py +68 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/picklist_values_tab.py +211 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/properties_tab.py +107 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/query_builder.py +430 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/query_builder.tcss +119 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/query_tab.py +120 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/record_view_modal.py +131 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/results_viewer.py +282 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/search_results.py +42 -0
- csdl_explore-0.1.0/src/csdl_explore/widgets/welcome_tab.py +96 -0
- csdl_explore-0.1.0/test_app.py +40 -0
- csdl_explore-0.1.0/tests/test_exit_codes.py +12 -0
- csdl_explore-0.1.0/tests/test_explorer.py +28 -0
- csdl_explore-0.1.0/tests/test_formatters.py +193 -0
- csdl_explore-0.1.0/tests/test_sap_errors.py +30 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: textual
|
|
3
|
+
description: Build terminal user interface (TUI) applications with the Textual framework. Use when creating new Textual apps, adding screens/widgets, styling with TCSS, handling events and reactivity, testing TUI apps, or any task involving "textual", "TUI", or terminal-based Python applications.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Textual TUI Framework
|
|
7
|
+
|
|
8
|
+
Build terminal applications with Textual's web-inspired architecture: App → Screen → Widget.
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from textual.app import App, ComposeResult
|
|
14
|
+
from textual.widgets import Header, Footer, Static
|
|
15
|
+
|
|
16
|
+
class MyApp(App):
|
|
17
|
+
CSS_PATH = "styles.tcss"
|
|
18
|
+
BINDINGS = [("q", "quit", "Quit"), ("d", "toggle_dark", "Dark Mode")]
|
|
19
|
+
|
|
20
|
+
def compose(self) -> ComposeResult:
|
|
21
|
+
yield Header()
|
|
22
|
+
yield Static("Hello, World!")
|
|
23
|
+
yield Footer()
|
|
24
|
+
|
|
25
|
+
def action_toggle_dark(self) -> None:
|
|
26
|
+
self.theme = "textual-dark" if self.theme == "textual-light" else "textual-light"
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
MyApp().run()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Core Concepts
|
|
33
|
+
|
|
34
|
+
### Widget Lifecycle
|
|
35
|
+
1. `__init__()` → `compose()` → `on_mount()` → `on_show()`/`on_hide()` → `on_unmount()`
|
|
36
|
+
|
|
37
|
+
### Reactivity
|
|
38
|
+
```python
|
|
39
|
+
from textual.reactive import reactive, var
|
|
40
|
+
|
|
41
|
+
class MyWidget(Widget):
|
|
42
|
+
count = reactive(0) # Triggers refresh on change
|
|
43
|
+
internal = var("") # No automatic refresh
|
|
44
|
+
|
|
45
|
+
def watch_count(self, new_value: int) -> None:
|
|
46
|
+
"""Called when count changes."""
|
|
47
|
+
self.styles.background = "green" if new_value > 0 else "red"
|
|
48
|
+
|
|
49
|
+
def validate_count(self, value: int) -> int:
|
|
50
|
+
"""Constrain values."""
|
|
51
|
+
return max(0, min(100, value))
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Events and Messages
|
|
55
|
+
```python
|
|
56
|
+
from textual import on
|
|
57
|
+
from textual.message import Message
|
|
58
|
+
|
|
59
|
+
class MyWidget(Widget):
|
|
60
|
+
class Selected(Message):
|
|
61
|
+
def __init__(self, value: str) -> None:
|
|
62
|
+
self.value = value
|
|
63
|
+
super().__init__()
|
|
64
|
+
|
|
65
|
+
def on_click(self) -> None:
|
|
66
|
+
self.post_message(self.Selected("item"))
|
|
67
|
+
|
|
68
|
+
class MyApp(App):
|
|
69
|
+
# Handler naming: on_<widget>_<message>
|
|
70
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
71
|
+
self.log(f"Button {event.button.id} pressed")
|
|
72
|
+
|
|
73
|
+
@on(Button.Pressed, "#submit") # CSS selector filtering
|
|
74
|
+
def handle_submit(self) -> None:
|
|
75
|
+
pass
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Data Flow
|
|
79
|
+
- **Attributes down**: Parent sets child properties directly
|
|
80
|
+
- **Messages up**: Child posts messages to parent via `post_message()`
|
|
81
|
+
|
|
82
|
+
## Screens
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from textual.screen import Screen
|
|
86
|
+
|
|
87
|
+
class WelcomeScreen(Screen):
|
|
88
|
+
BINDINGS = [("escape", "app.pop_screen", "Back")]
|
|
89
|
+
|
|
90
|
+
def compose(self) -> ComposeResult:
|
|
91
|
+
yield Static("Welcome!")
|
|
92
|
+
yield Button("Continue", id="continue")
|
|
93
|
+
|
|
94
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
95
|
+
if event.button.id == "continue":
|
|
96
|
+
self.app.push_screen("main")
|
|
97
|
+
|
|
98
|
+
class MyApp(App):
|
|
99
|
+
SCREENS = {"welcome": WelcomeScreen, "main": MainScreen}
|
|
100
|
+
|
|
101
|
+
def on_mount(self) -> None:
|
|
102
|
+
self.push_screen("welcome")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Custom Widgets
|
|
106
|
+
|
|
107
|
+
### Simple Widget
|
|
108
|
+
```python
|
|
109
|
+
class Greeting(Widget):
|
|
110
|
+
def render(self) -> RenderResult:
|
|
111
|
+
return "Hello, [bold]World[/bold]!"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Compound Widget
|
|
115
|
+
```python
|
|
116
|
+
class LabeledButton(Widget):
|
|
117
|
+
DEFAULT_CSS = """
|
|
118
|
+
LabeledButton { layout: horizontal; height: auto; }
|
|
119
|
+
LabeledButton Label { width: 1fr; }
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, label: str, button_text: str) -> None:
|
|
123
|
+
self.label_text = label
|
|
124
|
+
self.button_text = button_text
|
|
125
|
+
super().__init__()
|
|
126
|
+
|
|
127
|
+
def compose(self) -> ComposeResult:
|
|
128
|
+
yield Label(self.label_text)
|
|
129
|
+
yield Button(self.button_text)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Focusable Widget
|
|
133
|
+
```python
|
|
134
|
+
class Counter(Widget):
|
|
135
|
+
can_focus = True
|
|
136
|
+
BINDINGS = [("up", "increment", "+"), ("down", "decrement", "-")]
|
|
137
|
+
count = reactive(0)
|
|
138
|
+
|
|
139
|
+
def action_increment(self) -> None:
|
|
140
|
+
self.count += 1
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Layout Patterns
|
|
144
|
+
|
|
145
|
+
### Containers
|
|
146
|
+
```python
|
|
147
|
+
from textual.containers import Horizontal, Vertical, Grid, VerticalScroll
|
|
148
|
+
|
|
149
|
+
def compose(self) -> ComposeResult:
|
|
150
|
+
with Vertical():
|
|
151
|
+
with Horizontal():
|
|
152
|
+
yield Button("Left")
|
|
153
|
+
yield Button("Right")
|
|
154
|
+
with VerticalScroll():
|
|
155
|
+
for i in range(100):
|
|
156
|
+
yield Label(f"Item {i}")
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Grid CSS
|
|
160
|
+
```css
|
|
161
|
+
Grid {
|
|
162
|
+
layout: grid;
|
|
163
|
+
grid-size: 3 2; /* columns rows */
|
|
164
|
+
grid-columns: 1fr 2fr 1fr;
|
|
165
|
+
grid-gutter: 1 2;
|
|
166
|
+
}
|
|
167
|
+
#wide { column-span: 2; }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Docking
|
|
171
|
+
```css
|
|
172
|
+
#header { dock: top; height: 3; }
|
|
173
|
+
#sidebar { dock: left; width: 25; }
|
|
174
|
+
#footer { dock: bottom; height: 1; }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Workers (Async)
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from textual import work
|
|
181
|
+
|
|
182
|
+
class MyApp(App):
|
|
183
|
+
@work(exclusive=True) # Cancels previous
|
|
184
|
+
async def fetch_data(self, url: str) -> None:
|
|
185
|
+
async with httpx.AsyncClient() as client:
|
|
186
|
+
response = await client.get(url)
|
|
187
|
+
self.query_one("#result").update(response.text)
|
|
188
|
+
|
|
189
|
+
@work(thread=True) # For sync APIs
|
|
190
|
+
def sync_operation(self) -> None:
|
|
191
|
+
result = blocking_call()
|
|
192
|
+
self.call_from_thread(self.update_ui, result)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Testing
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
async def test_app():
|
|
199
|
+
app = MyApp()
|
|
200
|
+
async with app.run_test() as pilot:
|
|
201
|
+
await pilot.press("enter")
|
|
202
|
+
await pilot.click("#button")
|
|
203
|
+
await pilot.pause() # Wait for messages
|
|
204
|
+
assert app.query_one("#status").render() == "Done"
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Common Operations
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
# Query widgets
|
|
211
|
+
self.query_one("#id")
|
|
212
|
+
self.query_one(Button)
|
|
213
|
+
self.query(".class")
|
|
214
|
+
|
|
215
|
+
# CSS classes
|
|
216
|
+
widget.add_class("active")
|
|
217
|
+
widget.toggle_class("visible")
|
|
218
|
+
widget.set_class(condition, "active")
|
|
219
|
+
|
|
220
|
+
# Visibility
|
|
221
|
+
widget.display = True/False
|
|
222
|
+
|
|
223
|
+
# Mount/remove
|
|
224
|
+
self.mount(NewWidget())
|
|
225
|
+
widget.remove()
|
|
226
|
+
|
|
227
|
+
# Timers
|
|
228
|
+
self.set_interval(1.0, callback)
|
|
229
|
+
self.set_timer(5.0, callback)
|
|
230
|
+
|
|
231
|
+
# Exit
|
|
232
|
+
self.exit(return_code=0)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## References
|
|
236
|
+
|
|
237
|
+
- **Widget catalog and messages**: See [references/widgets.md](references/widgets.md)
|
|
238
|
+
- **CSS properties and selectors**: See [references/css.md](references/css.md)
|
|
239
|
+
- **Complete examples**: See [references/examples.md](references/examples.md)
|
|
240
|
+
- **Official docs**: https://textual.textualize.io/
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# Textual CSS (TCSS) Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [Selectors](#selectors)
|
|
5
|
+
- [Layout Properties](#layout-properties)
|
|
6
|
+
- [Sizing Properties](#sizing-properties)
|
|
7
|
+
- [Positioning Properties](#positioning-properties)
|
|
8
|
+
- [Appearance Properties](#appearance-properties)
|
|
9
|
+
- [Text Properties](#text-properties)
|
|
10
|
+
- [Scrolling Properties](#scrolling-properties)
|
|
11
|
+
- [Animation Properties](#animation-properties)
|
|
12
|
+
- [Theme Variables](#theme-variables)
|
|
13
|
+
- [Pseudo-classes](#pseudo-classes)
|
|
14
|
+
|
|
15
|
+
## Selectors
|
|
16
|
+
|
|
17
|
+
```css
|
|
18
|
+
/* Type selector - matches Python class name */
|
|
19
|
+
Button { }
|
|
20
|
+
|
|
21
|
+
/* ID selector */
|
|
22
|
+
#sidebar { }
|
|
23
|
+
|
|
24
|
+
/* Class selector */
|
|
25
|
+
.error { }
|
|
26
|
+
|
|
27
|
+
/* Compound selectors */
|
|
28
|
+
Button.primary { }
|
|
29
|
+
|
|
30
|
+
/* Descendant combinator */
|
|
31
|
+
#dialog Button { }
|
|
32
|
+
|
|
33
|
+
/* Child combinator */
|
|
34
|
+
Container > Button { }
|
|
35
|
+
|
|
36
|
+
/* Multiple selectors */
|
|
37
|
+
#submit, #cancel { }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Nested CSS
|
|
41
|
+
|
|
42
|
+
```css
|
|
43
|
+
#container {
|
|
44
|
+
background: $surface;
|
|
45
|
+
|
|
46
|
+
.item {
|
|
47
|
+
padding: 1;
|
|
48
|
+
|
|
49
|
+
&:hover {
|
|
50
|
+
background: $primary;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&.-active {
|
|
54
|
+
border: solid green;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Layout Properties
|
|
61
|
+
|
|
62
|
+
```css
|
|
63
|
+
layout: vertical; /* vertical, horizontal, grid */
|
|
64
|
+
|
|
65
|
+
/* Grid layout */
|
|
66
|
+
grid-size: 3 2; /* columns rows */
|
|
67
|
+
grid-columns: 1fr 2fr 1fr;
|
|
68
|
+
grid-rows: auto 1fr;
|
|
69
|
+
grid-gutter: 1 2; /* vertical horizontal */
|
|
70
|
+
column-span: 2;
|
|
71
|
+
row-span: 2;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Sizing Properties
|
|
75
|
+
|
|
76
|
+
```css
|
|
77
|
+
width: 50%; /* auto, %, fr, cells */
|
|
78
|
+
height: 100%;
|
|
79
|
+
min-width: 20;
|
|
80
|
+
max-width: 80;
|
|
81
|
+
min-height: 10;
|
|
82
|
+
max-height: 50;
|
|
83
|
+
|
|
84
|
+
margin: 1 2; /* vertical horizontal */
|
|
85
|
+
margin: 1 2 1 2; /* top right bottom left */
|
|
86
|
+
padding: 1 2;
|
|
87
|
+
padding: 1 2 1 2;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Positioning Properties
|
|
91
|
+
|
|
92
|
+
```css
|
|
93
|
+
dock: top; /* top, right, bottom, left */
|
|
94
|
+
offset: 5 10; /* x y offset */
|
|
95
|
+
offset-x: -100%;
|
|
96
|
+
offset-y: 50%;
|
|
97
|
+
layer: overlay; /* layer name */
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Layers
|
|
101
|
+
```css
|
|
102
|
+
Screen {
|
|
103
|
+
layers: base overlay modal;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#background { layer: base; }
|
|
107
|
+
#popup { layer: overlay; }
|
|
108
|
+
#dialog { layer: modal; }
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Appearance Properties
|
|
112
|
+
|
|
113
|
+
```css
|
|
114
|
+
background: darkblue;
|
|
115
|
+
color: white;
|
|
116
|
+
|
|
117
|
+
/* Borders */
|
|
118
|
+
border: solid green; /* none, solid, double, round, heavy, tall, wide */
|
|
119
|
+
border-top: double red;
|
|
120
|
+
border-right: solid blue;
|
|
121
|
+
border-bottom: heavy green;
|
|
122
|
+
border-left: round yellow;
|
|
123
|
+
|
|
124
|
+
outline: dashed red;
|
|
125
|
+
opacity: 0.5;
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Text Properties
|
|
129
|
+
|
|
130
|
+
```css
|
|
131
|
+
text-align: center; /* left, center, right */
|
|
132
|
+
content-align: center middle; /* horizontal vertical */
|
|
133
|
+
text-style: bold italic; /* bold, italic, underline, reverse, strike */
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Scrolling Properties
|
|
137
|
+
|
|
138
|
+
```css
|
|
139
|
+
overflow: auto; /* auto, hidden, scroll */
|
|
140
|
+
overflow-x: auto;
|
|
141
|
+
overflow-y: scroll;
|
|
142
|
+
scrollbar-gutter: stable;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Animation Properties
|
|
146
|
+
|
|
147
|
+
```css
|
|
148
|
+
transition: background 500ms;
|
|
149
|
+
transition: offset 200ms ease-in-out;
|
|
150
|
+
transition: opacity 300ms linear;
|
|
151
|
+
|
|
152
|
+
/* Multiple transitions */
|
|
153
|
+
transition: background 200ms, offset 300ms;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Easing Functions
|
|
157
|
+
- `linear`
|
|
158
|
+
- `ease-in`
|
|
159
|
+
- `ease-out`
|
|
160
|
+
- `ease-in-out`
|
|
161
|
+
|
|
162
|
+
## Theme Variables
|
|
163
|
+
|
|
164
|
+
### Core Colors
|
|
165
|
+
```css
|
|
166
|
+
$primary
|
|
167
|
+
$secondary
|
|
168
|
+
$success
|
|
169
|
+
$warning
|
|
170
|
+
$error
|
|
171
|
+
$text
|
|
172
|
+
$background
|
|
173
|
+
$surface
|
|
174
|
+
$panel
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Color Variants
|
|
178
|
+
```css
|
|
179
|
+
$primary-lighten-1
|
|
180
|
+
$primary-lighten-2
|
|
181
|
+
$primary-lighten-3
|
|
182
|
+
$primary-darken-1
|
|
183
|
+
$primary-darken-2
|
|
184
|
+
$primary-darken-3
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Custom Variables
|
|
188
|
+
```css
|
|
189
|
+
$my-color: dodgerblue;
|
|
190
|
+
$spacing: 2;
|
|
191
|
+
|
|
192
|
+
.widget {
|
|
193
|
+
background: $my-color;
|
|
194
|
+
padding: $spacing;
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Pseudo-classes
|
|
199
|
+
|
|
200
|
+
```css
|
|
201
|
+
Button:hover { background: lightblue; }
|
|
202
|
+
Button:focus { border: double green; }
|
|
203
|
+
Button:disabled { opacity: 0.5; }
|
|
204
|
+
|
|
205
|
+
/* Custom state classes (prefixed with -) */
|
|
206
|
+
.sidebar.-visible { offset-x: 0; }
|
|
207
|
+
.item.-selected { background: $primary; }
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Common Patterns
|
|
211
|
+
|
|
212
|
+
### Animated Sidebar
|
|
213
|
+
```css
|
|
214
|
+
Sidebar {
|
|
215
|
+
width: 30;
|
|
216
|
+
dock: left;
|
|
217
|
+
offset-x: -100%;
|
|
218
|
+
transition: offset 200ms;
|
|
219
|
+
|
|
220
|
+
&.-visible {
|
|
221
|
+
offset-x: 0;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Centered Modal
|
|
227
|
+
```css
|
|
228
|
+
#modal {
|
|
229
|
+
layer: modal;
|
|
230
|
+
width: 60;
|
|
231
|
+
height: 20;
|
|
232
|
+
background: $surface;
|
|
233
|
+
border: thick $primary;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
Screen {
|
|
237
|
+
align: center middle;
|
|
238
|
+
layers: base modal;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Responsive Grid
|
|
243
|
+
```css
|
|
244
|
+
Grid {
|
|
245
|
+
layout: grid;
|
|
246
|
+
grid-size: 3;
|
|
247
|
+
grid-gutter: 1;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@media (max-width: 60) {
|
|
251
|
+
Grid {
|
|
252
|
+
grid-size: 2;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|