Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/site_shared/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include: package:analysis_defaults/analysis.yaml

formatter:
trailing_commas: preserve
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use '../base/mixins';
@use 'base/mixins';

a {
color: var(--site-link-fgColor);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use '../base/mixins';
@use 'base/mixins';

.card-list {
display: flex;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use '../base/mixins';
@use 'base/mixins';

.dropdown {
.dropdown-content {
Expand Down
26 changes: 26 additions & 0 deletions packages/site_shared/lib/_sass/_menu-toggle.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Toggle between menu and close buttons if sidenav is open or not.
body:not(.sidenav-closed) #menu-toggle {
@media (min-width: 1024px) {
display: none;
}
}

#menu-toggle span.material-symbols {
&:first-child {
display: inline;
}

&:last-child {
display: none;
}
}

body.open_menu #menu-toggle span.material-symbols {
&:first-child {
display: none;
}

&:last-child {
display: inline;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@use '../base/mixins';

#site-switcher {
position: relative;

Expand Down Expand Up @@ -64,4 +62,4 @@
letter-spacing: normal;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@use '../base/mixins';
@use 'base/mixins';

.tab-pane {
display: none;
Expand Down
21 changes: 21 additions & 0 deletions packages/site_shared/lib/analytics.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2025 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:meta/meta.dart';

import 'src/analytics/analytics_server.dart'
if (dart.library.js_interop) 'src/analytics/analytics_web.dart';

/// Used to report analytic events.
final analytics = AnalyticsImplementation();

/// Contains methods for reporting analytics events.
abstract class Analytics {
@protected
void sendEvent(String eventName, Map<String, Object?> parameters);

void sendFeedback(bool helpful) {
sendEvent('feedback', {'feedback_type': helpful ? 'up' : 'down'});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:jaspr_content/jaspr_content.dart';

import '../../util.dart';
import '../util.dart';
import 'material_icon.dart';

/// Breadcrumbs navigation component that
Expand All @@ -18,11 +18,14 @@ import 'material_icon.dart';
/// - https://schema.org/BreadcrumbList
/// - https://www.w3.org/TR/wai-aria-practices/examples/breadcrumb/index.html
class PageBreadcrumbs extends StatelessComponent {
const PageBreadcrumbs({super.key});
const PageBreadcrumbs({this.crumbs, super.key});

final List<BreadcrumbItem>? crumbs;

@override
Component build(BuildContext context) {
final crumbs = _breadcrumbsForPage(context.pages, context.page);
final crumbs =
this.crumbs ?? _breadcrumbsForPage(context.pages, context.page);
if (crumbs == null || crumbs.isEmpty) {
return const Component.empty();
}
Expand Down Expand Up @@ -54,7 +57,7 @@ class PageBreadcrumbs extends StatelessComponent {
///
/// Uses page metadata to generate breadcrumb titles with fallbacks:
/// `breadcrumb` > `shortTitle` > `title`.
List<_BreadcrumbItem>? _breadcrumbsForPage(List<Page> pages, Page page) {
List<BreadcrumbItem>? _breadcrumbsForPage(List<Page> pages, Page page) {
final pageUrl = page.url;

// Only show breadcrumbs if the URL isn't empty.
Expand All @@ -71,7 +74,7 @@ class PageBreadcrumbs extends StatelessComponent {
.toList(growable: false);
if (segments.isEmpty) return null;

final breadcrumbs = <_BreadcrumbItem>[];
final breadcrumbs = <BreadcrumbItem>[];
var currentPath = '';

// Build breadcrumbs for each segment except the current page.
Expand All @@ -88,7 +91,7 @@ class PageBreadcrumbs extends StatelessComponent {

if (indexPage.breadcrumb case final indexBreadcrumb?) {
breadcrumbs.add(
_BreadcrumbItem(
BreadcrumbItem(
title: indexBreadcrumb,
url: indexPage.url,
),
Expand All @@ -104,7 +107,7 @@ class PageBreadcrumbs extends StatelessComponent {

// Add the current page as the final breadcrumb.
breadcrumbs.add(
_BreadcrumbItem(
BreadcrumbItem(
title: pageBreadcrumb,
url: pageUrl,
),
Expand All @@ -127,8 +130,8 @@ extension on Page {
}
}

final class _BreadcrumbItem {
const _BreadcrumbItem({required this.title, required this.url});
final class BreadcrumbItem {
const BreadcrumbItem({required this.title, required this.url});

final String title;
final String url;
Expand All @@ -142,7 +145,7 @@ final class _BreadcrumbItemComponent extends StatelessComponent {
required this.isLast,
});

final _BreadcrumbItem crumb;
final BreadcrumbItem crumb;
final int index;
final bool isLast;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';

import '../../util.dart';
import '../util.dart';
import 'material_icon.dart';

/// A generic button component with different style variants.
Expand All @@ -14,6 +14,7 @@ class Button extends StatelessComponent {
const Button({
super.key,
this.icon,
this.trailingIcon,
this.href,
this.content,
this.style = ButtonStyle.text,
Expand All @@ -30,6 +31,7 @@ class Button extends StatelessComponent {
final String? title;
final ButtonStyle style;
final String? icon;
final String? trailingIcon;
final String? id;
final String? href;
final Map<String, String> attributes;
Expand All @@ -48,14 +50,16 @@ class Button extends StatelessComponent {

final mergedClasses = [
style.cssClass,
if (icon != null && content == null) 'icon-button',
if ((icon != null || trailingIcon != null) && content == null)
'icon-button',
...?classes,
].toClasses;

final children = <Component>[
if (icon case final iconId?) MaterialIcon(iconId),
if (content case final contentText?)
asRaw ? RawText(contentText) : .text(contentText),
if (trailingIcon case final iconId?) MaterialIcon(iconId),
];

if (href case final href?) {
Expand Down Expand Up @@ -90,17 +94,3 @@ enum ButtonStyle {
ButtonStyle.text => 'text-button',
};
}

class SegmentedButton extends StatelessComponent {
const SegmentedButton({
super.key,
required this.children,
});

final List<Component> children;

@override
Component build(BuildContext context) {
return span(classes: ['segmented-button'].toClasses, children);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';

import '../../util.dart';
import '../util.dart';

class Card extends StatelessComponent {
/// Creates a card that can have a [header], [content], and [actions].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:universal_web/web.dart' as web;

import '../../util.dart';
import '../util/global_event_listener.dart';
import '../util.dart';
import '../utils/global_event_listener.dart';
import 'material_icon.dart';

/// A set of Material Design-like chips for configuration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:universal_web/web.dart' as web;

import '../../../util.dart';
import '../../util.dart';
import '../button.dart';

/// The cookie banner to show on a user's first time visiting the site.
@client
final class CookieNotice extends StatefulComponent {
const CookieNotice({super.key});
const CookieNotice({
super.key,
required this.host,
this.alwaysDarkMode = false,
});

final String host;
final bool alwaysDarkMode;

@override
State<CookieNotice> createState() => _CookieNoticeState();
Expand Down Expand Up @@ -60,13 +67,16 @@ final class _CookieNoticeState extends State<CookieNotice> {
Component build(BuildContext context) {
return section(
id: 'cookie-notice',
classes: [if (showNotice) 'show'].toClasses,
classes: [
if (showNotice) 'show',
if (component.alwaysDarkMode) 'always-dark-mode',
].toClasses,
attributes: {'data-nosnippet': 'true'},
[
div(classes: 'container', [
const p([
p([
.text(
'docs.flutter.dev uses cookies from Google to deliver and '
'${component.host} uses cookies from Google to deliver and '
'enhance the quality of its services and to analyze traffic.',
),
]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import '../button.dart';
class CopyButton extends StatefulComponent {
const CopyButton({
this.buttonText,
this.toCopy,
this.classes = const [],
this.title,
});

final String? title;
final String? toCopy;
final String? buttonText;
final List<String> classes;

Expand All @@ -32,38 +34,42 @@ class _CopyButtonState extends State<CopyButton> {
@override
void initState() {
if (kIsWeb) {
// Extract the code content and unhide the copy button on the client.
context.binding.addPostFrameCallback(() {
setState(() {
final codeElement = buttonKey.currentNode
?.closest('.code-block-wrapper')
?.querySelector('pre code')
?.cloneNode(true);
if (codeElement == null) return;

// Filter out hidden elements like the terminal sign or folding icons.
final iterator = web.document.createNodeIterator(
codeElement,
/* NodeFilter.SHOW_ELEMENT */ 1,
);
web.Node? currentNode;
while ((currentNode = iterator.nextNode()) != null) {
final element = currentNode as web.Element;
if (element.getAttribute('aria-hidden') == 'true') {
element.remove();
if (component.toCopy != null) {
content = component.toCopy;
} else {
// Extract the code content and unhide the copy button on the client.
context.binding.addPostFrameCallback(() {
setState(() {
final codeElement = buttonKey.currentNode
?.closest('.code-block-wrapper')
?.querySelector('pre code')
?.cloneNode(true);
if (codeElement == null) return;

// Filter out hidden elements like the terminal sign or folding icons.
final iterator = web.document.createNodeIterator(
codeElement,
/* NodeFilter.SHOW_ELEMENT */ 1,
);
web.Node? currentNode;
while ((currentNode = iterator.nextNode()) != null) {
final element = currentNode as web.Element;
if (element.getAttribute('aria-hidden') == 'true') {
element.remove();
}
}
}

// Remove zero-width spaces
content = codeElement.textContent?.replaceAll('\u200B', '');
});
// Remove zero-width spaces
content = codeElement.textContent?.replaceAll('\u200B', '');
});

assert(
content != null,
'CopyButton: Unable to find code content to copy. '
'Is the CopyButton inside a code block?',
);
});
assert(
content != null,
'CopyButton: Unable to find code content to copy. '
'Is the CopyButton inside a code block?',
);
});
}
}

super.initState();
Expand Down
Loading
Loading