Skip to content

Commit c9c2f10

Browse files
feat: create a custom [DocCallout] component to match the Docusaurus callout UI
1 parent 5919901 commit c9c2f10

4 files changed

Lines changed: 219 additions & 4 deletions

File tree

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import 'package:jaspr/dom.dart';
2+
import 'package:jaspr/jaspr.dart';
3+
import 'package:jaspr_content/jaspr_content.dart';
4+
5+
/// Docusaurus-compatible callout/admonition component.
6+
///
7+
/// Replaces jaspr_content's [Callout] to match Docusaurus admonition layout:
8+
/// - Left-border only (5 px), 4 px border-radius
9+
/// - Heading row: filled SVG icon + uppercase type label
10+
/// - Content body below
11+
///
12+
/// SVG icon paths are the exact Octicon paths used by Docusaurus
13+
/// (packages/docusaurus-theme-classic/src/theme/Admonition/Icon/).
14+
///
15+
/// Color values match Infima's admonition tokens from
16+
/// @infima/infima/dist/css/default/default.css.
17+
class DocCallout extends CustomComponentBase {
18+
DocCallout();
19+
20+
@override
21+
final Pattern pattern = RegExp(r'Info|Warning|Error|Success');
22+
23+
@override
24+
Component apply(String name, Map<String, String> attributes, Component? child) {
25+
return _DocCallout(type: name.toLowerCase(), child: child!);
26+
}
27+
28+
@css
29+
static List<StyleRule> get styles => [
30+
// Base layout: left-border only, Docusaurus sizing + subtle shadow.
31+
css('.doc-callout').styles(
32+
padding: Padding.symmetric(vertical: 12.px, horizontal: 16.px),
33+
margin: Margin.only(bottom: 1.25.rem),
34+
radius: BorderRadius.circular(4.px),
35+
raw: {
36+
'border': '0 solid',
37+
'border-left-width': '5px',
38+
'box-shadow': '0 1px 2px 0 rgba(0, 0, 0, 0.1)',
39+
},
40+
),
41+
42+
// Heading row: icon + uppercase type label.
43+
// No explicit font-weight: Docusaurus's VGV site only loads Poppins 400,
44+
// so the browser synthesizes a light faux-bold. Inheriting 400 (normal)
45+
// matches that visual output exactly.
46+
css('.doc-callout-heading').styles(
47+
display: Display.flex,
48+
margin: Margin.only(bottom: 0.3.rem),
49+
alignItems: AlignItems.center,
50+
fontSize: 0.875.rem,
51+
fontWeight: FontWeight.w600,
52+
raw: {
53+
'gap': '0.3em',
54+
'line-height': '1.25',
55+
'text-transform': 'uppercase',
56+
},
57+
),
58+
59+
// Icon: 1.6em relative to heading font, fills current color.
60+
css('.doc-callout-icon svg').styles(
61+
raw: {
62+
'height': '1.6em',
63+
'width': '1.6em',
64+
'fill': 'currentColor',
65+
'flex-shrink': '0',
66+
},
67+
),
68+
69+
// Remove default paragraph margin inside body.
70+
css('.doc-callout-body > p').styles(
71+
margin: Margin.only(bottom: Unit.zero),
72+
),
73+
74+
// --- Light mode per-type colors (Infima tokens) ---
75+
css('.doc-callout-info').styles(
76+
color: Color('#193c47'),
77+
backgroundColor: Color('#eef9fd'),
78+
raw: {'border-color': '#54c7ec'},
79+
),
80+
css('.doc-callout-warning').styles(
81+
color: Color('#4d3800'),
82+
backgroundColor: Color('#fff8e6'),
83+
raw: {'border-color': '#e6a700'},
84+
),
85+
css('.doc-callout-error').styles(
86+
color: Color('#4b1113'),
87+
backgroundColor: Color('#ffebec'),
88+
raw: {'border-color': '#fa5252'},
89+
),
90+
css('.doc-callout-success').styles(
91+
color: Color('#003100'),
92+
backgroundColor: Color('#e6fce6'),
93+
raw: {'border-color': '#00a400'},
94+
),
95+
96+
// --- Dark mode: semi-transparent bg, same border colors, inherit text ---
97+
css('[data-theme="dark"] .doc-callout-info').styles(
98+
color: Color.inherit,
99+
backgroundColor: Color('rgba(84, 199, 236, 0.15)'),
100+
raw: {'border-color': '#54c7ec'},
101+
),
102+
css('[data-theme="dark"] .doc-callout-warning').styles(
103+
color: Color.inherit,
104+
backgroundColor: Color('rgba(230, 167, 0, 0.15)'),
105+
raw: {'border-color': '#e6a700'},
106+
),
107+
css('[data-theme="dark"] .doc-callout-error').styles(
108+
color: Color.inherit,
109+
backgroundColor: Color('rgba(250, 82, 82, 0.15)'),
110+
raw: {'border-color': '#fa5252'},
111+
),
112+
css('[data-theme="dark"] .doc-callout-success').styles(
113+
color: Color.inherit,
114+
backgroundColor: Color('rgba(0, 164, 0, 0.15)'),
115+
raw: {'border-color': '#00a400'},
116+
),
117+
];
118+
}
119+
120+
class _DocCallout extends StatelessComponent {
121+
const _DocCallout({required this.type, required this.child});
122+
123+
final String type;
124+
final Component child;
125+
126+
@override
127+
Component build(BuildContext context) {
128+
final label = switch (type) {
129+
'info' => 'info',
130+
'warning' => 'warning',
131+
'error' => 'danger',
132+
'success' => 'tip',
133+
_ => type,
134+
};
135+
136+
return div(classes: 'doc-callout doc-callout-$type', [
137+
div(classes: 'doc-callout-heading', [
138+
span(classes: 'doc-callout-icon', [_buildIcon(type)]),
139+
Component.text(label),
140+
]),
141+
div(classes: 'doc-callout-body', [child]),
142+
]);
143+
}
144+
145+
static Component _buildIcon(String type) => switch (type) {
146+
'info' => svg(
147+
viewBox: '0 0 14 16',
148+
attributes: {'fill': 'currentColor'},
149+
[
150+
path(
151+
d: 'M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z',
152+
attributes: {'fill-rule': 'evenodd'},
153+
[],
154+
),
155+
],
156+
),
157+
'warning' => svg(
158+
viewBox: '0 0 16 16',
159+
attributes: {'fill': 'currentColor'},
160+
[
161+
path(
162+
d: 'M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z',
163+
attributes: {'fill-rule': 'evenodd'},
164+
[],
165+
),
166+
],
167+
),
168+
'error' => svg(
169+
viewBox: '0 0 12 16',
170+
attributes: {'fill': 'currentColor'},
171+
[
172+
path(
173+
d: 'M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z',
174+
attributes: {'fill-rule': 'evenodd'},
175+
[],
176+
),
177+
],
178+
),
179+
_ => svg(
180+
// success β†’ tip (lightbulb)
181+
viewBox: '0 0 12 16',
182+
attributes: {'fill': 'currentColor'},
183+
[
184+
path(
185+
d: 'M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z',
186+
attributes: {'fill-rule': 'evenodd'},
187+
[],
188+
),
189+
],
190+
),
191+
};
192+
}

