Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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