Skip to content

Commit eb6a1d3

Browse files
stevekwon211claude
andcommitted
feat(blocks): Team + Profile + Stats — v0.23.0
shadcn's signature strength is blocks (full drop-in screens). We had 5; ship three more, composed from existing components (no new primitives, low risk): - Team — members list with avatars, roles, and an Invite action. - Profile — account-settings form (avatar + name/email/bio + save/cancel). - Stats — analytics overview: 4 metric cards + traffic-by-source bars. Registered as registry:block with proper requires; usage snippets verified by the docs-accuracy test (50/50). PreviewHost cases + showcase build.rs wired. Landing copy and ROADMAP updated (8 blocks total; Planned v0.23 Menu family renumbered to v0.24). Cache-bust 0.23.0. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e7fa6ac commit eb6a1d3

15 files changed

Lines changed: 343 additions & 22 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
[Live docs](https://stevekwon211.github.io/slintcn/docs/) · [Live demo](https://stevekwon211.github.io/slintcn) · [npm](https://www.npmjs.com/package/slintcn)
1010

11-
39 UI components, 5 installable blocks, a theme system, and a static registry you can host yourself.
11+
39 UI components, 8 installable blocks, a theme system, and a static registry you can host yourself.
1212

1313
<p align="center">
1414
<img src="docs/img/snapshots/section-6-dashboard.png" alt="slintcn dashboard components" width="31%">
@@ -79,6 +79,7 @@ node /path/to/slintcn/bin/slintcn.mjs add button card input dialog
7979
| **v0.21** | **Adoption W3** — docs API section (variants/sizes auto-derived) + per-item a11y/behavior contract (`a11y.json`, surfaced in docs + `export`) ||
8080
| **v0.22** | **Adoption W4** (from Zero adoption testing) — unified add/diff/export pipeline (diff+export now apply external-enums), adoption flags persist to slintcn.json, per-file `routes` (route overlay panels out of `componentsDir`) ||
8181
| **v0.22.1** | **Responsive + polish** — mobile nav drawer for the docs, demo fills the window (no letterbox, no resize flicker), Popover/ContextMenu layout fix, Firefox first-click focus ||
82+
| **v0.23** | **Blocks expansion** — Team (members + roles), Profile (account form), Stats (metrics + traffic bars) → 8 blocks total ||
8283
| **v1.0** | Game HUD registry expansion — hotbar, reticle, full keycap hints | later |
8384

8485
SaaS-first is a **wedge**, not a ceiling. Once tokens + motion + hover semantics

docs/ROADMAP.md

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,40 @@ Lead with the menu family (we already have the overlay infra) and the app-shell
1414
primitives (they serve real adopters like the Zero desktop app). Charts are a
1515
heavy, separate R&D track; the Game/HUD layer is the long-term differentiator.
1616

17-
### v0.23 — Menu family (overlay expansion)
17+
### v0.24 — Menu family (overlay expansion)
1818
- **Dropdown Menu, Menubar, Navigation Menu, Hover Card.**
1919
- Why: the highest-frequency missing primitives — every toolbar / profile menu.
2020
- Reuse: `PopupWindow` + the Popover/ContextMenu/Select patterns (close-on-
2121
click-outside, cursor/anchor positioning, arrow-key nav).
2222
- Risk: nested submenus (Menubar) need careful PopupWindow stacking + focus.
2323

24-
### v0.24 — Command & Combobox (search / filter)
24+
### v0.25 — Command & Combobox (search / filter)
2525
- **Combobox** (searchable Select), **Command** (⌘K palette: groups + filter).
2626
- Why: ⌘K is table-stakes in modern tools; Combobox unblocks large option sets.
2727
- Reuse: Select trigger + PopupWindow + Input for the query; filter a VecModel.
2828
- Risk: result highlight + keyboard nav; the global ⌘K shortcut is consumer-side.
2929

30-
### v0.25 — Data Table
30+
### v0.26 — Data Table
3131
- Sortable / filterable / paginated / row-selectable table (extends `Table`).
3232
- Why: the single biggest "complex" gap; most-requested.
3333
- Reuse: Table + Checkbox (select) + Pagination + Input (filter) + Button (sort).
3434
- Risk: column sizing + virtualized scroll for large data (ScrollArea). Own wave.
3535

36-
### v0.26 — Date & Calendar
36+
### v0.27 — Date & Calendar
3737
- **Calendar** (month grid + keyboard), **Date Picker** (Calendar in a Popover),
3838
basic range select.
3939
- Why: forms / scheduling.
4040
- Risk: date math in Rust glue, locale / first-day-of-week, range selection.
4141

42-
### v0.27 — App-shell primitives
42+
### v0.28 — App-shell primitives
4343
- **Sidebar** (collapsible app sidebar), **Resizable** (split panes), **Drawer**
4444
(sheet variant).
4545
- Why: makes slintcn viable for full desktop app shells — directly serves
4646
adopters like the Zero desktop app.
4747
- Reuse: Sheet (Drawer), ScrollArea, layout primitives; Resizable = TouchArea
4848
drag + width state.
4949

50-
### v0.28 — Catalog round-out (small gaps)
50+
### v0.29 — Catalog round-out (small gaps)
5151
- Collapsible, Aspect Ratio, Input OTP, Spinner, Carousel, Button Group,
5252
Empty / Field / Item, Native Select, Kbd (alias Keycap).
5353
- Why: parity polish; each is small.
@@ -77,7 +77,22 @@ heavy, separate R&D track; the Game/HUD layer is the long-term differentiator.
7777
7878
---
7979

80-
## v0.22.1 — responsive site + polish (current)
80+
## v0.23 — blocks expansion (current)
81+
82+
shadcn's signature strength is **Blocks** — drop-in full screens. We had 5;
83+
ship three more, composed from existing components (no new primitives):
84+
85+
- [x] **Team** — members list with avatars, roles, and an Invite action
86+
(Card + Avatar + Badge + Button).
87+
- [x] **Profile** — account-settings form: avatar, name, email, bio, save/cancel
88+
(Card + Avatar + Input + Textarea + Button).
89+
- [x] **Stats** — analytics overview: four metric cards + traffic-by-source
90+
bars (Card + Label + Badge + Progress; bars stand in until charts ship).
91+
92+
8 blocks total. Each registered + previewed in docs (`/docs/team`, `/profile`,
93+
`/stats`) + usage snippets verified by the docs-accuracy test.
94+
95+
## v0.22.1 — responsive site + polish
8196

8297
- [x] **Docs mobile nav** — off-canvas drawer (☰ → sidebar over a scrim; closes on
8398
scrim/Esc/select; body scroll lock). Restores component navigation on phones.

examples/showcase/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/showcase/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "slintcn-showcase"
3-
version = "0.22.1"
3+
version = "0.23.0"
44
edition = "2021"
55
publish = false
66

examples/showcase/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ fn main() {
2121
"text", "keycap", "hud-pill", "slot-tile",
2222
"scroll-area", "popover", "context-menu",
2323
"sign-in", "login", "pricing", "dashboard", "settings",
24+
"team", "profile", "stats",
2425
])
2526
.status()
2627
.expect("failed to invoke `node` for the slintcn CLI (need Node 20+ on PATH)");