β€Žsite_jaspr/lib/main.server.dartβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import 'dart:io';
1010
import 'package:jaspr/dom.dart';
1111
import 'package:jaspr/server.dart';
1212

13-
import 'package:jaspr_content/components/callout.dart';
1413
import 'package:jaspr_content/components/header.dart';
1514
import 'package:jaspr_content/components/image.dart';
1615
import 'package:jaspr_content/components/sidebar.dart';
@@ -21,6 +20,7 @@ import 'package:jaspr_content/jaspr_content.dart';
2120
import 'package:jaspr_content/theme.dart';
2221

2322
import 'components/breadcrumb.dart';
23+
import 'components/doc_callout.dart';
2424
import 'components/edit_page_link.dart';
2525
import 'components/homepage_layout.dart';
2626
import 'components/icon_link.dart';
@@ -50,7 +50,7 @@ void main() {
5050
TableOfContentsExtension(),
5151
],
5252
components: [
53-
Callout(),
53+
DocCallout(),
5454
SafeCodeBlock(
5555
grammars: {
5656
'yaml': File('grammars/yaml.tmLanguage.json').readAsStringSync(),

β€Žsite_jaspr/lib/main.server.options.dartβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import 'package:jaspr_content/components/_internal/code_block_copy_button.dart'
99
as _code_block_copy_button;
1010
import 'package:jaspr_content/components/_internal/zoomable_image.dart'
1111
as _zoomable_image;
12-
import 'package:jaspr_content/components/callout.dart' as _callout;
1312
import 'package:jaspr_content/components/code_block.dart' as _code_block;
1413
import 'package:jaspr_content/components/image.dart' as _image;
1514
import 'package:jaspr_content/components/sidebar_toggle_button.dart'
@@ -18,6 +17,7 @@ import 'package:jaspr_content/components/theme_toggle.dart' as _theme_toggle;
1817
import 'package:site_jaspr/components/breadcrumb.dart' as _breadcrumb;
1918
import 'package:site_jaspr/components/collapsible_sidebar.dart'
2019
as _collapsible_sidebar;
20+
import 'package:site_jaspr/components/doc_callout.dart' as _doc_callout;
2121
import 'package:site_jaspr/components/edit_page_link.dart' as _edit_page_link;
2222
import 'package:site_jaspr/components/icon_link.dart' as _icon_link;
2323
import 'package:site_jaspr/components/nav_link.dart' as _nav_link;
@@ -61,12 +61,12 @@ ServerOptions get defaultServerOptions => ServerOptions(
6161
},
6262
styles: () => [
6363
..._zoomable_image.ZoomableImage.styles,
64-
..._callout.Callout.styles,
6564
..._code_block.CodeBlock.styles,
6665
..._image.Image.styles,
6766
..._theme_toggle.ThemeToggleState.styles,
6867
..._breadcrumb.Breadcrumb.styles,
6968
..._collapsible_sidebar.CollapsibleSidebar.styles,
69+
..._doc_callout.DocCallout.styles,
7070
..._edit_page_link.EditPageLink.styles,
7171
..._icon_link.IconLink.styles,
7272
..._nav_link.NavLink.styles,

β€Žsite_jaspr/lib/styles/site_styles.dartβ€Ž

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,29 @@ List<StyleRule> get siteStyles => [
318318
css('.content-container a:not(.breadcrumb-link):hover').styles(
319319
textDecoration: TextDecoration(line: TextDecorationLine.underline),
320320
),
321+
// Callout links: inherit foreground color + always underlined (Infima alert behavior).
322+
// Overrides the blue `.content-container a` rule above via cascade order.
323+
css('.content-container .doc-callout a').styles(
324+
color: Color.inherit,
325+
fontWeight: FontWeight.w400,
326+
textDecoration: TextDecoration(line: TextDecorationLine.underline),
327+
),
328+
css('.content-container .doc-callout a:hover').styles(
329+
raw: {'text-decoration-thickness': '2px'},
330+
),
331+
// Underline color matches the left border color per type.
332+
css('.content-container .doc-callout-info a').styles(
333+
raw: {'text-decoration-color': '#54c7ec'},
334+
),
335+
css('.content-container .doc-callout-warning a').styles(
336+
raw: {'text-decoration-color': '#e6a700'},
337+
),
338+
css('.content-container .doc-callout-error a').styles(
339+
raw: {'text-decoration-color': '#fa5252'},
340+
),
341+
css('.content-container .doc-callout-success a').styles(
342+
raw: {'text-decoration-color': '#00a400'},
343+
),
321344

322345
// ───────────────────────────────────────────────────────────────────────
323346
// 10. DARK MODE: CODE BLOCKS & SCROLLBARS

0 commit comments

Comments
Β (0)