blinkui 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.
- blinkui-0.1.0/PKG-INFO +7 -0
- blinkui-0.1.0/README.md +191 -0
- blinkui-0.1.0/blinkui/__init__.py +7 -0
- blinkui-0.1.0/blinkui/app.py +30 -0
- blinkui-0.1.0/blinkui/components/__init__.py +24 -0
- blinkui-0.1.0/blinkui/components/base.py +159 -0
- blinkui-0.1.0/blinkui/components/button.py +44 -0
- blinkui-0.1.0/blinkui/components/card.py +132 -0
- blinkui-0.1.0/blinkui/components/image.py +65 -0
- blinkui-0.1.0/blinkui/components/input.py +143 -0
- blinkui-0.1.0/blinkui/components/layout.py +93 -0
- blinkui-0.1.0/blinkui/components/navigation.py +135 -0
- blinkui-0.1.0/blinkui/components/text.py +66 -0
- blinkui-0.1.0/blinkui/http/__init__.py +17 -0
- blinkui-0.1.0/blinkui/http/client.py +269 -0
- blinkui-0.1.0/blinkui/http/response.py +54 -0
- blinkui-0.1.0/blinkui/router.py +117 -0
- blinkui-0.1.0/blinkui/screen.py +67 -0
- blinkui-0.1.0/blinkui/state.py +44 -0
- blinkui-0.1.0/blinkui/storage/__init__.py +3 -0
- blinkui-0.1.0/blinkui/storage/async_storage.py +264 -0
- blinkui-0.1.0/blinkui/theme.py +170 -0
- blinkui-0.1.0/blinkui.egg-info/PKG-INFO +7 -0
- blinkui-0.1.0/blinkui.egg-info/SOURCES.txt +34 -0
- blinkui-0.1.0/blinkui.egg-info/dependency_links.txt +1 -0
- blinkui-0.1.0/blinkui.egg-info/entry_points.txt +2 -0
- blinkui-0.1.0/blinkui.egg-info/top_level.txt +2 -0
- blinkui-0.1.0/cli/__init__.py +0 -0
- blinkui-0.1.0/cli/commands/__init__.py +6 -0
- blinkui-0.1.0/cli/commands/build.py +73 -0
- blinkui-0.1.0/cli/commands/install.py +82 -0
- blinkui-0.1.0/cli/commands/new.py +206 -0
- blinkui-0.1.0/cli/commands/run.py +119 -0
- blinkui-0.1.0/cli/main.py +78 -0
- blinkui-0.1.0/setup.cfg +4 -0
- blinkui-0.1.0/setup.py +14 -0
blinkui-0.1.0/PKG-INFO
ADDED
blinkui-0.1.0/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ⚡ BlinkUI
|
|
4
|
+
|
|
5
|
+
### Build mobile apps in pure Python
|
|
6
|
+
|
|
7
|
+
[](https://python.org)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[]()
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What is BlinkUI?
|
|
16
|
+
|
|
17
|
+
BlinkUI is a mobile app framework for Python developers. Write your app in pure Python and ship it to iOS and Android. No Swift, no Kotlin, no JavaScript.
|
|
18
|
+
```python
|
|
19
|
+
from blinkui import Screen, state
|
|
20
|
+
from blinkui.components import VStack, Text, Button, Heading
|
|
21
|
+
|
|
22
|
+
class HomeScreen(Screen):
|
|
23
|
+
count = state(0)
|
|
24
|
+
|
|
25
|
+
def build(self):
|
|
26
|
+
return VStack(
|
|
27
|
+
Heading("Hello from BlinkUI"),
|
|
28
|
+
Text(f"Count: {self.count}").size(48).bold(),
|
|
29
|
+
Button("Tap Me").on_tap(self.increment),
|
|
30
|
+
).spacing(16).padding(24)
|
|
31
|
+
|
|
32
|
+
def increment(self):
|
|
33
|
+
self.count += 1
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Why BlinkUI?
|
|
39
|
+
|
|
40
|
+
| | BlinkUI | React Native | Flutter |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| Language | **Python** | JavaScript | Dart |
|
|
43
|
+
| Python ecosystem | **✅ Full access** | ❌ | ❌ |
|
|
44
|
+
| AI/ML libraries | **✅ pandas, numpy** | ❌ | ❌ |
|
|
45
|
+
| Native UI | ✅ | ✅ | ❌ |
|
|
46
|
+
| Hot reload | ✅ | ✅ | ✅ |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Getting started
|
|
51
|
+
```bash
|
|
52
|
+
pip install blinkui
|
|
53
|
+
blink new myapp
|
|
54
|
+
cd myapp
|
|
55
|
+
blink run
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Architecture
|
|
61
|
+
|
|
62
|
+
BlinkUI is built in two layers:
|
|
63
|
+
|
|
64
|
+
**C Runtime** — a fast native engine inspired by Facebook's Hermes:
|
|
65
|
+
- Node tree — every UI element represented in memory
|
|
66
|
+
- Reconciler — diffs component trees, patches only what changed
|
|
67
|
+
- Event system — catches native interactions, fires Python callbacks
|
|
68
|
+
- Animation engine — 60fps animations running entirely in C
|
|
69
|
+
- CPython host — embeds Python inside iOS and Android
|
|
70
|
+
|
|
71
|
+
**Python Framework** — what developers write in:
|
|
72
|
+
- `state()` — declare reactive variables, UI updates automatically
|
|
73
|
+
- `Screen` — base class for every screen
|
|
74
|
+
- `Router` — navigation stack with data passing
|
|
75
|
+
- `Theme` — iOS-inspired design system with dark mode
|
|
76
|
+
- HTTP client — async requests that never freeze the UI
|
|
77
|
+
- Async storage — persistent key-value storage
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Components
|
|
82
|
+
```python
|
|
83
|
+
from blinkui.components import (
|
|
84
|
+
# Layout
|
|
85
|
+
VStack, HStack, ZStack, ScrollView, Spacer,
|
|
86
|
+
|
|
87
|
+
# Text
|
|
88
|
+
Text, Heading, Label, Badge,
|
|
89
|
+
|
|
90
|
+
# Input
|
|
91
|
+
Button, TextField, Toggle, Slider,
|
|
92
|
+
|
|
93
|
+
# Display
|
|
94
|
+
Card, Image, Avatar, Modal, BottomSheet,
|
|
95
|
+
|
|
96
|
+
# Navigation
|
|
97
|
+
TabBar, NavigationBar, Toast,
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## HTTP and AI backend
|
|
104
|
+
|
|
105
|
+
BlinkUI apps connect to Python backends via HTTP:
|
|
106
|
+
```python
|
|
107
|
+
from blinkui.http import get, post, set_base_url, set_token
|
|
108
|
+
|
|
109
|
+
class DashboardScreen(Screen):
|
|
110
|
+
data = state(None)
|
|
111
|
+
|
|
112
|
+
async def on_mount(self):
|
|
113
|
+
response = await get("https://api.myapp.com/data")
|
|
114
|
+
if response.ok:
|
|
115
|
+
self.data = response.data
|
|
116
|
+
else:
|
|
117
|
+
self.error = response.error
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Your AI and ML code lives on the server — pandas, tensorflow, pytorch — and your BlinkUI app calls it via API. Mobile stays fast. Python stays powerful.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Storage
|
|
125
|
+
```python
|
|
126
|
+
from blinkui.storage import storage
|
|
127
|
+
|
|
128
|
+
# save
|
|
129
|
+
await storage.set("token", "abc123")
|
|
130
|
+
|
|
131
|
+
# get
|
|
132
|
+
token = await storage.get("token")
|
|
133
|
+
|
|
134
|
+
# delete
|
|
135
|
+
await storage.delete("token")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## CLI
|
|
141
|
+
```bash
|
|
142
|
+
blink new myapp # create a new app
|
|
143
|
+
blink run # run on simulator
|
|
144
|
+
blink run --hot # hot reload
|
|
145
|
+
blink install pandas # install packages
|
|
146
|
+
blink build ios # build for App Store
|
|
147
|
+
blink build android # build for Play Store
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Project structure
|
|
153
|
+
```
|
|
154
|
+
myapp/
|
|
155
|
+
├── main.py # entry point
|
|
156
|
+
├── blink.toml # project config
|
|
157
|
+
├── screens/
|
|
158
|
+
│ ├── home.py
|
|
159
|
+
│ └── detail.py
|
|
160
|
+
├── components/
|
|
161
|
+
└── assets/
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Roadmap
|
|
167
|
+
|
|
168
|
+
- [x] C runtime — node tree, reconciler, events, animations
|
|
169
|
+
- [x] CPython embedding on mobile
|
|
170
|
+
- [x] Python framework — state, screen, router, theme
|
|
171
|
+
- [x] Component library
|
|
172
|
+
- [x] HTTP client
|
|
173
|
+
- [x] Async storage
|
|
174
|
+
- [x] CLI tool
|
|
175
|
+
- [ ] Android native bridge
|
|
176
|
+
- [ ] Hot reload on device
|
|
177
|
+
- [ ] PyPI publish
|
|
178
|
+
- [ ] iOS bridge
|
|
179
|
+
- [ ] Documentation site
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT — free to use, modify, and distribute.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
<div align="center">
|
|
190
|
+
Built with Python and C by the BlinkUI team
|
|
191
|
+
</div>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from .screen import Screen
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class App:
|
|
5
|
+
"""
|
|
6
|
+
Entry point for every BlinkUI app.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from blinkui import App
|
|
10
|
+
from screens.home import HomeScreen
|
|
11
|
+
|
|
12
|
+
App(entry=HomeScreen).run()
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, entry, theme=None):
|
|
16
|
+
self._entry = entry
|
|
17
|
+
self._theme = theme
|
|
18
|
+
self._screen = None
|
|
19
|
+
|
|
20
|
+
def run(self):
|
|
21
|
+
print("[App] BlinkUI starting...")
|
|
22
|
+
|
|
23
|
+
# instantiate the entry screen
|
|
24
|
+
self._screen = self._entry()
|
|
25
|
+
|
|
26
|
+
# mount and render
|
|
27
|
+
self._screen.on_mount()
|
|
28
|
+
self._screen._render()
|
|
29
|
+
|
|
30
|
+
print("[App] App running")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .layout import VStack, HStack, ZStack, ScrollView, Spacer, Divider
|
|
2
|
+
from .text import Text, Heading, Label, Badge
|
|
3
|
+
from .button import Button, IconButton
|
|
4
|
+
from .image import Image, Avatar
|
|
5
|
+
from .input import TextField, Toggle, Slider, Picker
|
|
6
|
+
from .card import Card, Skeleton, Modal, BottomSheet
|
|
7
|
+
from .navigation import TabBar, Tab, NavigationBar, Toast
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
# layout
|
|
11
|
+
"VStack", "HStack", "ZStack", "ScrollView", "Spacer", "Divider",
|
|
12
|
+
# text
|
|
13
|
+
"Text", "Heading", "Label", "Badge",
|
|
14
|
+
# button
|
|
15
|
+
"Button", "IconButton",
|
|
16
|
+
# image
|
|
17
|
+
"Image", "Avatar",
|
|
18
|
+
# input
|
|
19
|
+
"TextField", "Toggle", "Slider", "Picker",
|
|
20
|
+
# card
|
|
21
|
+
"Card", "Skeleton", "Modal", "BottomSheet",
|
|
22
|
+
# navigation
|
|
23
|
+
"TabBar", "Tab", "NavigationBar", "Toast",
|
|
24
|
+
]
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# ─────────────────────────────────────────
|
|
2
|
+
# Base component
|
|
3
|
+
# Every BlinkUI component inherits from this
|
|
4
|
+
# ─────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
class Component:
|
|
7
|
+
"""
|
|
8
|
+
Base class for all BlinkUI components.
|
|
9
|
+
Supports method chaining for styling.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, *children):
|
|
13
|
+
self._children = list(children)
|
|
14
|
+
self._font_size = 16
|
|
15
|
+
self._bold = False
|
|
16
|
+
self._italic = False
|
|
17
|
+
self._color = None
|
|
18
|
+
self._background = None
|
|
19
|
+
self._width = None
|
|
20
|
+
self._height = None
|
|
21
|
+
self._padding = [0, 0, 0, 0] # top right bottom left
|
|
22
|
+
self._margin = [0, 0, 0, 0]
|
|
23
|
+
self._corner_radius = 0
|
|
24
|
+
self._opacity = 1.0
|
|
25
|
+
self._visible = True
|
|
26
|
+
self._on_tap = None
|
|
27
|
+
self._on_change = None
|
|
28
|
+
self._spacing = 8
|
|
29
|
+
self._align = "start"
|
|
30
|
+
self._animation = None
|
|
31
|
+
|
|
32
|
+
# ── Styling methods — all return self for chaining ──
|
|
33
|
+
|
|
34
|
+
def size(self, font_size):
|
|
35
|
+
self._font_size = font_size
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
def bold(self, value=True):
|
|
39
|
+
self._bold = value
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
def italic(self, value=True):
|
|
43
|
+
self._italic = value
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
def color(self, color):
|
|
47
|
+
self._color = color
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def background(self, color):
|
|
51
|
+
self._background = color
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
def width(self, value):
|
|
55
|
+
self._width = value
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def height(self, value):
|
|
59
|
+
self._height = value
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def padding(self, top=0, right=None, bottom=None, left=None):
|
|
63
|
+
# support padding(16) for all sides
|
|
64
|
+
if right is None:
|
|
65
|
+
right = bottom = left = top
|
|
66
|
+
elif bottom is None:
|
|
67
|
+
bottom = top
|
|
68
|
+
left = right
|
|
69
|
+
self._padding = [top, right, bottom, left]
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
def margin(self, top=0, right=None, bottom=None, left=None):
|
|
73
|
+
if right is None:
|
|
74
|
+
right = bottom = left = top
|
|
75
|
+
elif bottom is None:
|
|
76
|
+
bottom = top
|
|
77
|
+
left = right
|
|
78
|
+
self._margin = [top, right, bottom, left]
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def corner_radius(self, value):
|
|
82
|
+
self._corner_radius = value
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
def round(self, value=999):
|
|
86
|
+
self._corner_radius = value
|
|
87
|
+
return self
|
|
88
|
+
|
|
89
|
+
def opacity(self, value):
|
|
90
|
+
self._opacity = value
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
def visible(self, value=True):
|
|
94
|
+
self._visible = value
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
def spacing(self, value):
|
|
98
|
+
self._spacing = value
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def align(self, value):
|
|
102
|
+
# "start" "center" "end"
|
|
103
|
+
self._align = value
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
def center(self):
|
|
107
|
+
self._align = "center"
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
# ── Event methods ──
|
|
111
|
+
|
|
112
|
+
def on_tap(self, callback):
|
|
113
|
+
self._on_tap = callback
|
|
114
|
+
return self
|
|
115
|
+
|
|
116
|
+
def on_change(self, callback):
|
|
117
|
+
self._on_change = callback
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
# ── Animation ──
|
|
121
|
+
|
|
122
|
+
def animate(self, duration=300, curve="easeOut", **props):
|
|
123
|
+
self._animation = {
|
|
124
|
+
"duration": duration,
|
|
125
|
+
"curve": curve,
|
|
126
|
+
"props": props
|
|
127
|
+
}
|
|
128
|
+
return self
|
|
129
|
+
|
|
130
|
+
# ── Serialization ──
|
|
131
|
+
# Converts component tree to dict
|
|
132
|
+
# C runtime reads this dict to build native views
|
|
133
|
+
|
|
134
|
+
def to_dict(self):
|
|
135
|
+
return {
|
|
136
|
+
"type": self.__class__.__name__,
|
|
137
|
+
"font_size": self._font_size,
|
|
138
|
+
"bold": self._bold,
|
|
139
|
+
"italic": self._italic,
|
|
140
|
+
"color": self._color,
|
|
141
|
+
"background": self._background,
|
|
142
|
+
"width": self._width,
|
|
143
|
+
"height": self._height,
|
|
144
|
+
"padding": self._padding,
|
|
145
|
+
"margin": self._margin,
|
|
146
|
+
"corner_radius": self._corner_radius,
|
|
147
|
+
"opacity": self._opacity,
|
|
148
|
+
"visible": self._visible,
|
|
149
|
+
"spacing": self._spacing,
|
|
150
|
+
"align": self._align,
|
|
151
|
+
"animation": self._animation,
|
|
152
|
+
"children": [
|
|
153
|
+
c.to_dict() if isinstance(c, Component) else str(c)
|
|
154
|
+
for c in self._children
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
def __repr__(self):
|
|
159
|
+
return f"<{self.__class__.__name__} children={len(self._children)}>"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from .base import Component
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Button(Component):
|
|
5
|
+
"""
|
|
6
|
+
Tappable button.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
Button("Get Started").on_tap(self.handle_tap)
|
|
10
|
+
Button("Delete").color("#FF3B30").on_tap(self.delete)
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, label=""):
|
|
13
|
+
super().__init__()
|
|
14
|
+
self._label = str(label)
|
|
15
|
+
self._background = "#007AFF" # iOS blue
|
|
16
|
+
self._color = "#FFFFFF"
|
|
17
|
+
self._font_size = 16
|
|
18
|
+
self._bold = True
|
|
19
|
+
self._corner_radius = 12
|
|
20
|
+
self._padding = [14, 20, 14, 20]
|
|
21
|
+
|
|
22
|
+
def to_dict(self):
|
|
23
|
+
d = super().to_dict()
|
|
24
|
+
d["label"] = self._label
|
|
25
|
+
return d
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class IconButton(Component):
|
|
29
|
+
"""
|
|
30
|
+
Button with just an icon.
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
IconButton("heart").on_tap(self.like)
|
|
34
|
+
"""
|
|
35
|
+
def __init__(self, icon=""):
|
|
36
|
+
super().__init__()
|
|
37
|
+
self._icon = icon
|
|
38
|
+
self._background = "transparent"
|
|
39
|
+
self._color = "#007AFF"
|
|
40
|
+
|
|
41
|
+
def to_dict(self):
|
|
42
|
+
d = super().to_dict()
|
|
43
|
+
d["icon"] = self._icon
|
|
44
|
+
return d
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from .base import Component
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Card(Component):
|
|
5
|
+
"""
|
|
6
|
+
Elevated surface for grouping content.
|
|
7
|
+
iOS style with subtle shadow and rounded corners.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
Card(
|
|
11
|
+
VStack(
|
|
12
|
+
Text("Title").bold(),
|
|
13
|
+
Text("Subtitle").color("#8E8E93")
|
|
14
|
+
)
|
|
15
|
+
).padding(16)
|
|
16
|
+
|
|
17
|
+
Card(
|
|
18
|
+
Text("Simple card")
|
|
19
|
+
).on_tap(self.handle_tap)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, *children):
|
|
23
|
+
super().__init__(*children)
|
|
24
|
+
self._background = "#FFFFFF"
|
|
25
|
+
self._corner_radius = 12
|
|
26
|
+
self._padding = [16, 16, 16, 16]
|
|
27
|
+
self._shadow = True
|
|
28
|
+
self._shadow_color = "rgba(0,0,0,0.08)"
|
|
29
|
+
self._shadow_radius = 8
|
|
30
|
+
self._shadow_offset = [0, 2]
|
|
31
|
+
|
|
32
|
+
def shadow(self, value=True):
|
|
33
|
+
self._shadow = value
|
|
34
|
+
return self
|
|
35
|
+
|
|
36
|
+
def shadow_color(self, color):
|
|
37
|
+
self._shadow_color = color
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
def to_dict(self):
|
|
41
|
+
d = super().to_dict()
|
|
42
|
+
d["shadow"] = self._shadow
|
|
43
|
+
d["shadow_color"] = self._shadow_color
|
|
44
|
+
d["shadow_radius"] = self._shadow_radius
|
|
45
|
+
d["shadow_offset"] = self._shadow_offset
|
|
46
|
+
return d
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Skeleton(Component):
|
|
50
|
+
"""
|
|
51
|
+
Loading placeholder that animates.
|
|
52
|
+
Shows while content is being fetched.
|
|
53
|
+
|
|
54
|
+
Usage:
|
|
55
|
+
Skeleton().width(200).height(20)
|
|
56
|
+
Skeleton().width(150).height(20)
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self):
|
|
60
|
+
super().__init__()
|
|
61
|
+
self._background = "#E5E5EA"
|
|
62
|
+
self._animated = True
|
|
63
|
+
|
|
64
|
+
def to_dict(self):
|
|
65
|
+
d = super().to_dict()
|
|
66
|
+
d["animated"] = self._animated
|
|
67
|
+
return d
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Modal(Component):
|
|
71
|
+
"""
|
|
72
|
+
Full screen overlay modal.
|
|
73
|
+
|
|
74
|
+
Usage:
|
|
75
|
+
Modal(
|
|
76
|
+
VStack(
|
|
77
|
+
Heading("Are you sure?"),
|
|
78
|
+
Button("Confirm").on_tap(self.confirm),
|
|
79
|
+
Button("Cancel").on_tap(self.dismiss)
|
|
80
|
+
),
|
|
81
|
+
visible=self.show_modal
|
|
82
|
+
)
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __init__(self, *children, visible=False):
|
|
86
|
+
super().__init__(*children)
|
|
87
|
+
self._modal_visible = visible
|
|
88
|
+
self._dismissable = True
|
|
89
|
+
self._background = "#FFFFFF"
|
|
90
|
+
self._corner_radius = 20
|
|
91
|
+
|
|
92
|
+
def dismissable(self, value=True):
|
|
93
|
+
self._dismissable = value
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
def to_dict(self):
|
|
97
|
+
d = super().to_dict()
|
|
98
|
+
d["modal_visible"] = self._modal_visible
|
|
99
|
+
d["dismissable"] = self._dismissable
|
|
100
|
+
return d
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class BottomSheet(Component):
|
|
104
|
+
"""
|
|
105
|
+
Sheet that slides up from the bottom.
|
|
106
|
+
|
|
107
|
+
Usage:
|
|
108
|
+
BottomSheet(
|
|
109
|
+
VStack(
|
|
110
|
+
Text("Options"),
|
|
111
|
+
Button("Share").on_tap(self.share),
|
|
112
|
+
),
|
|
113
|
+
visible=self.show_sheet
|
|
114
|
+
)
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(self, *children, visible=False):
|
|
118
|
+
super().__init__(*children)
|
|
119
|
+
self._sheet_visible = visible
|
|
120
|
+
self._detents = ["medium", "large"] # snap points
|
|
121
|
+
self._background = "#FFFFFF"
|
|
122
|
+
self._corner_radius = 20
|
|
123
|
+
|
|
124
|
+
def detents(self, *values):
|
|
125
|
+
self._detents = list(values)
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def to_dict(self):
|
|
129
|
+
d = super().to_dict()
|
|
130
|
+
d["sheet_visible"] = self._sheet_visible
|
|
131
|
+
d["detents"] = self._detents
|
|
132
|
+
return d
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from .base import Component
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Image(Component):
|
|
5
|
+
"""
|
|
6
|
+
Displays an image from a path or URL.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
Image("avatar.png").size(80).round()
|
|
10
|
+
Image("https://example.com/photo.jpg").width(200).height(150)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, source=""):
|
|
14
|
+
super().__init__()
|
|
15
|
+
self._source = source
|
|
16
|
+
self._fit = "cover" # cover, contain, fill
|
|
17
|
+
self._alt = ""
|
|
18
|
+
|
|
19
|
+
def fit(self, mode):
|
|
20
|
+
# "cover" "contain" "fill"
|
|
21
|
+
self._fit = mode
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def alt(self, text):
|
|
25
|
+
self._alt = text
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
def to_dict(self):
|
|
29
|
+
d = super().to_dict()
|
|
30
|
+
d["source"] = self._source
|
|
31
|
+
d["fit"] = self._fit
|
|
32
|
+
d["alt"] = self._alt
|
|
33
|
+
return d
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Avatar(Component):
|
|
37
|
+
"""
|
|
38
|
+
Circular avatar image. Falls back to initials if no image.
|
|
39
|
+
|
|
40
|
+
Usage:
|
|
41
|
+
Avatar("photo.png").size(48)
|
|
42
|
+
Avatar(initials="JD").size(48).color("#007AFF")
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, source="", initials=""):
|
|
46
|
+
super().__init__()
|
|
47
|
+
self._source = source
|
|
48
|
+
self._initials = initials
|
|
49
|
+
self._size_val = 40
|
|
50
|
+
self._background = "#007AFF"
|
|
51
|
+
self._color = "#FFFFFF"
|
|
52
|
+
|
|
53
|
+
def size(self, value):
|
|
54
|
+
self._size_val = value
|
|
55
|
+
self._width = value
|
|
56
|
+
self._height = value
|
|
57
|
+
self._corner_radius = value / 2
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def to_dict(self):
|
|
61
|
+
d = super().to_dict()
|
|
62
|
+
d["source"] = self._source
|
|
63
|
+
d["initials"] = self._initials
|
|
64
|
+
d["size"] = self._size_val
|
|
65
|
+
return d
|