examples/showcase/ui/app_window.slint

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ import { Login } from "slintcn/blocks/login.slint
5858
import { Pricing } from "slintcn/blocks/pricing.slint";
5959
import { Dashboard as DashboardBlock } from "slintcn/blocks/dashboard.slint";
6060
import { Settings as SettingsBlock } from "slintcn/blocks/settings.slint";
61+
import { Team } from "slintcn/blocks/team.slint";
62+
import { Profile } from "slintcn/blocks/profile.slint";
63+
import { Stats } from "slintcn/blocks/stats.slint";
6164

6265
// Sidebar nav item — local helper, not part of the registry.
6366
component NavItem inherits Rectangle {
@@ -251,6 +254,9 @@ component PreviewHost inherits Rectangle {
251254
if root.name == "pricing": Pricing { }
252255
if root.name == "dashboard": Rectangle { width: 760px; DashboardBlock { } }
253256
if root.name == "settings": Rectangle { width: 620px; SettingsBlock { } }
257+
if root.name == "team": Team { }
258+
if root.name == "profile": Profile { }
259+
if root.name == "stats": Rectangle { width: 760px; Stats { } }
254260
}
255261
}
256262

@@ -324,7 +330,7 @@ export component AppWindow inherits Window {
324330
font-weight: Tokens.typography-weight-semibold;
325331
}
326332
Badge {
327-
text: "v0.22";
333+
text: "v0.23";
328334
variant: BadgeVariant.secondary;
329335
size: BadgeSize.sm;
330336
}
@@ -532,7 +538,7 @@ export component AppWindow inherits Window {
532538
spacing: 8px;
533539
alignment: start;
534540
Label { text: "Sign up for the beta"; variant: LabelVariant.default; }
535-
Badge { text: "v0.22"; variant: BadgeVariant.secondary; size: BadgeSize.sm; }
541+
Badge { text: "v0.23"; variant: BadgeVariant.secondary; size: BadgeSize.sm; }
536542
}
537543

