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
51 changes: 51 additions & 0 deletions apps/demo/lib/components/checkbox_group.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
import 'package:remix_fortal/remix_fortal.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;

@widgetbook.UseCase(name: 'Interests', type: RemixCheckboxGroup)
Widget buildCheckboxGroupUseCase(BuildContext context) =>
const CheckboxGroupExample();

class CheckboxGroupExample extends StatefulWidget {
const CheckboxGroupExample({super.key});

@override
State<CheckboxGroupExample> createState() => _CheckboxGroupExampleState();
}

class _CheckboxGroupExampleState extends State<CheckboxGroupExample> {
Set<String> _values = {'Design'};

@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 16,
children: [
RemixCheckboxGroup<String>(
values: _values,
onChanged: (values) => setState(() => _values = values),
semanticLabel: 'Interests',
child: const Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FortalCheckboxGroupItem(value: 'Design', label: 'Design'),
FortalCheckboxGroupItem(value: 'Code', label: 'Code'),
FortalCheckboxGroupItem(
value: 'Research',
label: 'Research',
enabled: false,
),
],
),
),
Text('Selected: ${_values.isEmpty ? 'none' : _values.join(', ')}'),
],
),
),
);
}
57 changes: 57 additions & 0 deletions apps/demo/lib/components/sidebar.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
import 'package:remix_fortal/remix_fortal.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;

@widgetbook.UseCase(name: 'Workspace navigation', type: RemixSidebar)
Widget buildSidebarUseCase(BuildContext context) => const SidebarExample();

class SidebarExample extends StatefulWidget {
const SidebarExample({super.key});

@override
State<SidebarExample> createState() => _SidebarExampleState();
}

class _SidebarExampleState extends State<SidebarExample> {
String _selected = 'Overview';

@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: SizedBox(
width: 256,
height: 320,
child: FortalSidebar<String>(
sections: const [
RemixSidebarSection(
label: 'Workspace',
destinations: [
RemixSidebarDestination(
value: 'Overview',
label: 'Overview',
icon: Icons.space_dashboard_outlined,
),
RemixSidebarDestination(
value: 'Settings',
label: 'Settings',
icon: Icons.settings_outlined,
),
RemixSidebarDestination(
value: 'Billing',
label: 'Billing',
icon: Icons.credit_card,
enabled: false,
),
],
),
],
selectedValue: _selected,
onSelected: (value) => setState(() => _selected = value),
semanticLabel: 'Workspace navigation',
footer: Text('Selected: $_selected'),
),
),
),
);
}
38 changes: 38 additions & 0 deletions apps/demo/lib/components/skeleton.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
import 'package:remix_fortal/remix_fortal.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;

@widgetbook.UseCase(name: 'Loading content', type: RemixSkeleton)
Widget buildSkeletonUseCase(BuildContext context) => const SkeletonExample();

class SkeletonExample extends StatefulWidget {
const SkeletonExample({super.key});

@override
State<SkeletonExample> createState() => _SkeletonExampleState();
}

