|
| 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 | +} |
0 commit comments