Skip to content

Commit b3398af

Browse files
stevekwon211claude
andcommitted
feat(form): v0.27.0 — Calendar + DatePicker
Two new form components. Slint 1.16 has no Date type, so the consumer owns the date math (pass `days-in-month` + `first-day-offset`, handle `prev-month` / `next-month` to roll your model). The components handle grid + selection + popup chrome. - Calendar — 6x7 month grid with header chevrons, S/M/T/W/T/F/S labels, day cells with hover / selected / out-of-month states. `day-selected(int)` callback, two-way `selected-day`. - DatePicker — @children trigger opens a Calendar in a PopupWindow; closes on selection or click-outside. 48 components total. Cache-bust 0.27.0; WASM rebuilt. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 10bb5e7 commit b3398af

15 files changed

Lines changed: 312 additions & 23 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-
46 UI components, 8 installable blocks, a theme system, and a static registry you can host yourself.
11+
48 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%">
@@ -86,6 +86,7 @@ node /path/to/slintcn/bin/slintcn.mjs add button card input dialog
8686
| **v0.25** | **Command & Combobox** — Combobox (searchable Select); Command (⌘K palette modal) → 45 components ||
8787
| **v0.25.1** | **Combobox + Command keyboard nav** — ↑/↓ move highlight, Enter selects, Esc closes (ancestor FocusScope catches keys bubbling from the search Input) ||
8888
| **v0.26** | **Data Table** — sortable headers + paginated rows + row clicks; consumer owns data slicing → 46 components ||
89+
| **v0.27** | **Calendar + Date Picker** — month grid (`Calendar`) + Popover-wrapped trigger (`DatePicker`); consumer owns date math → 48 components ||
8990
| **v1.0** | Game HUD registry expansion — hotbar, reticle, full keycap hints | later |
9091

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

docs/ROADMAP.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
# slintcn roadmap
22

3-
## v0.26 — Data Table (current)
3+
## v0.27 — Calendar + Date Picker (current)
4+
5+
Two new form components. Slint 1.16 has no `Date` type and no date arithmetic,
6+
so the **consumer owns the date math**: pass `days-in-month` + `first-day-
7+
offset` (0 = Sun) for the displayed month, and respond to `prev-month` /
8+
`next-month` callbacks by rolling your model. The components handle the grid
9+
+ selection + popup chrome.
10+
11+
- [x] `Calendar` — 6×7 month grid (header chevrons + S/M/T/W/T/F/S labels +
12+
day cells with hover / selected / out-of-month states), `day-selected`
13+
callback, two-way `selected-day`.
14+
- [x] `DatePicker``@children` trigger that opens a `Calendar` in a
15+
PopupWindow; closes on selection or click-outside.
16+
17+
## v0.26 — Data Table
418

519
A new `DataTable` component — clickable headers with sort indicators, body
620
rows (hover + click), and optional `Pagination` underneath. Filter/sort/page
@@ -91,12 +105,6 @@ Lead with the menu family (we already have the overlay infra) and the app-shell
91105
primitives (they serve real adopters like the Zero desktop app). Charts are a
92106
heavy, separate R&D track; the Game/HUD layer is the long-term differentiator.
93107

94-
### v0.27 — Date & Calendar
95-
- **Calendar** (month grid + keyboard), **Date Picker** (Calendar in a Popover),
96-
basic range select.
97-
- Why: forms / scheduling.
98-
- Risk: date math in Rust glue, locale / first-day-of-week, range selection.
99-
100108
### v0.28 — App-shell primitives
101109
- **Sidebar** (collapsible app sidebar), **Resizable** (split panes), **Drawer**
102110
(sheet variant).

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.26.0"
3+
version = "0.27.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
@@ -22,6 +22,7 @@ fn main() {
2222
"scroll-area", "popover", "context-menu",
2323
"dropdown-menu", "hover-card", "menubar", "navigation-menu",
2424
"combobox", "command", "data-table",
25+
"calendar", "date-picker",
2526
"sign-in", "login", "pricing", "dashboard", "settings",
2627
"team", "profile", "stats",
2728
])