class _SkeletonExampleState extends State<SkeletonExample> {
bool _loading = true;

@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
spacing: 24,
children: [
FortalSkeleton(
loading: _loading,
child: const Text('Your workspace is ready'),
),
FortalButton.outline(
label: _loading ? 'Show content' : 'Show skeleton',
onPressed: () => setState(() => _loading = !_loading),
),
],
),
),
);
}
31 changes: 31 additions & 0 deletions apps/demo/lib/main.directories.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import 'package:demo/components/button.dart' as _demo_components_button;
import 'package:demo/components/callout.dart' as _demo_components_callout;
import 'package:demo/components/card.dart' as _demo_components_card;
import 'package:demo/components/checkbox.dart' as _demo_components_checkbox;
import 'package:demo/components/checkbox_group.dart'
as _demo_components_checkbox_group;
import 'package:demo/components/code.dart' as _demo_components_code;
import 'package:demo/components/data_list.dart' as _demo_components_data_list;
import 'package:demo/components/data_table.dart' as _demo_components_data_table;
Expand All @@ -35,6 +37,8 @@ import 'package:demo/components/radio.dart' as _demo_components_radio;
import 'package:demo/components/segmented_control.dart'
as _demo_components_segmented_control;
import 'package:demo/components/select.dart' as _demo_components_select;
import 'package:demo/components/sidebar.dart' as _demo_components_sidebar;
import 'package:demo/components/skeleton.dart' as _demo_components_skeleton;
import 'package:demo/components/slider.dart' as _demo_components_slider;
import 'package:demo/components/spinner.dart' as _demo_components_spinner;
import 'package:demo/components/switch.dart' as _demo_components_switch;
Expand Down Expand Up @@ -197,6 +201,15 @@ final directories = <_widgetbook.WidgetbookNode>[
),
],
),
_widgetbook.WidgetbookComponent(
name: 'RemixCheckboxGroup<Object>',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'Interests',
builder: _demo_components_checkbox_group.buildCheckboxGroupUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'RemixDataList',
useCases: [
Expand Down Expand Up @@ -342,6 +355,24 @@ final directories = <_widgetbook.WidgetbookNode>[
),
],
),
_widgetbook.WidgetbookComponent(
name: 'RemixSidebar<Object>',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'Workspace navigation',
builder: _demo_components_sidebar.buildSidebarUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'RemixSkeleton',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'Loading content',
builder: _demo_components_skeleton.buildSkeletonUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'RemixSlider',
useCases: [
Expand Down
100 changes: 100 additions & 0 deletions apps/demo/test/docs_preview_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import 'dart:convert';
import 'dart:io';

import 'package:demo/components/checkbox_group.dart';
import 'package:demo/components/sidebar.dart';
import 'package:demo/components/skeleton.dart';
import 'package:demo/main.directories.g.dart';
import 'package:demo/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:remix_fortal/remix_fortal.dart';
import 'package:widgetbook/widgetbook.dart';

void main() {
test('preview theme query matches the catalog addon contract', () {
final addon = FortalThemeAddon();
expect(addon.groupName, 'theme');
for (final name in ['light', 'dark']) {
expect(addon.valueFromQueryGroup({'name': name}).name, name);
}
});

test('every documentation component maps to real Widgetbook routes', () {
final root = WidgetbookRoot(children: directories);
final routes = root.leaves
.whereType<WidgetbookUseCase>()
.map((node) => node.path)
.toSet();
final manifest =
jsonDecode(
File('../../docs/component-previews.json').readAsStringSync(),
)
as Map<String, dynamic>;
final pages = Directory('../../docs/components')
.listSync()
.whereType<File>()
.map((file) => file.uri.pathSegments.last.replaceAll('.mdx', ''))
.toSet();
expect(manifest.keys.toSet(), pages);
for (final entry in manifest.entries) {
expect(File('lib/components/${entry.key}.dart').existsSync(), isTrue);
final cases = entry.value['cases'] as List;
expect(cases, isNotEmpty);
for (final example in cases) {
expect(
routes,
contains(example['path']),
reason: '${entry.key}: ${example['name']}',
);
}
}
});

Future<void> show(WidgetTester tester, Widget child) async {
await tester.pumpWidget(MaterialApp(home: FortalScope(child: child)));
await tester.pump(const Duration(milliseconds: 100));
expect(tester.takeException(), isNull);
}

testWidgets(
'checkbox example changes its controlled set and keeps research disabled',
(tester) async {
await show(tester, const CheckboxGroupExample());
await tester.tap(find.text('Code'));
await tester.pump();
expect(find.text('Selected: Design, Code'), findsOneWidget);
await tester.tap(find.text('Research'));
await tester.pump();
expect(find.text('Selected: Design, Code'), findsOneWidget);
},
);

testWidgets('sidebar example changes selection and keeps billing disabled', (
tester,
) async {
await show(tester, const SidebarExample());
await tester.tap(find.text('Settings'));
await tester.pump();
expect(find.text('Selected: Settings'), findsOneWidget);
await tester.tap(find.text('Billing'));
await tester.pump();
expect(find.text('Selected: Settings'), findsOneWidget);
});

testWidgets('skeleton example can reveal its content', (tester) async {
await show(tester, const SkeletonExample());
expect(
tester.widget<FortalSkeleton>(find.byType(FortalSkeleton)).loading,
isTrue,
);
await tester.tap(find.text('Show content'));
await tester.pump();
expect(
tester.widget<FortalSkeleton>(find.byType(FortalSkeleton)).loading,
isFalse,
);
expect(find.text('Show skeleton'), findsOneWidget);
await tester.pumpWidget(const SizedBox.shrink());
});
}
8 changes: 8 additions & 0 deletions apps/demo/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@

</head>
<body>
<script>
// The docs host only accepts this signal from its own preview frame.
window.addEventListener('flutter-first-frame', function () {
if (window.parent !== window) {
window.parent.postMessage({ type: 'remix-preview-ready' }, window.location.origin);
}
});
</script>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>
8 changes: 8 additions & 0 deletions apps/docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules/
.next/
.source/
.generated/
public/assets/
public/previews/
*.tsbuildinfo
next-env.d.ts
Loading
Loading