Skip to content

Commit

Permalink
feat: knobs with nullable values (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kirpal committed Mar 9, 2022
1 parent 4b5dd2b commit b1674c4
Show file tree
Hide file tree
Showing 21 changed files with 1,155 additions and 184 deletions.
4 changes: 2 additions & 2 deletions packages/storybook_flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ class MyApp extends StatelessWidget {
description: 'The title of the app bar.',
),
),
elevation: context.knobs.slider(
elevation: context.knobs.nullable.slider(
label: 'AppBar elevation',
initial: 4,
min: 0,
max: 10,
description: 'Elevation of the app bar.',
),
backgroundColor: context.knobs.options(
backgroundColor: context.knobs.nullable.options(
label: 'AppBar color',
initial: Colors.blue,
description: 'Background color of the app bar.',
Expand Down
36 changes: 22 additions & 14 deletions packages/storybook_flutter/lib/src/knobs/bool_knob.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,31 @@ import 'package:provider/provider.dart';
import '../plugins/knobs.dart';
import 'knobs.dart';

/// {@template bool_knob}
/// A knob that allows the user to toggle a boolean value.
/// {@template bool_knob_value}
/// A knob value that allows the user to toggle a boolean value.
///
/// See also:
/// * [BooleanKnobWidget], which is the widget that displays the knob.
/// {@endtemplate}
class BoolKnob extends Knob<bool> {
/// {@macro bool_knob}
BoolKnob({
required String label,
String? description,
class BoolKnobValue extends KnobValue<bool> {
/// {@macro bool_knob_value}
BoolKnobValue({
required bool value,
}) : super(
label: label,
description: description,
value: value,
);
}) : super(value: value);

@override
Widget build() => BooleanKnobWidget(
Widget build({
required String label,
required String? description,
required bool enabled,
required bool nullable,
}) =>
BooleanKnobWidget(
label: label,
description: description,
value: value,
enabled: enabled,
nullable: nullable,
);
}

Expand All @@ -45,17 +47,23 @@ class BooleanKnobWidget extends StatelessWidget {
required this.label,
required this.description,
required this.value,
required this.enabled,
required this.nullable,
}) : super(key: key);

final String label;
final String? description;
final bool value;
final bool enabled;
final bool nullable;

@override
Widget build(BuildContext context) => CheckboxListTile(
tristate: nullable,
title: Text(label),
subtitle: description == null ? null : Text(description!),
value: value,
value: enabled ? value : null,
onChanged: (v) => context.read<KnobsNotifier>().update(label, v),
controlAffinity: ListTileControlAffinity.leading,
);
}
45 changes: 45 additions & 0 deletions packages/storybook_flutter/lib/src/knobs/knob.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'package:flutter/widgets.dart';

import 'knob_value.dart';

/// {@template knob}
/// An class that represents a control knob.
/// {@endtemplate}
class Knob<T> {
/// {@macro knob}
Knob({
required this.label,
this.description,
required this.knobValue,
});

/// The label of the knob.
final String label;

/// An optional description of the knob.
final String? description;

/// {@template knob.value}
/// The current value of the knob.
///
/// This may change as the user interacts with the knob.
/// {@endtemplate}
T get value => knobValue.value;

/// {@macro knob.value}
set value(T newValue) => knobValue.value = newValue;

@protected
KnobValue<T> knobValue;

/// The build method for the knob.
///
/// This method is responsible for building the widget that represents the
/// knob.
Widget build() => knobValue.build(
label: label,
description: description,
nullable: false,
enabled: true,
);
}
59 changes: 59 additions & 0 deletions packages/storybook_flutter/lib/src/knobs/knob_list_tile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';

class KnobListTile extends StatelessWidget {
const KnobListTile({
Key? key,
required this.enabled,
required this.nullable,
required this.onToggled,
this.title,
this.subtitle,
this.isThreeLine = false,
}) : super(key: key);

final Widget? title;
final Widget? subtitle;
final bool enabled;
final bool nullable;
final ValueChanged<bool> onToggled;
final bool isThreeLine;

@override
Widget build(BuildContext context) {
if (nullable) {
return SwitchListTile(
isThreeLine: isThreeLine,
onChanged: onToggled,
value: enabled,
controlAffinity: ListTileControlAffinity.leading,
title: IgnorePointer(
key: const Key('knobListTile_ignorePointer_disableTitle'),
ignoring: !enabled,
child: Opacity(
opacity: enabled ? 1 : 0.5,
child: DefaultTextStyle.merge(
style: TextStyle(
color: Theme.of(context).textTheme.subtitle1?.color,
),
child: title!,
),
),
),
subtitle: IgnorePointer(
key: const Key('knobListTile_ignorePointer_disableSubtitle'),
ignoring: !enabled,
child: Opacity(
opacity: enabled ? 1 : 0.5,
child: subtitle,
),
),
);
} else {
return ListTile(
isThreeLine: isThreeLine,
title: title,
subtitle: subtitle,
);
}
}
}
29 changes: 29 additions & 0 deletions packages/storybook_flutter/lib/src/knobs/knob_value.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:flutter/widgets.dart';

/// {@template knob}
/// An abstract class that represents a control knob.
///
/// Consumers can extend this class to create custom knob types.
/// {@endtemplate}
abstract class KnobValue<T> {
/// {@macro knob}
KnobValue({
required this.value,
});

/// The current value of the knob.
///
/// This may change as the user interacts with the knob.
T value;

/// The build method for the knob.
///
/// This method is responsible for building the widget that represents the
/// knob.
Widget build({
required String label,
required String? description,
required bool nullable,
required bool enabled,
});
}
105 changes: 8 additions & 97 deletions packages/storybook_flutter/lib/src/knobs/knobs.dart
Original file line number Diff line number Diff line change
@@ -1,97 +1,8 @@
import 'package:flutter/widgets.dart';

import 'select_knob.dart';

/// {@template knob}
/// An abstract class that represents a control knob.
///
/// Consumers can extend this class to create custom knob types.
/// {@endtemplate}
abstract class Knob<T> {
/// {@macro knob}
Knob({
required this.label,
this.description,
required this.value,
});

/// The label of the knob.
final String label;

/// An optional description of the knob.
final String? description;

/// The current value of the knob.
///
/// This may change as the user interacts with the knob.
T value;

/// The build method for the knob.
///
/// This method is responsible for building the widget that represents the
/// knob.
Widget build();
}

/// {@template knobs_builder}
/// Provides helper methods for creating knobs: control elements
/// that can be used in stories to dynamically update its properties.
///
/// It's injected into a story builder, so you can use it there:
///
/// ```dart
/// Story(
/// name: 'Flat button',
/// builder: (_, k) => MaterialButton(
/// onPressed: k.boolean('Enabled', initial: true) ? () {} : null,
/// child: Text(k.text('Text', initial: 'Flat button')),
/// ),
/// )
/// ```
/// {@endtemplate}
abstract class KnobsBuilder {
/// {@macro knobs_builder}
const KnobsBuilder();

/// Creates checkbox with [label], [description] and [initial] value.
bool boolean({
required String label,
String? description,
bool initial = false,
});

/// Creates text input field with [label], [description] and [initial] value.
String text({
required String label,
String? description,
String initial = '',
});

/// Creates slider knob with `double` value.
double slider({
required String label,
String? description,
double initial = 0,
double max = 1,
double min = 0,
});

/// Creates slider knob with `int` value.
int sliderInt({
required String label,
String? description,
int initial = 0,
int max = 100,
int min = 0,
int divisions = 100,
});

/// Creates select field with [label], [description], [initial] value and
/// list of [options].
T options<T>({
required String label,
String? description,
required T initial,
List<Option<T>> options = const [],
});
}
export 'bool_knob.dart';
export 'knob.dart';
export 'knob_value.dart';
export 'knobs_builder.dart';
export 'nullable_knob.dart';
export 'select_knob.dart';
export 'slider_knob.dart';
export 'string_knob.dart';

0 comments on commit b1674c4

Please sign in to comment.