feat(draw): fold the d2 source into a "view source" disclosure (T-494)

The rendered diagram leads; the d2 source carries through the draw bus and
folds into a collapsed ClideCollapserCard beneath the card (D-78 display-
only). en+nl catalogs. Also switches the d2 test to a null-aware element.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 12:08:33 +02:00
co-authored by Claude Opus 4.8
parent 64d77ec5dc
commit 5f8fa6af10
9 changed files with 82 additions and 4 deletions
+31 -1
View File
@@ -14,6 +14,7 @@ library;
import 'package:clide/kernel/src/theme/tokens.dart';
import 'package:clide/widgets/src/clide_card_metrics.dart';
import 'package:clide/widgets/src/clide_collapser_card.dart';
import 'package:clide/widgets/src/clide_settings.dart';
import 'package:clide/widgets/src/clide_text.dart';
import 'package:clide/widgets/src/svg/svg_painter.dart';
@@ -22,12 +23,27 @@ import 'package:clide/src/svg/svg_node.dart';
import 'package:flutter/widgets.dart';
class DrawingCard extends StatelessWidget {
const DrawingCard({super.key, required this.document, this.label, this.description, this.images, this.onLightbox, this.maxHeight = 360});
const DrawingCard({
super.key,
required this.document,
this.label,
this.description,
this.images,
this.onLightbox,
this.source,
this.sourceLabel,
this.maxHeight = 360,
});
final SvgDocument document;
final String? label, description;
final SvgImageResolver? images;
/// The template source (e.g. d2 diagram text) and its disclosure title. When
/// [source] is present, a collapsed "view source" disclosure renders beneath
/// the drawing (T-494) — the rendered diagram leads; the code folds away.
final String? source, sourceLabel;
/// Called when a `data-lightbox` element is tapped (the caller opens the
/// zoom view). Null ⇒ no lightbox affordance.
final VoidCallback? onLightbox;
@@ -51,6 +67,20 @@ class DrawingCard extends StatelessWidget {
padding: const EdgeInsets.only(top: kClideCardHeaderPadV),
child: ClideText(description!, fontSize: clideFontCaption, color: tokens.globalTextMuted),
),
if (_has(source))
Padding(
padding: const EdgeInsets.only(top: kClideCardHeaderPadV),
child: ClideCollapserCard(
label: sourceLabel ?? 'view source',
color: tokens.globalTextMuted,
children: [
Padding(
padding: const EdgeInsets.all(kClideCardHeaderPadH),
child: ClideText(source!, fontSize: clideFontCaption, fontFamily: ClideSettings.fonts.monoOf(context), color: tokens.globalForeground),
),
],
),
),
],
);
}