Skip to content

Commit e12f67b

Browse files
Ryanmello07claude
andcommitted
r1: the shared Wave-2 component kit in UrComponents
The spec §12 inventory the four Wave-2 destination agents build against, so Network/Earnings/Account/Settings come out of one kit rather than four reinventions. Each realizes a component in the brand tokens/faces (styles pulled from App.xaml by key, so the kit tracks the type ramp), returns the live parts a page updates, and carries its own accessibility: MakePageHeader Title (display face) + optional Body description MakeMetricCard boxed stat tile -> {root,label,value} MakeSettingsCard glyph + title/description + a trailing control slot MakeCopyField caption + (masked) value + a real clipboard copy button MakePlanUsageCard plan name + a UsageBar host + legend EmptyState (MakeEmptyState/Card), StatusField and Snackbar already shipped here; ConnectionHero is realized by ConnectPage's ControlsCard (Home-only) and not duplicated. Documented briefly in the header with a one-line contract each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PT7KcWCPKfFwQUc7SM3oZY
1 parent 41a6653 commit e12f67b

2 files changed

Lines changed: 263 additions & 0 deletions

File tree

app/src/App/UrComponents.cpp

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <winrt/Microsoft.UI.Xaml.Automation.Peers.h>
99
#include <winrt/Microsoft.UI.Xaml.Automation.h>
1010
#include <winrt/Microsoft.UI.Xaml.Media.h>
11+
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
1112
#include <winrt/Windows.UI.Text.h>
1213

1314
#include "Log.h"
@@ -181,6 +182,194 @@ FrameworkElement MakeEmptyStateCard(winrt::hstring const& glyph, winrt::hstring
181182
return card;
182183
}
183184