538544
VerticalLayout {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "slintcn",
3-
"version": "0.22.1",
3+
"version": "0.23.0",
44
"description": "Beautiful copy-paste Slint components — shadcn for native UI",
55
"keywords": [
66
"slint",
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Tokens } from "../theme/tokens.slint";
2+
import { Card, CardVariant } from "../components/card.slint";
3+
import { Avatar } from "../components/avatar.slint";
4+
import { Input } from "../components/input.slint";
5+
import { Textarea } from "../components/textarea.slint";
6+
import { Label, LabelVariant } from "../components/label.slint";
7+
import { Button, ButtonVariant } from "../components/button.slint";
8+
import { Separator, SeparatorOrientation } from "../components/separator.slint";
9+
10+
// Profile / account-settings block — avatar + name/email/bio fields with
11+
// Save / Cancel. Built from Card + Avatar + Input + Textarea + Button. Bind the
12+
// fields and handle `save()` / `cancel()`.
13+
export component Profile inherits Rectangle {
14+
in-out property <string> display-name: "Sofia Davis";
15+
in-out property <string> email: "sofia@acme.dev";
16+
in-out property <string> bio: "Product designer. Building tools for makers.";
17+
callback save();
18+
callback cancel();
19+
20+
background: transparent;
21+
22+
Card {
23+
variant: CardVariant.solid;
24+
width: 480px;
25+
VerticalLayout {
26+
padding: parent.padding-l;
27+
spacing: Tokens.spacing-lg;
28+
29+
VerticalLayout {
30+
spacing: 4px;
31+
Text {
32+
text: "Profile";
33+
color: Tokens.color-foreground;
34+
font-size: Tokens.typography-title-size;
35+
font-weight: Tokens.typography-weight-semibold;
36+
}
37+
Label { text: "How others see you across the app."; variant: LabelVariant.muted; }
38+
}
39+
40+
HorizontalLayout {
41+
spacing: 16px;
42+
VerticalLayout { alignment: center; Avatar { fallback: "SD"; size: 56px; } }
43+
VerticalLayout {
44+
horizontal-stretch: 1;
45+
alignment: center;
46+
spacing: 8px;
47+
Button { text: "Change avatar"; variant: ButtonVariant.outline; }
48+
Label { text: "JPG or PNG, up to 2 MB."; variant: LabelVariant.muted; }
49+
}
50+
}
51+
52+
Separator { orientation: SeparatorOrientation.horizontal; }
53+
54+
VerticalLayout {
55+
spacing: 6px;
56+
Label { text: "Name"; }
57+
Input { placeholder: "Your name"; text <=> root.display-name; }
58+
}
59+
VerticalLayout {
60+
spacing: 6px;
61+
Label { text: "Email"; }
62+
Input { placeholder: "you@example.com"; text <=> root.email; }
63+
}
64+
VerticalLayout {
65+
spacing: 6px;
66+
Label { text: "Bio"; }
67+
Textarea { placeholder: "A short bio…"; text <=> root.bio; }
68+
}
69+
70+
HorizontalLayout {
71+
spacing: Tokens.spacing-sm;
72+
VerticalLayout { horizontal-stretch: 1; }
73+
Button { text: "Cancel"; variant: ButtonVariant.outline; clicked => { root.cancel(); } }
74+
Button { text: "Save changes"; variant: ButtonVariant.default; clicked => { root.save(); } }
75+
}
76+
}
77+
}
78+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Tokens } from "../theme/tokens.slint";
2+
import { Card, CardVariant } from "../components/card.slint";
3+
import { Label, LabelVariant } from "../components/label.slint";
4+
import { Badge, BadgeVariant, BadgeSize } from "../components/badge.slint";
5+
import { Progress } from "../components/progress.slint";
6+
7+
// Analytics-overview block — four metric cards over a "traffic by source"
8+
// breakdown drawn with Progress bars (slintcn has no chart primitive yet, so
9+
// bars stand in). Built from Card + Label + Badge + Progress.
10+
export struct StatMetric { label: string, value: string, delta: string, up: bool }
11+
export struct TrafficSource { label: string, percent: float, value: string }
12+
13+
export component Stats inherits Rectangle {
14+
in property <[StatMetric]> metrics: [
15+
{ label: "Revenue", value: "$ 42,580", delta: "+12.4%", up: true },
16+
{ label: "Subscriptions", value: "2,340", delta: "+8.1%", up: true },
17+
{ label: "Active now", value: "1,284", delta: "+3.1%", up: true },
18+
{ label: "Churn", value: "1.8%", delta: "−0.4%", up: false },
19+
];
20+
in property <[TrafficSource]> sources: [
21+
{ label: "Direct", percent: 48, value: "12,480" },
22+
{ label: "Organic", percent: 31, value: "8,060" },
23+
{ label: "Referral", percent: 14, value: "3,640" },
24+
{ label: "Social", percent: 7, value: "1,820" },
25+
];
26+
27+
background: transparent;
28+
29+
VerticalLayout {
30+
spacing: 16px;
31+
alignment: start;
32+
33+
HorizontalLayout {
34+
spacing: 16px;
35+
for m in root.metrics: Card {
36+
variant: CardVariant.raised;
37+
width: 24%;
38+
height: 116px;
39+
VerticalLayout {
40+
padding: parent.padding-l;
41+
spacing: 6px;
42+
alignment: start;
43+
Label { text: m.label; variant: LabelVariant.muted; }
44+
Text {
45+
text: m.value;
46+
color: Tokens.color-foreground;
47+
font-size: 24px;
48+
font-weight: Tokens.typography-weight-semibold;
49+
}
50+
Badge {
51+
text: m.delta;
52+
variant: m.up ? BadgeVariant.default : BadgeVariant.destructive;
53+
size: BadgeSize.sm;
54+
}
55+
}
56+
}
57+
}
58+
59+
Card {
60+
variant: CardVariant.solid;
61+
VerticalLayout {
62+
padding: parent.padding-l;
63+
spacing: Tokens.spacing-md;
64+
Text {
65+
text: "Traffic by source";
66+
color: Tokens.color-foreground;
67+
font-size: Tokens.typography-title-size;
68+
font-weight: Tokens.typography-weight-semibold;
69+
}
70+
for s in root.sources: HorizontalLayout {
71+
spacing: 12px;
72+
VerticalLayout {
73+
width: 84px;
74+
alignment: center;
75+
Label { text: s.label; }
76+
}
77+
VerticalLayout {
78+
horizontal-stretch: 1;
79+
alignment: center;
80+
Progress { value: s.percent; }
81+
}
82+
VerticalLayout {
83+
width: 64px;
84+
alignment: center;
85+
Text {
86+
text: s.value;
87+
color: Tokens.color-muted-foreground;
88+
font-size: Tokens.typography-body-sm-size;
89+
horizontal-alignment: right;
90+
}
91+
}
92+
}
93+
}
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)