Skip to content

Commit f8552f4

Browse files
committed
Update
1 parent bcc0850 commit f8552f4

31 files changed

Lines changed: 1162 additions & 58 deletions

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,4 +364,7 @@ FodyWeavers.xsd
364364

365365
# App settings with secrets
366366
appsettings.json
367-
!appsettings.template.json
367+
!appsettings.template.json
368+
369+
# Auto Claude data directory
370+
.auto-claude/

Documentation/Components.md

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This document describes all available UI components for declarative `.page` file
77
- [command](#command)
88
- [open](#open)
99
- [checkbox](#checkbox)
10+
- [checkbox-list](#checkbox-list)
1011
- [radio](#radio)
1112
- [switch](#switch)
1213
- [card](#card)
@@ -137,6 +138,67 @@ cb.onUpdate = function(e) {
137138

138139
---
139140

141+
## checkbox-list
142+
143+
A multi-select list where multiple options can be toggled on/off.
144+
145+
### Attributes
146+
147+
| Attribute | Type | Default | Description |
148+
|-----------|------|---------|-------------|
149+
| `template` | string | `{{ (self.isChecked ? '✅ ' : '') + self.title }}` | Display template |
150+
| `:selected` | JS expression | - | Binding for selected IDs (comma-separated) |
151+
| `@update` | JS expression | - | Handler called when selection changes |
152+
| `id` | string | - | Component ID for JavaScript access |
153+
| `columns` | number | `1` | Number of columns for button layout |
154+
| `max-items` | number | - | Maximum items per page (enables pagination with `<navigate>`) |
155+
| `hide` | boolean | `false` | Hide the checkbox list |
156+
157+
### Child Elements
158+
159+
Options are defined using `<option>` child elements (same as radio).
160+
161+
### Template Variables
162+
163+
The `self` object in templates contains:
164+
- `self.isChecked` - boolean, whether this option is checked
165+
- `self.title` - string, the option title
166+
- `self.index` - number, option index
167+
168+
### Examples
169+
170+
```xml
171+
<!-- Basic checkbox list -->
172+
<checkbox-list id="features" @update="onFeatureChange(event)">
173+
<option value="dark-mode" title="Dark Mode" />
174+
<option value="notifications" title="Notifications" />
175+
<option value="auto-save" title="Auto Save" />
176+
</checkbox-list>
177+
178+
<!-- With binding and pagination -->
179+
<checkbox-list id="tags" max-items="5" :selected="selectedTags">
180+
<option v-for="tag in tags" :value="tag.id" :title="tag.name" />
181+
</checkbox-list>
182+
<navigate target="tags" />
183+
```
184+
185+
### JavaScript API
186+
187+
```javascript
188+
var list = component('features');
189+
list.toggle('dark-mode'); // Toggle option
190+
list.setChecked('notifications', true); // Set specific state
191+
console.log(list.IsChecked('dark-mode')); // Check if selected
192+
console.log(list.selectedIds); // Get all selected IDs
193+
console.log(list.checkedCount); // Get count of selected
194+
195+
list.onUpdate = function(e) {
196+
console.log('Changed:', e.item.id, 'checked:', e.isChecked);
197+
};
198+
```
199+
200+
---
201+
140202
## radio
141203

142204
A group of radio buttons where only one option can be selected.
@@ -149,6 +211,8 @@ A group of radio buttons where only one option can be selected.
149211
| `:selected` | JS expression | - | Binding for selected option ID |
150212
| `@select` | JS expression | - | Handler called when selection changes |
151213
| `id` | string | - | Component ID for JavaScript access |
214+
| `columns` | number | `1` | Number of columns for button layout |
215+
| `max-items` | number | - | Maximum items per page (enables pagination with `<navigate>`) |
152216
| `hide` | boolean | `false` | Hide the radio group |
153217

154218
### Child Elements
@@ -311,13 +375,13 @@ console.log(card.pageCount); // Total pages
311375

312376
## navigate
313377

314-
Navigation controls for paginated cards.
378+
Navigation controls for paginated components (card, radio, checkbox-list).
315379

316380
### Attributes
317381

318382
| Attribute | Type | Default | Description |
319383
|-----------|------|---------|-------------|
320-
| `target` | string | - | ID of the card to control |
384+
| `target` | string | - | ID of the paginated component to control |
321385
| `prevTitle` | string | `` | Previous button text |
322386
| `nextTitle` | string | `` | Next button text |
323387
| `counterTitle` | string | `{{ self.currentPage + 1 }} / {{ self.pageCount }}` | Counter display template |
@@ -327,15 +391,35 @@ Navigation controls for paginated cards.
327391
| `boundaryMessage` | string | - | Toast message at boundaries |
328392
| `@click` | JS expression | - | Handler for counter button click |
329393

394+
### Supported Components
395+
396+
Navigate can control any component that supports pagination:
397+
398+
- **card** - with `max-items` or `max-rows` attribute
399+
- **radio** - with `max-items` attribute
400+
- **checkbox-list** - with `max-items` attribute
401+
330402
### Examples
331403

332404
```xml
333-
<!-- Basic navigation -->
405+
<!-- Basic navigation with card -->
334406
<card id="myList" max-items="5">
335407
<command v-for="item in items" :title="item.name" />
336408
</card>
337409
<navigate target="myList" />
338410

411+
<!-- Navigation with radio -->
412+
<radio id="language" max-items="5" :selected="lang" @select="lang = event.select.id">
413+
<option v-for="l in languages" :value="l.code" :title="l.name" />
414+
</radio>
415+
<navigate target="language" />
416+
417+
<!-- Navigation with checkbox-list -->
418+
<checkbox-list id="features" max-items="4" :selected="selectedFeatures">
419+
<option v-for="f in features" :value="f.id" :title="f.name" />
420+
</checkbox-list>
421+
<navigate target="features" />
422+
339423
<!-- Custom navigation -->
340424
<navigate
341425
target="myList"
@@ -349,6 +433,17 @@ Navigation controls for paginated cards.
349433
<navigate target="myList" showCounter="false" />
350434
```
351435

436+
### Navigation in Modals
437+
438+
For `radio-modal` and `checkbox-modal` components, add `<navigate>` inside the modal to customize the built-in pagination. The `target` attribute is ignored (modal manages its own pagination).
439+
440+
```xml
441+
<radio-modal id="items" maxItems="10" title="Select Item">
442+
<option v-for="item in items" :value="item.id" :title="item.name" />
443+
<navigate prevTitle="Back" nextTitle="Forward" showCounter="false" />
444+
</radio-modal>
445+
```
446+
352447
---
353448

354449
## row

Documentation/Pages.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ function start() {
9898
</script>
9999
```
100100

101-
> **Note:** HTML mode is used by default. Use `<br/>` for line breaks in messages.
101+
> **Note:** HTML mode is used by default. Use `<br/>` for line breaks or `<p>content</p>` for paragraphs.
102102
103103
### Page with ViewModel
104104

Telegram.Bot.UI.Demo/Resources/Pages/App/Home/home.page

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</row>
1212

1313
<row>
14-
<link id="supportLink" :title="'🆘 ' + $t('Support')" url="https://t.me/BotFather" columns="2"></link>
14+
<open id="supportLink" type="link" :title="'🆘 ' + $t('Support')" target="https://t.me/BotFather" columns="2"></open>
1515
<command id="aboutBtn" :title="'ℹ️ ' + $t('About')" columns="2"></command>
1616
</row>
1717
</components>

Telegram.Bot.UI.Tests/E2E/Comprehensive/DemoFlowTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public async Task Start_ShowsHomePage_WithCorrectTextAndButtons() {
5555

5656
var expectedButtons = new[] {
5757
new[] { "🎮 UI Components", "🌐 Language" },
58-
new[] { "ℹ️ About" }
58+
new[] { "🆘 Support", "ℹ️ About" }
5959
};
6060

6161
Verify(expectedText, expectedButtons, "Home page");

Telegram.Bot.UI.Tests/Resources/Pages/App/Home/home.page

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</row>
1212

1313
<row>
14-
<link id="supportLink" :title="'🆘 ' + $t('Support')" url="https://t.me/BotFather" columns="2"></link>
14+
<open id="supportLink" type="link" :title="'🆘 ' + $t('Support')" target="https://t.me/BotFather" columns="2"></open>
1515
<command id="aboutBtn" :title="'ℹ️ ' + $t('About')" columns="2"></command>
1616
</row>
1717
</components>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<view id="command-vif-test" back-to-parent="false">
2+
<title>Command v-if Test</title>
3+
4+
<script>
5+
var showBtn1 = true;
6+
var showBtn2 = true;
7+
var showBtn3 = false;
8+
</script>
9+
10+
<message>
11+
Test: showBtn1={{ showBtn1 }}, showBtn2={{ showBtn2 }}, showBtn3={{ showBtn3 }}
12+
</message>
13+
14+
<components>
15+
<command v-if="showBtn1" title="BTN1" @click="UI.toast('1')"></command>
16+
<command v-if="showBtn2" title="BTN2" @click="UI.toast('2')"></command>
17+
<command v-if="showBtn3" title="BTN3" @click="UI.toast('3')"></command>
18+
<command title="Toggle1" @click="showBtn1 = !showBtn1; UI.refresh();"></command>
19+
<command title="Toggle2" @click="showBtn2 = !showBtn2; UI.refresh();"></command>
20+
<command title="Toggle3" @click="showBtn3 = !showBtn3; UI.refresh();"></command>
21+
</components>
22+
</view>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<view id="template-condition-test" vmodel="Telegram.Bot.UI.Tests.ViewModels.TemplateConditionViewModel">
2+
<title>Template Condition Test</title>
3+
4+
<message>
5+
<template v-if="VModel.isLoading">
6+
LOADING_STATE
7+
</template>
8+
<template v-else-if="VModel.hasResult">
9+
RESULT_STATE: {{ VModel.resultText }}
10+
</template>
11+
<template v-else-if="VModel.hasError">
12+
ERROR_STATE: {{ VModel.errorText }}
13+
</template>
14+
<template v-else>
15+
DEFAULT_STATE
16+
</template>
17+
</message>
18+
19+
<components>
20+
<row>
21+
<command id="start-loading" title="Start Loading"></command>
22+
<command id="set-result" title="Set Result"></command>
23+
</row>
24+
<row>
25+
<command id="set-error" title="Set Error"></command>
26+
<command id="reset" title="Reset"></command>
27+
</row>
28+
</components>
29+
30+
<script>
31+
onMounted(function() {
32+
component('start-loading').onClick = function() {
33+
VModel.StartLoading();
34+
UI.refresh();
35+
};
36+
37+
component('set-result').onClick = function() {
38+
VModel.SetResult('Success!');
39+
UI.refresh();
40+
};
41+
42+
component('set-error').onClick = function() {
43+
VModel.SetError('Something went wrong');
44+
UI.refresh();
45+
};
46+
47+
component('reset').onClick = function() {
48+
VModel.Reset();
49+
UI.refresh();
50+
};
51+
});
52+
</script>
53+
</view>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<view id="attr-syntax-test">
2+
<title>Attribute Syntax Test</title>
3+
4+
<message>
5+
Testing attribute syntax equivalence
6+
</message>
7+
8+
<components>
9+
<!-- Test 1: template literal with mustache inside -->
10+
<radio id="radio1" template="[{{ self.title }}]">
11+
<option value="a" title="Option A"></option>
12+
</radio>
13+
14+
<!-- Test 2: :template binding (should work the same as template) -->
15+
<radio id="radio2" :template="'[' + self.title + ']'">
16+
<option value="b" title="Option B"></option>
17+
</radio>
18+
19+
<!-- Test 3: title literal -->
20+
<command title="Test Button"></command>
21+
22+
<!-- Test 4: :title binding (should work the same as title) -->
23+
<command :title="'Test Button'"></command>
24+
</components>
25+
</view>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<view id="radio-pagination-test">
2+
<title>Radio Pagination Test</title>
3+
4+
<script>
5+
var items = [
6+
{ id: '1', name: 'Item 1' },
7+
{ id: '2', name: 'Item 2' },
8+
{ id: '3', name: 'Item 3' },
9+
{ id: '4', name: 'Item 4' },
10+
{ id: '5', name: 'Item 5' }
11+
];
12+
var selected = '1';
13+
</script>
14+
15+
<message>
16+
Testing radio pagination
17+
</message>
18+
19+
<components>
20+
<radio id="items" columns="1" max-items="3"
21+
v-bind-selected="selected"
22+
v-on-select="selected = event.select.id;">
23+
<option v-for="item in items" :value="item.id" :title="item.name"></option>
24+
</radio>
25+
26+
<navigate target="items"></navigate>
27+
</components>
28+
</view>

0 commit comments

Comments
 (0)