Skip to content

Commit 69376fa

Browse files
committed
add CodePreview component
1 parent 4908cb5 commit 69376fa

File tree

9 files changed

+924
-787
lines changed

9 files changed

+924
-787
lines changed

site/lib/_sass/base/_base.scss

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -158,43 +158,6 @@ main figure {
158158
font-style: italic;
159159
text-align: center;
160160
}
161-
162-
&.code-and-image {
163-
gap: 0.25rem;
164-
justify-content: space-between;
165-
flex-direction: row;
166-
flex-wrap: wrap;
167-
168-
>div {
169-
width: 100%;
170-
171-
&:last-child {
172-
text-align: center;
173-
}
174-
}
175-
176-
@media(min-width: 769px) {
177-
>div {
178-
&:first-child {
179-
flex: 0 0 58%;
180-
max-width: 58%;
181-
}
182-
183-
&:last-child {
184-
flex: 0 0 40%;
185-
max-width: 40%;
186-
}
187-
}
188-
189-
figcaption {
190-
text-align: left;
191-
}
192-
193-
img {
194-
max-width: 100%;
195-
}
196-
}
197-
}
198161
}
199162

200163
.text-icon {

site/lib/_sass/components/_code.scss

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ pre {
276276
}
277277

278278
.code-block-wrapper {
279+
display: flex;
280+
flex-direction: column;
281+
279282
margin-block-start: 1rem;
280283
margin-block-end: 1rem;
281284
border: 1px solid var(--site-inset-borderColor);
@@ -292,6 +295,8 @@ pre {
292295
}
293296

294297
.code-block-body {
298+
flex-grow: 1;
299+
295300
position: relative;
296301
background: none;
297302

@@ -328,6 +333,7 @@ pre {
328333
}
329334

330335
pre {
336+
height: 100%;
331337
margin: 0;
332338
padding-right: 0;
333339
padding-left: 0;
@@ -358,8 +364,8 @@ iframe[src^="https://dartpad"] {
358364
--file-tree-text: var(--site-base-fgColor);
359365
--file-tree-icon: var(--site-base-fgColor-alt);
360366
--file-tree-highlight: var(--site-link-fgColor);
361-
362-
font-family: var(--site-code-fontFamily);
367+
368+
font-family: var(--site-code-fontFamily);
363369

364370
border: 1px solid var(--site-inset-borderColor);
365371
margin-block-start: 1rem;
@@ -374,3 +380,58 @@ iframe[src^="https://dartpad"] {
374380
}
375381
}
376382
}
383+
384+
.code-preview {
385+
display: flex;
386+
flex-direction: column;
387+
388+
border: 1px solid var(--site-inset-borderColor);
389+
margin-block-start: 1rem;
390+
margin-block-end: 1rem;
391+
392+
.preview-area {
393+
display: flex;
394+
flex-flow: column nowrap;
395+
justify-content: center;
396+
align-items: center;
397+
398+
padding: 2rem;
399+
text-align: center;
400+
401+
&.fixed-bg * {
402+
// --site-base-fgColor-lighter, but fixed to light mode variant.
403+
color: #{color.scale(#212121, $lightness: 20%)}
404+
}
405+
}
406+
407+
.code-block-wrapper {
408+
border: none;
409+
margin: 0;
410+
411+
border-top: inherit;
412+
}
413+
414+
&[data-direction="row"] {
415+
flex-direction: row-reverse;
416+
flex-wrap: wrap;
417+
418+
>* {
419+
width: 100%;
420+
}
421+
422+
@media (min-width: 760px) {
423+
.preview-area {
424+
flex: 0 0 42%;
425+
max-width: 42%;
426+
}
427+
428+
.code-block-wrapper {
429+
flex: 0 0 58%;
430+
max-width: 58%;
431+
432+
border-top: none;
433+
border-right: inherit;
434+
}
435+
}
436+
}
437+
}

site/lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'jaspr_options.dart'; // Generated. Do not remove or edit.
1212
import 'src/components/common/card.dart';
1313
import 'src/components/common/client/download_latest_button.dart';
1414
import 'src/components/common/client/os_selector.dart';
15+
import 'src/components/common/code_preview.dart';
1516
import 'src/components/common/dash_image.dart';
1617
import 'src/components/common/tabs.dart';
1718
import 'src/components/common/youtube_embed.dart';
@@ -96,6 +97,7 @@ final RegExp _passThroughPattern = RegExp(r'.*\.(txt|json|pdf)$');
9697
List<CustomComponent> get _embeddableComponents => [
9798
const DashTabs(),
9899
const DashImage(),
100+
const CodePreview(),
99101
const YoutubeEmbed(),
100102
const FileTree(),
101103
const Quiz(),
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2025 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:jaspr/jaspr.dart';
6+
import 'package:jaspr_content/jaspr_content.dart';
7+
8+
import '../../util.dart';
9+
import 'wrapped_code_block.dart';
10+
11+
/// A component that displays a preview area alongside a code block.
12+
///
13+
/// The `<CodePreview>` component takes in standard markdown content, but
14+
/// expects at least two children, with the last child being a code block.
15+
/// Any content before the last child is treated as the preview area.
16+
class CodePreview extends CustomComponent {
17+
const CodePreview() : super.base();
18+
19+
@override
20+
Component? create(Node node, NodesBuilder builder) {
21+
if (node case ElementNode(
22+
tag: 'CodePreview',
23+
:final attributes,
24+
:final children?,
25+
)) {
26+
if (children.length < 2) {
27+
throw Exception('CodePreview requires at least two child elements.');
28+
}
29+
final lastChild = children.last;
30+
if (lastChild is! ComponentNode ||
31+
lastChild.component is! WrappedCodeBlock) {
32+
throw Exception(
33+
'The last child of CodePreview must be a code block.',
34+
);
35+
}
36+
37+
final previewChildren = <Node>[];
38+
39+
for (var i = 0; i < children.length - 1; i++) {
40+
if (children[i] case ElementNode(tag: 'p', :final children?)) {
41+
// Unwrap paragraph nodes to avoid extra spacing.
42+
previewChildren.addAll(children);
43+
} else {
44+
previewChildren.add(children[i]);
45+
}
46+
}
47+
48+
final direction = attributes['direction'] ?? 'column';
49+
final previewColor = attributes['previewcolor'];
50+
51+
return div(
52+
classes: 'code-preview',
53+
attributes: {'data-direction': direction},
54+
[
55+
div(
56+
classes: [
57+
'preview-area',
58+
if (previewColor != null) 'fixed-bg',
59+
].toClasses,
60+
styles: previewColor != null
61+
? Styles(backgroundColor: Color(previewColor))
62+
: null,
63+
[
64+
builder.build(previewChildren),
65+
],
66+
),
67+
lastChild.component,
68+
],
69+
);
70+
}
71+
72+
return null;
73+
}
74+
}

site/lib/src/style_hash.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
// dart format off
33

44
/// The generated hash of the `main.css` file.
5-
const generatedStylesHash = 'v4/Tg9ef/l7C';
5+
const generatedStylesHash = 'Mwk/S177Y9Gd';

src/_includes/docs/code-and-image.md

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/content/app-architecture/case-study/ui-layer.md

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -404,26 +404,30 @@ a [`Dismissible`][] widget.
404404

405405
Recall this code from the previous snippet:
406406

407-
{% render "docs/code-and-image.md",
408-
image:"app-architecture/case-study/dismissible.webp",
409-
img-style:"max-height: 480px; border-radius: 12px; border: black 2px solid;",
410-
alt: "A clip that demonstrates the 'dismissible' functionality of the Compass app."
411-
code:"
412-
```dart title=home_screen.dart highlightLines=9-10
413-
SliverList.builder(
414-
itemCount: widget.viewModel.bookings.length,
415-
itemBuilder: (_, index) => _Booking(
416-
key: ValueKey(viewModel.bookings[index].id),
417-
booking: viewModel.bookings[index],
418-
onTap: () => context.push(
419-
Routes.bookingWithId(viewModel.bookings[index].id)
407+
<CodePreview direction="row">
408+
409+
<DashImage
410+
image="app-architecture/case-study/dismissible.webp"
411+
alt="A clip that demonstrates the 'dismissible' functionality of the Compass app."
412+
img-style="max-height: 480px; border-radius: 12px; border: black 2px solid;"
413+
/>
414+
415+
```dart title=home_screen.dart highlightLines=9-10
416+
SliverList.builder(
417+
itemCount: widget.viewModel.bookings.length,
418+
itemBuilder: (_, index) => _Booking(
419+
key: ValueKey(viewModel.bookings[index].id),
420+
booking: viewModel.bookings[index],
421+
onTap: () => context.push(
422+
Routes.bookingWithId(viewModel.bookings[index].id)
423+
),
424+
onDismissed: (_) =>
425+
viewModel.deleteBooking.execute(widget.viewModel.bookings[index].id),
420426
),
421-
onDismissed: (_) =>
422-
viewModel.deleteBooking.execute(widget.viewModel.bookings[index].id),
423427
),
424-
),
425-
```
426-
" %}
428+
```
429+
430+
</CodePreview>
427431

428432
On the `HomeScreen`, a user's saved trip is represented by
429433
the `_Booking` widget. When a `_Booking` is dismissed,

0 commit comments

Comments
 (0)