185+
// ---- the Wave-2 component kit (spec §12) -----------------------------------
186+
187+
FrameworkElement MakePageHeader(winrt::hstring const& title,
188+
winrt::hstring const& description) {
189+
StackPanel column;
190+
column.Spacing(4);
191+
column.VerticalAlignment(VerticalAlignment::Top);
192+
193+
TextBlock titleBlock;
194+
titleBlock.Text(title);
195+
if (auto style = StyleByKey(L"UrTitleTextStyle")) titleBlock.Style(style);
196+
column.Children().Append(titleBlock);
197+
198+
if (!description.empty()) {
199+
TextBlock desc;
200+
desc.Text(description);
201+
if (auto style = StyleByKey(L"UrBodyTextStyle")) desc.Style(style);
202+
desc.Foreground(urnw::colors::MutedBrush());
203+
desc.TextWrapping(TextWrapping::Wrap);
204+
// ~60ch reading measure, per the spec's page-description rule
205+
desc.MaxWidth(560);
206+
desc.HorizontalAlignment(HorizontalAlignment::Left);
207+
column.Children().Append(desc);
208+
}
209+
return column;
210+
}
211+
212+
MetricCard MakeMetricCard(winrt::hstring const& label, winrt::hstring const& value) {
213+
MetricCard card;
214+
card.root = Controls::Border();
215+
if (auto style = StyleByKey(L"UrStatTileStyle")) card.root.Style(style);
216+
217+
StackPanel column;
218+
card.label = TextBlock();
219+
card.label.Text(label);
220+
if (auto style = StyleByKey(L"UrStatLabelStyle")) card.label.Style(style);
221+
column.Children().Append(card.label);
222+
223+
card.value = TextBlock();
224+
card.value.Text(value);
225+
if (auto style = StyleByKey(L"UrStatValueStyle")) card.value.Style(style);
226+
column.Children().Append(card.value);
227+
228+
card.root.Child(column);
229+
return card;
230+
}
231+
232+
SettingsCard MakeSettingsCard(winrt::hstring const& glyph, winrt::hstring const& title,
233+
winrt::hstring const& description) {
234+
SettingsCard card;
235+
card.root = Controls::Border();
236+
if (auto style = StyleByKey(L"UrCardStyle")) card.root.Style(style);
237+
238+
Grid row;
239+
row.ColumnSpacing(12);
240+
{ Controls::ColumnDefinition c; c.Width(GridLengthHelper::Auto()); row.ColumnDefinitions().Append(c); }
241+
{ Controls::ColumnDefinition c; c.Width(GridLengthHelper::FromValueAndType(1, GridUnitType::Star)); row.ColumnDefinitions().Append(c); }
242+
{ Controls::ColumnDefinition c; c.Width(GridLengthHelper::Auto()); row.ColumnDefinitions().Append(c); }
243+
244+
FontIcon icon;
245+
icon.FontFamily(IconFont());
246+
icon.Glyph(glyph);
247+
icon.FontSize(20);
248+
icon.Foreground(urnw::colors::MutedBrush());
249+
icon.VerticalAlignment(VerticalAlignment::Center);
250+
Automation::AutomationProperties::SetAccessibilityView(
251+
icon, Automation::Peers::AccessibilityView::Raw);
252+
Grid::SetColumn(icon, 0);
253+
row.Children().Append(icon);
254+
255+
StackPanel text;
256+
text.Spacing(2);
257+
text.VerticalAlignment(VerticalAlignment::Center);
258+
Grid::SetColumn(text, 1);
259+
card.title = TextBlock();
260+
card.title.Text(title);
261+
if (auto style = StyleByKey(L"UrBodyTextStyle")) card.title.Style(style);
262+
card.title.TextWrapping(TextWrapping::Wrap);
263+
text.Children().Append(card.title);
264+
card.description = TextBlock();
265+
card.description.Text(description);
266+
if (auto style = StyleByKey(L"UrCaptionTextStyle")) card.description.Style(style);
267+
card.description.TextWrapping(TextWrapping::Wrap);
268+
// an empty description must not spend the panel's spacing on nothing
269+
card.description.Visibility(description.empty() ? Visibility::Collapsed
270+
: Visibility::Visible);
271+
text.Children().Append(card.description);
272+
row.Children().Append(text);
273+
274+
card.trailing = Grid();
275+
card.trailing.HorizontalAlignment(HorizontalAlignment::Right);
276+
card.trailing.VerticalAlignment(VerticalAlignment::Center);
277+
Grid::SetColumn(card.trailing, 2);
278+
row.Children().Append(card.trailing);
279+
280+
card.root.Child(row);
281+
return card;
282+
}
283+
284+
CopyField MakeCopyField(winrt::hstring const& label, winrt::hstring const& value,
285+
bool masked) {
286+
CopyField field;
287+
StackPanel column;
288+
column.Spacing(2);
289+
290+
TextBlock caption;
291+
caption.Text(label);
292+
if (auto style = StyleByKey(L"UrLabelStyle")) caption.Style(style);
293+
column.Children().Append(caption);
294+
295+
Grid row;
296+
row.ColumnSpacing(8);
297+
{ Controls::ColumnDefinition c; c.Width(GridLengthHelper::FromValueAndType(1, GridUnitType::Star)); row.ColumnDefinitions().Append(c); }
298+
{ Controls::ColumnDefinition c; c.Width(GridLengthHelper::Auto()); row.ColumnDefinitions().Append(c); }
299+
300+
field.value = TextBlock();
301+
// masked DISPLAY only; the real value is captured by the copy button below
302+
field.value.Text(masked ? winrt::hstring{L"••••••••"} : value);
303+
if (auto style = StyleByKey(L"UrBodyTextStyle")) field.value.Style(style);
304+
field.value.TextTrimming(TextTrimming::CharacterEllipsis);
305+
field.value.VerticalAlignment(VerticalAlignment::Center);
306+
Grid::SetColumn(field.value, 0);
307+
row.Children().Append(field.value);
308+
309+
field.copy = Button();
310+
field.copy.Background(nullptr);
311+
field.copy.BorderThickness(ThicknessHelper::FromUniformLength(0));
312+
field.copy.Padding(ThicknessHelper::FromLengths(8, 4, 8, 4));
313+
field.copy.VerticalAlignment(VerticalAlignment::Center);
314+
FontIcon copyGlyph;
315+
copyGlyph.FontFamily(IconFont());
316+
copyGlyph.Glyph(L""); // Copy
317+
copyGlyph.FontSize(16);
318+
copyGlyph.Foreground(urnw::colors::MutedBrush());
319+
field.copy.Content(copyGlyph);
320+
// the button carries the action name a screen reader announces
321+
Automation::AutomationProperties::SetName(field.copy, winrt::hstring{L"Copy "} + label);
322+
// the full value is copied, never the mask
323+
field.copy.Click([value](auto const&, auto const&) {
324+
winrt::Windows::ApplicationModel::DataTransfer::DataPackage package;
325+
package.SetText(value);
326+
winrt::Windows::ApplicationModel::DataTransfer::Clipboard::SetContent(package);
327+
});
328+
Grid::SetColumn(field.copy, 1);
329+
row.Children().Append(field.copy);
330+
331+
column.Children().Append(row);
332+
field.value.VerticalAlignment(VerticalAlignment::Center);
333+
field.root = column;
334+
return field;
335+
}
336+
337+
PlanUsageCard MakePlanUsageCard(winrt::hstring const& planLabel,
338+
winrt::hstring const& planValue) {
339+
PlanUsageCard card;
340+
card.root = Controls::Border();
341+
if (auto style = StyleByKey(L"UrCardStyle")) card.root.Style(style);
342+
343+
StackPanel column;
344+
column.Spacing(8);
345+
346+
TextBlock caption;
347+
caption.Text(planLabel);
348+
if (auto style = StyleByKey(L"UrLabelStyle")) caption.Style(style);
349+
column.Children().Append(caption);
350+
351+
card.planValue = TextBlock();
352+
card.planValue.Text(planValue);
353+
if (auto style = StyleByKey(L"UrStatValueStyle")) card.planValue.Style(style);
354+
card.planValue.FontSize(22);
355+
column.Children().Append(card.planValue);
356+
357+
// the caller constructs a urnw::UsageBar into this host (the bar is not a XAML
358+
// control), and its legend into the panel below - the same wiring the connect
359+
// drawer and Account already do around their own hosts.
360+
card.usageBarHost = Grid();
361+
card.usageBarHost.Height(32);
362+
column.Children().Append(card.usageBarHost);
363+
364+
card.legend = StackPanel();
365+
card.legend.Orientation(Controls::Orientation::Horizontal);
366+
card.legend.Spacing(16);
367+
column.Children().Append(card.legend);
368+
369+
card.root.Child(column);
370+
return card;
371+
}
372+
184373
void ApplySupportingText(TextBlock const& line, winrt::hstring const& text,
185374
ValidationState state) {
186375
if (!line) return;

app/src/App/UrComponents.h

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,80 @@ winrt::Microsoft::UI::Xaml::Controls::Border MakeDivider();
110110
winrt::Microsoft::UI::Xaml::FrameworkElement MakeSectionHeader(winrt::hstring const& glyph,
111111
winrt::hstring const& text);
112112

113+
// ============================================================================
114+
// THE WAVE-2 COMPONENT KIT (R1, spec §12 inventory)
115+
//
116+
// Wave 1 owns the shell and Home; Wave 2 rebuilds Network / Earnings / Account /
117+
// Settings. These builders are the shared vocabulary those four agents build
118+
// against, so the four destinations come out of one kit rather than four
119+
// re-inventions. Each realizes a spec §12 component in the brand tokens/faces
120+
// (styles pulled from App.xaml by key, so the kit tracks the ramp), returns the
121+
// live parts a page must update, and carries its own accessibility.
122+
//
123+
// EmptyState (MakeEmptyState / MakeEmptyStateCard), CopyField's cousin
124+
// StatusField and the Snackbar already live above. ConnectionHero is realized by
125+
// ConnectPage's ControlsCard (the ConnectCanvas hero + status-leads layout Wave
126+
// 1 built); it is Home-only and not duplicated here.
127+
// ============================================================================
128+
129+
// PageHeader: a page Title in the brand display face + an optional one-line Body
130+
// description under it. Every Wave-2 destination opens with one.
131+
// contract: MakePageHeader(title[, description]) -> a top-aligned column.
132+
winrt::Microsoft::UI::Xaml::FrameworkElement MakePageHeader(
133+
winrt::hstring const& title, winrt::hstring const& description = {});
134+
135+
// MetricCard: a boxed stat tile - a muted caption over a big condensed value -
136+
// for the KPI rows on Earnings and Account. Returns its two live TextBlocks so
137+
// the page updates label/value without rebuilding the tile.
138+
// contract: MakeMetricCard(label[, value]).{root,label,value}
139+
struct MetricCard {
140+
winrt::Microsoft::UI::Xaml::Controls::Border root{nullptr};
141+
winrt::Microsoft::UI::Xaml::Controls::TextBlock label{nullptr};
142+
winrt::Microsoft::UI::Xaml::Controls::TextBlock value{nullptr};
143+
};
144+
MetricCard MakeMetricCard(winrt::hstring const& label, winrt::hstring const& value = {});
145+
146+
// SettingsCard: one settings row on a card surface - leading 20epx glyph, a
147+
// title with an optional one-line description, and a trailing slot the caller
148+
// drops a control (ToggleSwitch, ComboBox) or a chevron into. The trailing
149+
// control should point AutomationProperties.LabeledBy at `title`.
150+
// contract: MakeSettingsCard(glyph, title[, description]).{root,title,description,trailing}
151+
struct SettingsCard {
152+
winrt::Microsoft::UI::Xaml::Controls::Border root{nullptr};
153+
winrt::Microsoft::UI::Xaml::Controls::TextBlock title{nullptr};
154+
winrt::Microsoft::UI::Xaml::Controls::TextBlock description{nullptr};
155+
// append the control/chevron here; single-cell, right-aligned
156+
winrt::Microsoft::UI::Xaml::Controls::Grid trailing{nullptr};
157+
};
158+
SettingsCard MakeSettingsCard(winrt::hstring const& glyph, winrt::hstring const& title,
159+
winrt::hstring const& description = {});
160+
161+
// CopyField: a caption, a value (optionally masked for a secret/client id), and
162+
// a copy button that writes the FULL value to the clipboard with a Raw glyph.
163+
// The masked display never reaches the clipboard - the real value does.
164+
// contract: MakeCopyField(label, value[, masked]).{root,value,copy}
165+
struct CopyField {
166+
winrt::Microsoft::UI::Xaml::FrameworkElement root{nullptr};
167+
winrt::Microsoft::UI::Xaml::Controls::TextBlock value{nullptr};
168+
winrt::Microsoft::UI::Xaml::Controls::Button copy{nullptr};
169+
};
170+
CopyField MakeCopyField(winrt::hstring const& label, winrt::hstring const& value,
171+
bool masked = false);
172+
173+
// PlanUsageCard: the plan name + value, a host Grid for a UsageBar, and a legend
174+
// panel - the shape the connect drawer and Account both draw the subscription
175+
// with. Returns the parts a page wires a urnw::UsageBar into (the bar itself is
176+
// not a XAML control, so it stays the caller's to construct into usageBarHost).
177+
// contract: MakePlanUsageCard(planLabel[, planValue]).{root,planValue,usageBarHost,legend}
178+
struct PlanUsageCard {
179+
winrt::Microsoft::UI::Xaml::Controls::Border root{nullptr};
180+
winrt::Microsoft::UI::Xaml::Controls::TextBlock planValue{nullptr};
181+
winrt::Microsoft::UI::Xaml::Controls::Grid usageBarHost{nullptr};
182+
winrt::Microsoft::UI::Xaml::Controls::StackPanel legend{nullptr};
183+
};
184+
PlanUsageCard MakePlanUsageCard(winrt::hstring const& planLabel,
185+
winrt::hstring const& planValue = {});
186+
113187
// ---- the persistent status strip -------------------------------------------
114188
//
115189
// One field of the window's bottom status strip: an optional state dot, an

0 commit comments

Comments
 (0)