mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-06 17:02:59 +02:00
Merge pull request #197 from ipnet-mesh/chore/update-radio-info-display
feat: replace flat radio config list with icon tile grid on homepage
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
# Plan: Radio Info Tile Display — Homepage Network Info Panel Overhaul
|
||||
|
||||
**Date**: 2026-05-06
|
||||
**Status**: Draft
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Replace the flat label:value list in the **Network Info** panel on the home page (`/`) with a **3×2 grid of compact tiles**. Each tile represents one radio configuration parameter and displays an icon, a label, and the value. The design uses subtle colour, rounded borders, and a clear visual hierarchy (icon → label → value).
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
### UI
|
||||
|
||||
The Network Info panel at `home.js:204–215` renders radio config parameters as a simple two-column flex list inside a `card bg-base-100 shadow-xl`:
|
||||
|
||||
```
|
||||
┌──────────────────────────────┐
|
||||
│ ℹ Network Info │
|
||||
│ │
|
||||
│ Profile: EU/UK Narrow │
|
||||
│ Frequency: 869.618MHz │
|
||||
│ Bandwidth: 62.5kHz │
|
||||
│ Spreading Factor: 8 │
|
||||
│ Coding Rate: 8 │
|
||||
│ TX Power: 22dBm │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
The `renderRadioConfig()` function at `home.js:11–28` maps the 6 `RadioConfig` fields to a flat array of label+value pairs, rendered with `<div class="flex justify-between">` rows.
|
||||
|
||||
### Data Source
|
||||
|
||||
Radio config is injected server-side via `window.__APP_CONFIG__.network_radio_config` (not an API endpoint). The `RadioConfig` Pydantic model (`common/schemas/network.py`) parses a comma-delimited string into:
|
||||
- `profile` (str) — e.g. `"EU/UK Narrow"`
|
||||
- `frequency` (str) — e.g. `"869.618MHz"`
|
||||
- `bandwidth` (str) — e.g. `"62.5kHz"`
|
||||
- `spreading_factor` (int) — e.g. `8`
|
||||
- `coding_rate` (int) — e.g. `8`
|
||||
- `tx_power` (str) — e.g. `"22dBm"`
|
||||
|
||||
Any field can be `None`/`null` (partial configs are supported).
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `src/meshcore_hub/web/static/js/spa/pages/home.js` | Home page render; `renderRadioConfig()` at lines 11–28; panel layout at lines 204–215 |
|
||||
| `src/meshcore_hub/web/static/css/app.css` | Custom styles; color palette at lines 21–29; panel utilities at lines 88–113 |
|
||||
| `src/meshcore_hub/web/static/js/spa/components.js` | Shared component imports; `t()`, `html`, `litRender`, `pageColors` |
|
||||
| `src/meshcore_hub/web/static/js/spa/icons.js` | SVG icon functions (`iconAntenna`, `iconChart`, etc.) |
|
||||
| `src/meshcore_hub/web/static/locales/en.json` | i18n keys: `links.profile`, `home.frequency`, `home.bandwidth`, `home.spreading_factor`, `home.coding_rate`, `home.tx_power`, `home.network_info` |
|
||||
| `src/meshcore_hub/common/schemas/network.py` | `RadioConfig` Pydantic model (data structure validation) |
|
||||
| `src/meshcore_hub/web/app.py` | Server-side config injection into `__APP_CONFIG__` (line 237–272) |
|
||||
|
||||
---
|
||||
|
||||
## Target State
|
||||
|
||||
### UI
|
||||
|
||||
Replace the flat label:value list with a **3-column × 2-row grid** of compact tiles. Each tile has this structure:
|
||||
|
||||
```
|
||||
┌──────────────────────┐
|
||||
│ │
|
||||
│ [icon] │ ← higher-contrast colour
|
||||
│ │
|
||||
│ Label │ ← medium-contrast font (e.g. "Frequency")
|
||||
│ 869.618MHz │ ← white (dark theme) / black (light theme)
|
||||
│ │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
**6 tiles**: Profile, Frequency, Bandwidth, Spreading Factor, Coding Rate, TX Power.
|
||||
|
||||
**Grid layout**:
|
||||
|
||||
```
|
||||
┌────────────┬────────────┬────────────┐
|
||||
│ Profile │ Frequency │ Bandwidth │
|
||||
│ EU/UK... │ 869.618MHz │ 62.5kHz │
|
||||
├────────────┼────────────┼────────────┤
|
||||
│ Spreading │ Coding │ TX Power │
|
||||
│ Factor 8 │ Rate 8 │ 22dBm │
|
||||
└────────────┴────────────┴────────────┘
|
||||
```
|
||||
|
||||
**Responsive behaviour**: On small screens (`md` breakpoint and below), collapse to 2 columns. No single-column fallback — 2 columns minimum ensures tiles remain compact even on phones.
|
||||
|
||||
**Missing fields**: If a radio parameter is `null`/`undefined`, the entire tile for that parameter is hidden (same as current behaviour where empty values are filtered out).
|
||||
|
||||
### Visual Design
|
||||
|
||||
| Element | Dark Theme | Light Theme |
|
||||
|---------|-----------|-------------|
|
||||
| **Tile background** | `transparent` (no fill) | Same |
|
||||
| **Tile border** | `border border-base-content/10 rounded-box` | Same |
|
||||
| **Icon colour** | `var(--color-radio)` — new cyan accent (`oklch(0.75 0.15 210)`) | `var(--color-radio)` — darker variant (`oklch(0.55 0.15 210)`) |
|
||||
| **Label** | `opacity-70` on `text-base-content` (medium contrast) | Same |
|
||||
| **Value** | `text-base-content` (white in dark, black in light) with `font-semibold` | Same |
|
||||
|
||||
Tiles use a transparent background — no fill, no `panel-glow`. The rounded border alone provides visual separation, keeping tiles compact and lightweight.
|
||||
|
||||
### Icon Selection
|
||||
|
||||
Create new SVG icon functions in `icons.js` for radio-specific concepts, OR reuse existing Heroicons:
|
||||
|
||||
| Parameter | Icon Concept | Source |
|
||||
|-----------|-------------|--------|
|
||||
| Profile | Cog/gear (settings profile) | Existing `iconSettings` or new cog variant |
|
||||
| Frequency | Waveform/sine wave | New SVG — sine wave icon |
|
||||
| Bandwidth | Sliders/adjust | New SVG — horizontal sliders |
|
||||
| Spreading Factor | Arrows expanding outward | New SVG — expand arrows |
|
||||
| Coding Rate | Shield with check | New SVG — shield-check |
|
||||
| TX Power | Bolt/power | New SVG — bolt |
|
||||
|
||||
Each new icon follows the existing pattern: a function exporting a `lit-html` template with a `cls` parameter for CSS sizing, using Heroicons outline style (24×24 viewBox, `stroke="currentColor"` with `stroke-width="2"`, `stroke-linecap="round"`, `stroke-linejoin="round"`).
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### 1. Add Radio Parameter Icons (`icons.js`)
|
||||
|
||||
Add 5–6 new icon functions in `icons.js`. Where an adequate existing icon exists, reuse it. For the rest, create new Heroicons-style SVG icons.
|
||||
|
||||
Icons needed (at minimum 5 — `iconSettings` already exists for Profile):
|
||||
- `iconFrequency` — waveform/sine (new)
|
||||
- `iconBandwidth` — horizontal sliders (new)
|
||||
- `iconSpreadingFactor` — expand arrows (new)
|
||||
- `iconCodingRate` — shield-check (new)
|
||||
- `iconTxPower` — bolt/lightning (new)
|
||||
|
||||
### 2. Rewrite `renderRadioConfig()` → `renderRadioTiles()` (`home.js`)
|
||||
|
||||
Replace the current `renderRadioConfig()` function (lines 11–28) with a new `renderRadioTiles()` function that:
|
||||
|
||||
1. Maps each parameter to a tile definition: `{ icon, label, value }`
|
||||
2. Filters out tiles with no value
|
||||
3. Renders a CSS Grid (3 columns, responsive) of tiles
|
||||
4. Each tile is a `<div>` with transparent background, rounded border, centered content
|
||||
|
||||
**Template structure (per tile)**:
|
||||
```js
|
||||
html`
|
||||
<div class="flex flex-col items-center justify-center gap-1.5 p-3
|
||||
border border-base-content/10 rounded-box
|
||||
text-center">
|
||||
<span class="radio-tile-icon w-6 h-6">
|
||||
${icon('w-full h-full')}
|
||||
</span>
|
||||
<span class="text-xs opacity-70 leading-tight">${label}</span>
|
||||
<span class="text-sm font-semibold leading-tight">${String(value)}</span>
|
||||
</div>`
|
||||
```
|
||||
|
||||
**Grid wrapper**: `grid grid-cols-2 md:grid-cols-3 gap-3`
|
||||
|
||||
**Panel container**: Replace `space-y-2` with the grid wrapper.
|
||||
|
||||
### 3. Update Icon Imports (`home.js`)
|
||||
|
||||
Add the new icon imports to the existing import block at line 6–9:
|
||||
|
||||
```js
|
||||
import {
|
||||
iconDashboard, iconNodes, iconAdvertisements, iconMessages, iconMembers, iconMap,
|
||||
iconPage, iconInfo, iconChart, iconGlobe, iconGithub,
|
||||
iconSettings, iconFrequency, iconBandwidth, iconSpreadingFactor, iconCodingRate, iconTxPower,
|
||||
} from '../icons.js';
|
||||
```
|
||||
|
||||
### 4. Add Radio Tile CSS (`app.css`)
|
||||
|
||||
Add a new CSS rule for the radio tile icon colour:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--color-radio: oklch(0.75 0.15 210); /* cyan — radio tile icons */
|
||||
}
|
||||
[data-theme="light"] {
|
||||
--color-radio: oklch(0.55 0.15 210);
|
||||
}
|
||||
|
||||
.radio-tile-icon {
|
||||
color: var(--color-radio);
|
||||
}
|
||||
```
|
||||
|
||||
This adds a new `--color-radio` CSS custom property (cyan) with light-mode variant, following the existing palette convention in `app.css:21–40`. The `.radio-tile-icon` class applies it to tile icons.
|
||||
|
||||
### 5. i18n Updates (`en.json`)
|
||||
|
||||
The existing i18n keys are reused as-is:
|
||||
- `links.profile` → label "Profile"
|
||||
- `home.frequency` → label "Frequency"
|
||||
- `home.bandwidth` → label "Bandwidth"
|
||||
- `home.spreading_factor` → label "Spreading Factor"
|
||||
- `home.coding_rate` → label "Coding Rate"
|
||||
- `home.tx_power` → label "TX Power"
|
||||
|
||||
No new i18n keys required.
|
||||
|
||||
---
|
||||
|
||||
## Files Changed — Summary
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/meshcore_hub/web/static/js/spa/icons.js` | Add 5 new radio-specific SVG icon functions (`iconFrequency`, `iconBandwidth`, `iconSpreadingFactor`, `iconCodingRate`, `iconTxPower`) |
|
||||
| `src/meshcore_hub/web/static/js/spa/pages/home.js` | Replace `renderRadioConfig()` with `renderRadioTiles()`; update panel template at lines 204–215 to use grid layout; add new icon imports to existing import block at lines 6–9 |
|
||||
| `src/meshcore_hub/web/static/css/app.css` | Add `--color-radio` cyan CSS custom property (with light-mode variant) to colour palette section; add `.radio-tile-icon` class |
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Visual Verification
|
||||
|
||||
1. Start the app with a radio config string: `NETWORK_RADIO_CONFIG="EU/UK Narrow,869.618MHz,62.5kHz,8,8,22dBm"`
|
||||
2. Navigate to `/` (home page)
|
||||
3. Verify:
|
||||
- 6 tiles display in a 3×2 grid on desktop
|
||||
- Tiles collapse to 2-column on tablet/mobile (no single-column fallback)
|
||||
- Each tile has an icon (coloured), label (medium contrast), and value (high contrast)
|
||||
- Tiles align properly at different viewport widths
|
||||
4. Test with missing fields (e.g. `NETWORK_RADIO_CONFIG="EU/UK Narrow,,,,22dBm"`) — verify only 2 tiles render
|
||||
5. Test with no radio config (empty/null) — verify the entire Network Info panel still renders gracefully (empty body or component not shown)
|
||||
|
||||
### Automated Tests
|
||||
|
||||
```bash
|
||||
# Web-specific tests (render logic)
|
||||
pytest tests/test_web/ -v
|
||||
|
||||
# Quality checks
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
### Cross-Theme Testing
|
||||
|
||||
- Toggle between dark and light themes via the navbar theme switcher
|
||||
- Verify icon colour adjusts per theme (via `var(--color-radio)` with light/dark variants in `app.css`)
|
||||
- Verify label contrast and value contrast are appropriate on both themes
|
||||
|
||||
---
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| 6 tiles make panel taller than current list | The current list already shows up to 6 rows (one per param). A 3×2 grid is comparable in height. The tile format adds small icon spacing overhead but the grid layout is more space-efficient than 6 stacked rows. |
|
||||
| Icons may not be immediately intuitive | Use standard metaphor: waveform for frequency, bolt for power, shield for coding rate. Labels are always present below icons. |
|
||||
| New SVG icons bloat `icons.js` | Each icon function is ~5–7 lines. 5 new icons ≈ 30 lines. Acceptable. |
|
||||
| Tile border colour may clash with theme | Use `border-base-content/10` which adapts to theme via DaisyUI. |
|
||||
| Responsive layout may break at edge cases | Test at 320px, 375px, 768px, 1024px, 1440px widths. The `grid-cols-2 md:grid-cols-3` pattern keeps a minimum of 2 columns at all sizes, which works well for 6 tiles. |
|
||||
@@ -0,0 +1,45 @@
|
||||
# Tasks: Radio Info Tile Display
|
||||
|
||||
**Plan**: [plan.md](plan.md)
|
||||
**Date**: 2026-05-06
|
||||
|
||||
---
|
||||
|
||||
## Implementation
|
||||
|
||||
- [ ] 1. **Add 5 radio icon functions to `icons.js`**
|
||||
- `iconFrequency` — waveform/sine wave SVG
|
||||
- `iconBandwidth` — horizontal sliders SVG
|
||||
- `iconSpreadingFactor` — expanding arrows SVG
|
||||
- `iconCodingRate` — shield-with-check SVG
|
||||
- `iconTxPower` — bolt/lightning SVG
|
||||
- Each follows existing pattern: exported function with `(cls = 'h-5 w-5')` param, Heroicons outline style (24×24 viewBox, `stroke="currentColor"`, `stroke-width="2"`)
|
||||
|
||||
- [ ] 2. **Replace `renderRadioConfig()` with `renderRadioTiles()` in `home.js`**
|
||||
- Remove old `renderRadioConfig()` function (lines 11–28)
|
||||
- Add new `renderRadioTiles(rc)` that maps `RadioConfig` fields to tile definitions, filters nulls, returns a `grid grid-cols-2 md:grid-cols-3 gap-3` wrapper with tile `div`s
|
||||
- Each tile: icon (`radio-tile-icon w-6 h-6`), label (`text-xs opacity-70`), value (`text-sm font-semibold`)
|
||||
- Update panel container (line ~204): replace `space-y-2` with the grid wrapper
|
||||
|
||||
- [ ] 3. **Update icon imports in `home.js`**
|
||||
- Add `iconSettings`, `iconFrequency`, `iconBandwidth`, `iconSpreadingFactor`, `iconCodingRate`, `iconTxPower` to the import block (lines 6–9)
|
||||
|
||||
- [ ] 4. **Add `--color-radio` CSS to `app.css`**
|
||||
- Add `--color-radio: oklch(0.75 0.15 210)` to the existing `:root` block (dark theme)
|
||||
- Add `--color-radio: oklch(0.55 0.15 210)` to the existing `[data-theme="light"]` block
|
||||
- Add `.radio-tile-icon { color: var(--color-radio); }` rule
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] 5. **Run quality checks**
|
||||
- `pre-commit run --all-files`
|
||||
|
||||
- [ ] 6. **Run web tests**
|
||||
- `pytest tests/test_web/ -v`
|
||||
|
||||
- [ ] 7. **Manual visual check**
|
||||
- Start with `NETWORK_RADIO_CONFIG="EU/UK Narrow,869.618MHz,62.5kHz,8,8,22dBm"`
|
||||
- Verify 3×2 grid on desktop, 2-column on mobile
|
||||
- Verify icon colour (cyan) in both dark and light themes
|
||||
- Test with missing fields: `NETWORK_RADIO_CONFIG="EU/UK Narrow,,,,22dBm"` → 2 tiles
|
||||
- Test with no config: Network Info panel handles empty gracefully
|
||||
@@ -26,6 +26,7 @@
|
||||
--color-map: oklch(0.8471 0.199 83.87); /* yellow (matches btn-warning) */
|
||||
--color-members: oklch(0.72 0.17 50); /* orange */
|
||||
--color-neutral: oklch(0.3 0.01 250); /* subtle dark grey */
|
||||
--color-radio: oklch(0.75 0.15 210); /* cyan — radio tile icons */
|
||||
}
|
||||
|
||||
/* Light mode: darker section colors for contrast on light backgrounds */
|
||||
@@ -37,6 +38,7 @@
|
||||
--color-map: oklch(0.58 0.16 45);
|
||||
--color-members: oklch(0.55 0.18 25);
|
||||
--color-neutral: oklch(0.85 0.01 250);
|
||||
--color-radio: oklch(0.55 0.15 210);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
@@ -112,6 +114,10 @@
|
||||
background-color: color-mix(in oklch, var(--panel-color, transparent) 10%, var(--color-base-100));
|
||||
}
|
||||
|
||||
.radio-tile-icon {
|
||||
color: var(--color-radio);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Scrollbar Styling
|
||||
========================================================================== */
|
||||
|
||||
@@ -133,3 +133,23 @@ export function iconPause(cls = 'w-4 h-4') {
|
||||
export function iconPlay(cls = 'w-4 h-4') {
|
||||
return html`<svg xmlns="http://www.w3.org/2000/svg" class=${cls} viewBox="0 0 20 20" fill="currentColor"><path d="M6.3 2.84A1.5 1.5 0 0 0 4 4.11v11.78a1.5 1.5 0 0 0 2.3 1.27l9.344-5.891a1.5 1.5 0 0 0 0-2.538L6.3 2.84Z" /></svg>`;
|
||||
}
|
||||
|
||||
export function iconFrequency(cls = 'h-5 w-5') {
|
||||
return html`<svg xmlns="http://www.w3.org/2000/svg" class=${cls} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2 12C3.333 4 6.667 4 8 12c1.333 8 4.667 8 6 0 1.333-8 4.667-8 6 0" /></svg>`;
|
||||
}
|
||||
|
||||
export function iconBandwidth(cls = 'h-5 w-5') {
|
||||
return html`<svg xmlns="http://www.w3.org/2000/svg" class=${cls} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" /></svg>`;
|
||||
}
|
||||
|
||||
export function iconSpreadingFactor(cls = 'h-5 w-5') {
|
||||
return html`<svg xmlns="http://www.w3.org/2000/svg" class=${cls} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" /></svg>`;
|
||||
}
|
||||
|
||||
export function iconCodingRate(cls = 'h-5 w-5') {
|
||||
return html`<svg xmlns="http://www.w3.org/2000/svg" class=${cls} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" /></svg>`;
|
||||
}
|
||||
|
||||
export function iconTxPower(cls = 'h-5 w-5') {
|
||||
return html`<svg xmlns="http://www.w3.org/2000/svg" class=${cls} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" /></svg>`;
|
||||
}
|
||||
|
||||
@@ -6,25 +6,31 @@ import {
|
||||
import {
|
||||
iconDashboard, iconNodes, iconAdvertisements, iconMessages, iconMembers, iconMap,
|
||||
iconPage, iconInfo, iconChart, iconGlobe, iconGithub,
|
||||
iconSettings, iconFrequency, iconBandwidth, iconSpreadingFactor, iconCodingRate, iconTxPower,
|
||||
} from '../icons.js';
|
||||
|
||||
function renderRadioConfig(rc) {
|
||||
function renderRadioTiles(rc) {
|
||||
if (!rc) return nothing;
|
||||
const fields = [
|
||||
[t('links.profile'), rc.profile],
|
||||
[t('home.frequency'), rc.frequency],
|
||||
[t('home.bandwidth'), rc.bandwidth],
|
||||
[t('home.spreading_factor'), rc.spreading_factor],
|
||||
[t('home.coding_rate'), rc.coding_rate],
|
||||
[t('home.tx_power'), rc.tx_power],
|
||||
const tiles = [
|
||||
{ icon: iconSettings, label: t('links.profile'), value: rc.profile },
|
||||
{ icon: iconFrequency, label: t('home.frequency'), value: rc.frequency },
|
||||
{ icon: iconBandwidth, label: t('home.bandwidth'), value: rc.bandwidth },
|
||||
{ icon: iconSpreadingFactor, label: t('home.spreading_factor'), value: rc.spreading_factor },
|
||||
{ icon: iconCodingRate, label: t('home.coding_rate'), value: rc.coding_rate },
|
||||
{ icon: iconTxPower, label: t('home.tx_power'), value: rc.tx_power },
|
||||
];
|
||||
return fields
|
||||
.filter(([, v]) => v)
|
||||
.map(([label, value]) => html`
|
||||
<div class="flex justify-between">
|
||||
<span class="opacity-70">${label}:</span>
|
||||
<span class="font-mono">${String(value)}</span>
|
||||
</div>`);
|
||||
const visible = tiles.filter(t => t.value);
|
||||
if (visible.length === 0) return nothing;
|
||||
return html`
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
${visible.map(({ icon, label, value }) => html`
|
||||
<div class="flex flex-col items-center justify-center gap-1.5 p-3
|
||||
border border-base-content/10 rounded-box text-center">
|
||||
<span class="radio-tile-icon w-6 h-6">${icon('w-full h-full')}</span>
|
||||
<span class="text-xs opacity-70 leading-tight">${label}</span>
|
||||
<span class="text-sm font-semibold leading-tight">${String(value)}</span>
|
||||
</div>`)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderNavCard({ href, icon, label, colorVar }) {
|
||||
@@ -208,8 +214,8 @@ export async function render(container, params, router) {
|
||||
${iconInfo('h-6 w-6')}
|
||||
${t('home.network_info')}
|
||||
</h2>
|
||||
<div class="space-y-2">
|
||||
${renderRadioConfig(rc)}
|
||||
<div class="mt-2">
|
||||
${renderRadioTiles(rc)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user