Skip to content

Commit 20450b0

Browse files
authored
Refactor widget catalog pages to generate from data (#12760)
- Moves the widget catalog at `/resource/widgets` to a Jaspr component - Generates all '/ui/widgets/<category>" pages from data and remove empty markdown files - Remove CatalogPageLayout Resolves #12739
1 parent 59109c3 commit 20450b0

23 files changed

+392
-486
lines changed

site/lib/main.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import 'src/components/tutorial/summary_card.dart';
2929
import 'src/components/tutorial/tutorial_outline.dart';
3030
import 'src/components/util/component_ref.dart';
3131
import 'src/extensions/registry.dart';
32-
import 'src/layouts/catalog_page_layout.dart';
3332
import 'src/layouts/doc_layout.dart';
3433
import 'src/layouts/toc_layout.dart';
3534
import 'src/loaders/data_processor.dart';
@@ -72,7 +71,7 @@ Component get _docsFlutterDevSite => ContentApp.custom(
7271
rawOutputPattern: _passThroughPattern,
7372
extensions: allNodeProcessingExtensions,
7473
components: _embeddableComponents,
75-
layouts: const [DocLayout(), TocLayout(), CatalogPageLayout()],
74+
layouts: const [DocLayout(), TocLayout()],
7675
theme: const ContentTheme.none(),
7776
secondaryOutputs: [
7877
const RobotsTxtOutput(),
@@ -113,6 +112,7 @@ List<CustomComponent> get _embeddableComponents => [
113112
const Stepper(),
114113
const WidgetCatalogCategories(),
115114
const TutorialOutline(),
115+
const WidgetCatalogGrid(),
116116
CustomComponent(
117117
pattern: RegExp('OSSelector', caseSensitive: false),
118118
builder: (_, _, _) => const OsSelector(),

site/lib/src/components/pages/widget_catalog.dart

Lines changed: 125 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import 'package:collection/collection.dart';
66
import 'package:jaspr/jaspr.dart';
77
import 'package:jaspr_content/jaspr_content.dart';
88

9+
import '../../markdown/markdown_parser.dart';
10+
import '../../models/widget_catalog_model.dart';
11+
import '../../util.dart';
12+
913
class WidgetCatalogCategories extends CustomComponentBase {
1014
const WidgetCatalogCategories();
1115

@@ -24,10 +28,10 @@ class WidgetCatalogCategories extends CustomComponentBase {
2428
{'catalog': {'index': final List<Object?> index}} =>
2529
index
2630
.cast<Map<String, Object?>>()
27-
.map(_WidgetCatalogCategory.new)
31+
.map(WidgetCatalogCategory.new)
2832
.sortedBy((c) => c.name),
2933
_ => throw Exception(
30-
'Catalog not found. '
34+
'Widget Catalog not found. '
3135
'Make sure the `data/catalog/index.yml` file exists.',
3236
),
3337
};
@@ -59,16 +63,123 @@ class WidgetCatalogCategories extends CustomComponentBase {
5963
}
6064
}
6165

62-
extension type _WidgetCatalogCategory(Map<String, Object?> _data) {
63-
String get id =>
64-
_data['id'] as String? ??
65-
(throw Exception('Missing id for widget catalog category. '));
66-
String get name =>
67-
_data['name'] as String? ??
68-
(throw Exception('Missing name for widget catalog category. '));
69-
String get description =>
70-
_data['description'] as String? ??
71-
(throw Exception(
72-
'Missing description for widget catalog category "$name".',
73-
));
66+
class WidgetCatalogGrid extends CustomComponentBase {
67+
const WidgetCatalogGrid();
68+
69+
@override
70+
Pattern get pattern => 'WidgetCatalogGrid';
71+
72+
@override
73+
Component apply(
74+
String name,
75+
Map<String, String> attributes,
76+
Component? child,
77+
) {
78+
return Builder(
79+
builder: (context) {
80+
final widgets = switch (context.page.data) {
81+
{'catalog': {'widgets': final List<Object?> widgets}} =>
82+
widgets
83+
.cast<Map<String, Object?>>()
84+
.map(WidgetCatalogWidget.new)
85+
.sortedBy((c) => c.name),
86+
_ => throw Exception(
87+
'Catalog not found. '
88+
'Make sure the `data/catalog/widgets.yml` file exists.',
89+
),
90+
};
91+
92+
return div(classes: 'card-grid', [
93+
for (final widget in widgets) WidgetCatalogCard(widget: widget),
94+
]);
95+
},
96+
);
97+
}
98+
}
99+
100+
class WidgetCatalogCard extends StatelessComponent {
101+
const WidgetCatalogCard({
102+
required this.widget,
103+
this.isMaterialCatalog = false,
104+
this.subcategory,
105+
super.key,
106+
});
107+
108+
final WidgetCatalogWidget widget;
109+
final bool isMaterialCatalog;
110+
final WidgetCatalogSubcategory? subcategory;
111+
112+
@override
113+
Component build(BuildContext context) {
114+
return a(href: widget.link, classes: 'card outlined-card', [
115+
_buildCardImageHolder(),
116+
div(classes: 'card-header', [
117+
span(classes: 'card-title', [text(widget.name)]),
118+
]),
119+
div(classes: 'card-content', [
120+
p([
121+
DashMarkdown(
122+
inline: true,
123+
content: truncateWords(widget.description, 25),
124+
),
125+
]),
126+
]),
127+
]);
128+
}
129+
130+
Component _buildCardImageHolder() {
131+
final holderClass = isMaterialCatalog
132+
? 'card-image-holder-material-3'
133+
: 'card-image-holder';
134+
135+
final imageAlt = isMaterialCatalog
136+
? 'Rendered example of the ${widget.name} Material widget.'
137+
: 'Rendered image or visualization of the ${widget.name} widget.';
138+
139+
final styleAttributes = isMaterialCatalog && subcategory?.color != null
140+
? {'style': '--bg-color: ${subcategory?.color}'}
141+
: <String, String>{};
142+
143+
final placeholder = img(
144+
alt:
145+
'Placeholder Flutter logo in place of '
146+
'missing widget image or visualization.',
147+
src: '/assets/images/docs/catalog-widget-placeholder.png',
148+
attributes: {'aria-hidden': 'true'},
149+
);
150+
151+
return div(
152+
classes: holderClass,
153+
attributes: styleAttributes,
154+
[
155+
if (isMaterialCatalog) ...[
156+
// Material catalog always expects an image.
157+
if (widget.imageSrc case final imageSrc? when imageSrc.isNotEmpty)
158+
img(alt: imageAlt, src: imageSrc)
159+
else
160+
placeholder,
161+
if (widget.hoverBackgroundSrc case final hoverBackgroundSrc?
162+
when hoverBackgroundSrc.isNotEmpty)
163+
div(classes: 'card-image-material-3-hover', [
164+
img(
165+
alt:
166+
'Decorated background for '
167+
'Material widget visualizations.',
168+
src: hoverBackgroundSrc,
169+
attributes: {'aria-hidden': 'true'},
170+
),
171+
]),
172+
] else ...[
173+
// Standard catalog prefers vector, then image, then placeholder.
174+
if (widget.vector case final vector? when vector.isNotEmpty)
175+
raw(vector)
176+
else if (widget.imageSrc case final imageSrc?
177+
when imageSrc.isNotEmpty)
178+
img(alt: imageAlt, src: imageSrc)
179+
else
180+
placeholder,
181+
],
182+
],
183+
);
184+
}
74185
}

0 commit comments

Comments
 (0)