examples/showcase/ui/app_window.slint

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ import { NavigationMenu, NavigationItem } from "slintcn/components/navigat
6060
import { Combobox, ComboboxItem } from "slintcn/components/combobox.slint";
6161
import { Command, CommandItem } from "slintcn/components/command.slint";
6262
import { DataTable, DataTableRow } from "slintcn/components/data-table.slint";
63+
import { Calendar } from "slintcn/components/calendar.slint";
64+
import { DatePicker } from "slintcn/components/date-picker.slint";
6365
import { SignIn } from "slintcn/blocks/sign-in.slint";
6466
import { Login } from "slintcn/blocks/login.slint";
6567
import { Pricing } from "slintcn/blocks/pricing.slint";
@@ -298,6 +300,17 @@ component PreviewHost inherits Rectangle {
298300
{ label: "Toggle theme", id: "app.theme", hint: "" },
299301
];
300302
}
303+
if root.name == "calendar": Calendar {
304+
month-label: "May 2026";
305+
days-in-month: 31;
306+
first-day-offset: 5;
307+
}
308+
if root.name == "date-picker": DatePicker {
309+
month-label: "May 2026";
310+
days-in-month: 31;
311+
first-day-offset: 5;
312+
Button { variant: ButtonVariant.outline; text: "Pick a date"; }
313+
}
301314
if root.name == "data-table": Rectangle {
302315
width: 760px;
303316
DataTable {
@@ -395,7 +408,7 @@ export component AppWindow inherits Window {
395408
font-weight: Tokens.typography-weight-semibold;
396409
}
397410
Badge {
398-
text: "v0.26";
411+
text: "v0.27";
399412
variant: BadgeVariant.secondary;
400413
size: BadgeSize.sm;
401414
}
@@ -603,7 +616,7 @@ export component AppWindow inherits Window {
603616
spacing: 8px;
604617
alignment: start;
605618
Label { text: "Sign up for the beta"; variant: LabelVariant.default; }
606-
Badge { text: "v0.26"; variant: BadgeVariant.secondary; size: BadgeSize.sm; }
619+
Badge { text: "v0.27"; variant: BadgeVariant.secondary; size: BadgeSize.sm; }
607620
}
608621

609622
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.26.0",
3+
"version": "0.27.0",
44
"description": "Beautiful copy-paste Slint components — shadcn for native UI",
55
"keywords": [
66
"slint",

registry/default/a11y.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,7 @@
2727
"hover-card": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": false },
2828
"combobox": { "focusable": true, "keyboard": ["text entry", "Arrow keys", "Enter"], "focusTrap": false, "escapeDismiss": true },
2929
"command": { "focusable": true, "keyboard": ["text entry", "Arrow keys", "Enter"], "focusTrap": false, "escapeDismiss": true },
30-
"data-table": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": false }
30+
"data-table": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": false },
31+
"calendar": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": false },
32+
"date-picker": { "focusable": false, "keyboard": [], "focusTrap": false, "escapeDismiss": true }
3133
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { Tokens } from "../theme/tokens.slint";
2+
import { Icon } from "icon.slint";
3+
import { LucidePaths } from "lucide-paths.slint";
4+
5+
// Month-grid calendar. Slint 1.16 has no Date type or date arithmetic, so the
6+
// consumer provides `days-in-month` + `first-day-offset` (0 = Sun … 6 = Sat)
7+
// computed from `year`/`month` in your model — the component renders the grid,
8+
// handles selection, and fires prev / next so you can flip the page.
9+
//
10+
// Wire it like:
11+
// Calendar {
12+
// month-label: "May 2026";
13+
// days-in-month: 31;
14+
// first-day-offset: 5; // 1 May 2026 is a Friday
15+
// selected-day <=> day;
16+
// day-selected(d) => { day = d }
17+
// prev-month => { /* roll your model back one month */ }
18+
// next-month => { /* roll your model forward one month */ }
19+
// }
20+
export component Calendar inherits Rectangle {
21+
// Header text, e.g. "May 2026". The component does no date formatting.
22+
in property <string> month-label;
23+
// Number of days in the displayed month — 28 / 29 / 30 / 31.
24+
in property <int> days-in-month: 30;
25+
// Day-of-week of the first day of the month (0 = Sun, 6 = Sat). Leading
26+
// empty cells render before day 1.
27+
in property <int> first-day-offset: 0;
28+
// Two-way; the selected day-of-month (1 … days-in-month), or -1 for none.
29+
in-out property <int> selected-day: -1;
30+
// Fired when the user clicks a valid day cell.
31+
callback day-selected(int);
32+
// Fired when the previous-month chevron is clicked.
33+
callback prev-month();
34+
// Fired when the next-month chevron is clicked.
35+
callback next-month();
36+
37+
background: transparent;
38+
preferred-width: layout.preferred-width;
39+
preferred-height: layout.preferred-height;
40+
41+
layout := VerticalLayout {
42+
padding: 12px;
43+
spacing: 8px;
44+
45+
// Header: ◀ Month Year ▶
46+
HorizontalLayout {
47+
spacing: 8px;
48+
// Prev-month chevron — same tap target as the next-month one.
49+
Rectangle {
50+
width: 28px;
51+
height: 28px;
52+
border-radius: Tokens.radius-sm;
53+
background: prev-touch.has-hover
54+
? Tokens.color-surface-1 : transparent;
55+
Icon {
56+
commands: LucidePaths.chevron-left;
57+
size: 16px;
58+
tint: Tokens.color-foreground;
59+
}
60+
prev-touch := TouchArea {
61+
mouse-cursor: pointer;
62+
clicked => { root.prev-month(); }
63+
}
64+
}
65+
VerticalLayout {
66+
horizontal-stretch: 1;
67+
alignment: center;
68+
Text {
69+
text: root.month-label;
70+
color: Tokens.color-foreground;
71+
font-size: Tokens.typography-body-size;
72+
font-weight: Tokens.typography-weight-semibold;
73+
horizontal-alignment: center;
74+
}
75+
}
76+
Rectangle {
77+
width: 28px;
78+
height: 28px;
79+
border-radius: Tokens.radius-sm;
80+
background: next-touch.has-hover
81+
? Tokens.color-surface-1 : transparent;
82+
Icon {
83+
commands: LucidePaths.chevron-right;
84+
size: 16px;
85+
tint: Tokens.color-foreground;
86+
}
87+
next-touch := TouchArea {
88+
mouse-cursor: pointer;
89+
clicked => { root.next-month(); }
90+
}
91+
}
92+
}
93+
94+
// Day-of-week labels — Sun-first; localize in the consumer.
95+
HorizontalLayout {
96+
spacing: 0;
97+
for label in ["S", "M", "T", "W", "T", "F", "S"]: Rectangle {
98+
horizontal-stretch: 1;
99+
height: 28px;
100+
Text {
101+
text: label;
102+
color: Tokens.color-muted-foreground;
103+
font-size: Tokens.typography-body-sm-size;
104+
horizontal-alignment: center;
105+
vertical-alignment: center;
106+
}
107+
}
108+
}
109+
110+
// 6-row × 7-col day grid. Each cell computes its day number from its
111+
// grid index minus the leading offset; cells outside the month render
112+
// muted (no day text, no click).
113+
for w in 6: HorizontalLayout {
114+
spacing: 0;
115+
for d in 7: Rectangle {
116+
property <int> cell-idx: w * 7 + d;
117+
property <int> day-num: cell-idx - root.first-day-offset + 1;
118+
property <bool> in-month: day-num >= 1 && day-num <= root.days-in-month;
119+
property <bool> selected: in-month && day-num == root.selected-day;
120+
horizontal-stretch: 1;
121+
height: 36px;
122+
border-radius: Tokens.radius-sm;
123+
background: selected
124+
? Tokens.color-foreground
125+
: (cell-touch.has-hover && in-month
126+
? Tokens.color-surface-1
127+
: transparent);
128+
Text {
129+
text: in-month ? day-num : "";
130+
color: selected
131+
? Tokens.color-background
132+
: (in-month
133+
? Tokens.color-foreground
134+
: Tokens.color-muted-foreground);
135+
font-size: Tokens.typography-body-sm-size;
136+
horizontal-alignment: center;
137+
vertical-alignment: center;
138+
}
139+
cell-touch := TouchArea {
140+
enabled: in-month;
141+
mouse-cursor: pointer;
142+
clicked => {
143+
root.selected-day = day-num;
144+
root.day-selected(day-num);
145+
}
146+
}
147+
animate background {
148+
duration: Tokens.motion-fast;
149+
easing: Tokens.motion-easing-standard;
150+
}
151+
}
152+
}
153+
}
154+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Tokens } from "../theme/tokens.slint";
2+
import { Calendar } from "calendar.slint";
3+
4+
// Date picker — click `@children` (typically a Button showing the selected
5+
// date) to open a Calendar popup. Like Calendar, dates are consumer-owned
6+
// (Slint 1.16 has no Date arithmetic), so wire the same year / month /
7+
// days-in-month / first-day-offset properties the Calendar needs.
8+
export component DatePicker inherits Rectangle {
9+
// Header text inside the popup (e.g. "May 2026").
10+
in property <string> month-label;
11+
// Number of days in the visible month.
12+
in property <int> days-in-month: 30;
13+
// Day-of-week of day 1 (0 = Sun … 6 = Sat).
14+
in property <int> first-day-offset: 0;
15+
// Two-way; selected day-of-month (1 … days-in-month), or -1 for none.
16+
in-out property <int> selected-day: -1;
17+
// Pixel width of the popup panel.
18+
in property <length> content-width: 296px;
19+
// Fired with the chosen day; the popup closes automatically.
20+
callback day-selected(int);
21+
// Forwarded from the inner Calendar header chevrons.
22+
callback prev-month();
23+
callback next-month();
24+
25+
background: transparent;
26+
// Expose the trigger's size so a parent layout allocates real space.
27+
preferred-width: trigger.preferred-width;
28+
preferred-height: trigger.preferred-height;
29+
30+
trigger := HorizontalLayout {
31+
@children
32+
}
33+
34+
touch := TouchArea {
35+
mouse-cursor: pointer;
36+
clicked => { popup.show(); }
37+
}
38+
39+
popup := PopupWindow {
40+
x: 0;
41+
y: root.height + 6px;
42+
width: root.content-width;
43+
close-policy: close-on-click-outside;
44+
45+
Rectangle {
46+
background: Tokens.color-card;
47+
border-color: Tokens.color-border-hairline;
48+
border-width: 1px;
49+
border-radius: Tokens.radius-lg;
50+
drop-shadow-blur: Tokens.shadow-e3-blur;
51+
drop-shadow-offset-y: Tokens.shadow-e3-offset-y;
52+
drop-shadow-color: Tokens.shadow-e3-color;
53+
height: cal.preferred-height;
54+
55+
cal := Calendar {
56+
month-label: root.month-label;
57+
days-in-month: root.days-in-month;
58+
first-day-offset: root.first-day-offset;
59+
selected-day <=> root.selected-day;
60+
day-selected(d) => {
61+
root.day-selected(d);
62+
popup.close();
63+
}
64+
prev-month => { root.prev-month(); }
65+
next-month => { root.next-month(); }
66+
}
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)