chico_ui 1.7.0
chico_ui: ^1.7.0 copied to clipboard
Zero-dependency Flutter UI kit for minimal system interfaces. Responsive, adaptive, RTL-first widgets built from design tokens.
import 'package:chico_ui/chico_ui.dart';
import 'package:flutter/widgets.dart';
void main() {
runApp(const GalleryApp());
}
/// Live catalog of Chico UI. Rebuilds [ChicoApp] when the appearance mode
/// changes so light / dark / system can be compared on device.
class GalleryApp extends StatefulWidget {
const GalleryApp({super.key});
@override
State<GalleryApp> createState() => _GalleryAppState();
}
class _GalleryAppState extends State<GalleryApp> {
ChicoThemeMode _mode = ChicoThemeMode.system;
Locale? _locale;
@override
Widget build(BuildContext context) {
return ChicoApp(
title: 'Chico UI',
themeMode: _mode,
locale: _locale,
home: GalleryPage(
mode: _mode,
locale: _locale,
onModeChanged: (mode) => setState(() => _mode = mode),
onLocaleChanged: (locale) => setState(() => _locale = locale),
),
);
}
}
/// Scrollable catalog of every Chico widget.
class GalleryPage extends StatelessWidget {
const GalleryPage({
super.key,
required this.mode,
required this.locale,
required this.onModeChanged,
required this.onLocaleChanged,
});
final ChicoThemeMode mode;
final Locale? locale;
final ValueChanged<ChicoThemeMode> onModeChanged;
final ValueChanged<Locale?> onLocaleChanged;
@override
Widget build(BuildContext context) {
final theme = ChicoTheme.of(context);
final media = ChicoMedia.of(context);
return ChicoPage(
title: 'Chico UI',
child: ChicoColumn(
gap: ChicoSpace.space24,
children: [
const ChicoText(
'Minimal system kit. Resize the window or rotate the phone '
'to see breakpoints change. Tap outside a field to dismiss '
'the keyboard.',
role: ChicoTextRole.secondary,
),
_AppearanceRow(mode: mode, onModeChanged: onModeChanged),
_LocaleRow(locale: locale, onLocaleChanged: onLocaleChanged),
const _SectionTitle('Window'),
_WindowCard(media: media, theme: theme),
const _SectionTitle('Actions'),
ChicoRow(
gap: ChicoSpace.space8,
wrapBelow: ChicoBreakpoint.sm,
children: [
ChicoButton(label: 'Continue', onPressed: () {}),
ChicoButton(
label: 'Cancel',
tone: ChicoButtonTone.neutral,
onPressed: () {},
),
ChicoButton(
label: 'Delete',
tone: ChicoButtonTone.destructive,
onPressed: () {},
),
],
),
const ChicoButton(label: 'Disabled', expand: true),
ChicoRow(
gap: ChicoSpace.space8,
wrap: true,
children: [
ChicoIconButton(
icon: const ChicoGlyph(ChicoGlyphKind.add),
tooltip: 'Add',
tone: ChicoButtonTone.accent,
onPressed: () {},
),
ChicoIconButton(
icon: const ChicoGlyph(ChicoGlyphKind.close),
tooltip: 'Close',
onPressed: () {},
),
ChicoIconButton(
icon: const ChicoGlyph(ChicoGlyphKind.check),
tooltip: 'Done',
onPressed: () {},
),
ChicoIconButton(
icon: const ChicoGlyph(ChicoGlyphKind.chevronBack),
tooltip: 'Back',
onPressed: () {},
),
ChicoIconButton(
icon: const ChicoGlyph(ChicoGlyphKind.home),
tooltip: 'Home',
onPressed: () {},
),
ChicoIconButton(
icon: const ChicoGlyph(ChicoGlyphKind.search),
tooltip: 'Search',
onPressed: () {},
),
ChicoIconButton(
icon: const ChicoGlyph(ChicoGlyphKind.person),
tooltip: 'You',
onPressed: () {},
),
],
),
ChicoLink(label: 'Learn more', onPressed: () {}),
ChicoRow(
gap: ChicoSpace.space8,
wrap: true,
children: [
for (final kind in <ChicoGlyphKind>[
ChicoGlyphKind.settings,
ChicoGlyphKind.trash,
ChicoGlyphKind.mail,
ChicoGlyphKind.lock,
ChicoGlyphKind.eye,
ChicoGlyphKind.calendar,
ChicoGlyphKind.star,
ChicoGlyphKind.bell,
])
ChicoIconButton(
icon: ChicoGlyph(kind),
tooltip: kind.name,
onPressed: () {},
),
],
),
const _SectionTitle('Fields'),
const ChicoText(
'Switch keyboard language while typing. Tap outside to unfocus.',
role: ChicoTextRole.secondary,
variant: ChicoTextVariant.footnote,
),
const ChicoTextField(label: 'Name', hint: 'Type English or العربية…'),
const ChicoTextField(
label: 'Password',
hint: '••••••••',
obscureText: true,
),
const ChicoTextField(
label: 'Amount',
hint: '0.00',
leading: ChicoText('USD', role: ChicoTextRole.secondary),
maxLength: 8,
keyboardType: TextInputType.number,
),
const _SelectPlayground(),
const _ValidatePlayground(),
const _FormsPlayground(),
const _SectionTitle('Inputs+'),
const _InputsPlayground(),
const _SectionTitle('Feedback'),
const _FeedbackPlayground(),
const _SectionTitle('Surfaces'),
const _SurfacesPlayground(),
const _SectionTitle('Lists & grids'),
const _ListsPlayground(),
const _SectionTitle('Content+'),
const _ContentPlayground(),
const _SectionTitle('Navigation'),
const _NavigationPlayground(),
const _SectionTitle('Navigation+'),
const _NavigationPlusPlayground(),
const _SectionTitle('Adaptive'),
const _AdaptivePlayground(),
const _SectionTitle('Overlays'),
const _OverlaysPlayground(),
const _SectionTitle('Sheets'),
const _SheetsPlayground(),
const _SectionTitle('Pickers'),
const _PickersPlayground(),
const _SectionTitle('Data+'),
const _DataPlayground(),
const _SectionTitle('Type scale'),
...ChicoTextVariant.values.map((variant) {
return ChicoText(variant.name, variant: variant);
}),
const _SectionTitle('Text roles'),
const ChicoText('label — primary copy'),
const ChicoText(
'secondary — subtitles, timestamps',
role: ChicoTextRole.secondary,
),
const ChicoText(
'tertiary — hints, metadata',
role: ChicoTextRole.tertiary,
),
const ChicoText('tint — links, selection', role: ChicoTextRole.tint),
const ChicoText(
'destructive — errors, delete',
role: ChicoTextRole.destructive,
),
const ChicoText(
'success — saved, online',
role: ChicoTextRole.success,
),
const ChicoText(
'warning — pending, caution',
role: ChicoTextRole.warning,
),
const _SectionTitle('Colors'),
_ColorGrid(theme: theme, media: media),
const _SectionTitle('Spacing'),
_SpacingScale(theme: theme, media: media),
const _SectionTitle('Components'),
const ChicoText(
'Everything in the kit, grouped by category.',
role: ChicoTextRole.secondary,
),
..._roadmap.map(
(item) => _RoadmapRow(item: item, theme: theme, media: media),
),
],
),
);
}
}
class _SectionTitle extends StatelessWidget {
const _SectionTitle(this.label);
final String label;
@override
Widget build(BuildContext context) {
final media = ChicoMedia.of(context);
return Padding(
padding: EdgeInsets.only(bottom: media.space(ChicoSpace.space8)),
child: ChicoText(label, variant: ChicoTextVariant.headline),
);
}
}
class _AppearanceRow extends StatelessWidget {
const _AppearanceRow({required this.mode, required this.onModeChanged});
final ChicoThemeMode mode;
final ValueChanged<ChicoThemeMode> onModeChanged;
@override
Widget build(BuildContext context) {
final media = ChicoMedia.of(context);
return Wrap(
spacing: media.space(ChicoSpace.space8),
runSpacing: media.space(ChicoSpace.space8),
children: [
for (final value in ChicoThemeMode.values)
_ModeChip(
label: value.name,
selected: mode == value,
onTap: () => onModeChanged(value),
),
],
);
}
}
class _ModeChip extends StatelessWidget {
const _ModeChip({
required this.label,
required this.selected,
required this.onTap,
});
final String label;
final bool selected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final theme = ChicoTheme.of(context);
final fill = selected ? theme.colors.tint : theme.colors.fill;
final foreground = selected ? theme.colors.onTint : theme.colors.label;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: ChicoHitTarget.min),
child: DecoratedBox(
decoration: BoxDecoration(
color: fill,
borderRadius: ChicoRadius.borderMd,
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: ChicoSpace.space16,
vertical: ChicoSpace.space8,
),
child: Center(
child: ChicoText(
label,
variant: ChicoTextVariant.subheadline,
color: foreground,
),
),
),
),
),
);
}
}
class _LocaleRow extends StatelessWidget {
const _LocaleRow({required this.locale, required this.onLocaleChanged});
final Locale? locale;
final ValueChanged<Locale?> onLocaleChanged;
@override
Widget build(BuildContext context) {
final media = ChicoMedia.of(context);
return Wrap(
spacing: media.space(ChicoSpace.space8),
runSpacing: media.space(ChicoSpace.space8),
children: [
_ModeChip(
label: 'system',
selected: locale == null,
onTap: () => onLocaleChanged(null),
),
_ModeChip(
label: 'English',
selected: locale?.languageCode == 'en',
onTap: () => onLocaleChanged(const Locale('en')),
),
_ModeChip(
label: 'العربية',
selected: locale?.languageCode == 'ar',
onTap: () => onLocaleChanged(const Locale('ar')),
),
_ModeChip(
label: 'עברית',
selected: locale?.languageCode == 'he',
onTap: () => onLocaleChanged(const Locale('he')),
),
],
);
}
}
class _ValidatePlayground extends StatefulWidget {
const _ValidatePlayground();
@override
State<_ValidatePlayground> createState() => _ValidatePlaygroundState();
}
class _ValidatePlaygroundState extends State<_ValidatePlayground> {
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return ChicoForm(
formKey: _formKey,
child: ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoTextFormField(
label: 'Email',
hint: 'ada@example.com',
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || !value.contains('@')) {
return 'Enter an email';
}
return null;
},
),
ChicoButton(
label: 'Validate',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () => _formKey.currentState?.validate(),
),
],
),
);
}
}
class _FormsPlayground extends StatefulWidget {
const _FormsPlayground();
@override
State<_FormsPlayground> createState() => _FormsPlaygroundState();
}
class _FormsPlaygroundState extends State<_FormsPlayground> {
var _agreed = true;
var _notify = false;
var _size = 1;
var _volume = 0.4;
var _low = 0.2;
var _high = 0.8;
var _stars = 4;
@override
Widget build(BuildContext context) {
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoCheckbox(
value: _agreed,
label: 'I agree to the terms',
onChanged: (value) => setState(() => _agreed = value),
),
ChicoSwitch(
value: _notify,
label: 'Notifications',
onChanged: (value) => setState(() => _notify = value),
),
ChicoRadio<int>(
value: 0,
groupValue: _size,
label: 'Small',
onChanged: (value) => setState(() => _size = value),
),
ChicoRadio<int>(
value: 1,
groupValue: _size,
label: 'Medium',
onChanged: (value) => setState(() => _size = value),
),
ChicoRadio<int>(
value: 2,
groupValue: _size,
label: 'Large',
onChanged: (value) => setState(() => _size = value),
),
ChicoText(
'Volume ${(_volume * 100).round()}%',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoSlider(
value: _volume,
onChanged: (value) => setState(() => _volume = value),
),
ChicoText(
'Range ${(_low * 100).round()}–${(_high * 100).round()}',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoRangeSlider(
start: _low,
end: _high,
onChanged: (start, end) => setState(() {
_low = start;
_high = end;
}),
),
ChicoRating(
value: _stars,
onChanged: (value) => setState(() => _stars = value),
),
const ChicoAutocomplete(
label: 'Name',
hint: 'Ada, Alan, Grace…',
options: ['Ada', 'Alan', 'Grace', 'Linus', 'Margaret'],
),
const ChicoPinField(length: 4),
const ChicoNumberField(
label: 'Amount',
style: ChicoNumberStyle.currency,
hint: '0.00',
),
],
);
}
}
class _InputsPlayground extends StatefulWidget {
const _InputsPlayground();
@override
State<_InputsPlayground> createState() => _InputsPlaygroundState();
}
class _InputsPlaygroundState extends State<_InputsPlayground> {
var _unread = true;
var _flagged = false;
var _work = true;
var _count = 2;
var _tags = <String>['Design'];
@override
Widget build(BuildContext context) {
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoSearch(hint: 'Mail, people, documents', onChanged: (_) {}),
Wrap(
spacing: ChicoSpace.space8,
runSpacing: ChicoSpace.space8,
children: [
ChicoChip(
label: 'Unread',
selected: _unread,
onPressed: () => setState(() => _unread = !_unread),
),
ChicoChip(
label: 'Flagged',
selected: _flagged,
onPressed: () => setState(() => _flagged = !_flagged),
),
if (_work)
ChicoChip(
label: 'Work',
onDeleted: () => setState(() => _work = false),
),
],
),
ChicoTokenField(
label: 'Tags',
hint: 'Add a tag',
tokens: _tags,
onChanged: (next) => setState(() => _tags = next),
),
ChicoRow(
gap: ChicoSpace.space12,
children: [
const ChicoText('Quantity'),
ChicoStepper(
value: _count,
min: 1,
max: 9,
onChanged: (value) => setState(() => _count = value),
),
],
),
],
);
}
}
class _FeedbackPlayground extends StatefulWidget {
const _FeedbackPlayground();
@override
State<_FeedbackPlayground> createState() => _FeedbackPlaygroundState();
}
class _FeedbackPlaygroundState extends State<_FeedbackPlayground> {
var _showBanner = true;
var _progress = 0.45;
@override
Widget build(BuildContext context) {
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
const ChicoRow(
gap: ChicoSpace.space16,
children: [
ChicoSpinner(),
ChicoSpinner(tone: ChicoButtonTone.neutral),
ChicoSpinner(tone: ChicoButtonTone.destructive),
],
),
ChicoText(
'Upload ${(_progress * 100).round()}%',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoProgress(value: _progress),
ChicoSlider(
value: _progress,
onChanged: (value) => setState(() => _progress = value),
),
const ChicoText(
'Indeterminate',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
const ChicoProgress(),
if (_showBanner)
ChicoBanner(
title: 'Saved',
message: 'A copy is on this device.',
actionLabel: 'Undo',
onAction: () {},
onDismiss: () => setState(() => _showBanner = false),
),
const ChicoBanner(
message: 'Could not reach the server.',
tone: ChicoButtonTone.destructive,
),
const ChicoBanner(
title: 'Synced',
message: 'All changes are on this device.',
tone: ChicoButtonTone.success,
),
const ChicoBanner(
message: 'Battery is low.',
tone: ChicoButtonTone.warning,
),
const ChicoSkeleton.tile(),
ChicoButton(
label: 'Show dialog',
expand: true,
onPressed: () {
ChicoDialog.show<bool>(
context,
title: 'Delete item?',
message: 'This cannot be undone.',
actions: const [
ChicoDialogAction(
label: 'Cancel',
result: false,
tone: ChicoButtonTone.neutral,
),
ChicoDialogAction(
label: 'Delete',
result: true,
tone: ChicoButtonTone.destructive,
isDefault: true,
),
],
);
},
),
ChicoButton(
label: 'Show prompt',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
ChicoDialog.prompt(context, title: 'Rename', hint: 'Name');
},
),
ChicoButton(
label: 'Show confirm',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
ChicoDialog.confirm(
context,
title: 'Delete item?',
message: 'This cannot be undone.',
confirmTone: ChicoButtonTone.destructive,
);
},
),
ChicoButton(
label: 'Show busy',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () async {
ChicoBusy.show(context, message: 'Saving');
await Future<void>.delayed(const Duration(milliseconds: 1200));
ChicoBusy.hide();
},
),
ChicoButton(
label: 'Push page',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
Navigator.of(context).push(
ChicoPageRoute<void>(
builder: (context) {
return ChicoScaffold(
bar: const ChicoBar(title: 'Detail'),
body: const ChicoPage(
child: ChicoText('Pushed with ChicoPageRoute.'),
),
);
},
),
);
},
),
],
);
}
}
class _SurfacesPlayground extends StatelessWidget {
const _SurfacesPlayground();
@override
Widget build(BuildContext context) {
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoRow(
gap: ChicoSpace.space12,
children: [
ChicoAvatar.name('Ada Lovelace'),
ChicoAvatar.name('Grace Hopper', size: 32),
ChicoBadge(count: 3, child: ChicoAvatar.name('Alan Turing')),
const ChicoBadge(count: 12),
const ChicoBadge(tone: ChicoButtonTone.destructive),
],
),
ChicoAvatarStack(
avatars: [
ChicoAvatar.name('Ada Lovelace', size: 32),
ChicoAvatar.name('Grace Hopper', size: 32),
ChicoAvatar.name('Alan Turing', size: 32),
ChicoAvatar.name('Linus Torvalds', size: 32),
],
),
const ChicoCard(
child: ChicoText(
'Grouped surface. Hairline, no shadow.',
role: ChicoTextRole.secondary,
),
),
],
);
}
}
class _ListsPlayground extends StatefulWidget {
const _ListsPlayground();
@override
State<_ListsPlayground> createState() => _ListsPlaygroundState();
}
class _ListsPlaygroundState extends State<_ListsPlayground> {
var _macbook = true;
var _wifi = true;
var _loadingMore = false;
var _pageCount = 1;
final _order = <String>['Design', 'Launch', 'Review'];
static const _mailboxes = [
('Inbox', '128'),
('Sent', '42'),
('Drafts', '3'),
('Archive', '910'),
];
@override
Widget build(BuildContext context) {
return ChicoColumn(
gap: ChicoSpace.space16,
children: [
const _ListCaption(
'ChicoGrid — adaptive columns (override with columns:)',
),
ChicoGrid(
children: [
for (final entry in _mailboxes)
ChicoCard(
child: ChicoColumn(
gap: ChicoSpace.space4,
children: [
ChicoText(entry.$1, variant: ChicoTextVariant.headline),
ChicoText(
entry.$2,
role: ChicoTextRole.secondary,
variant: ChicoTextVariant.footnote,
),
],
),
),
],
),
const _ListCaption('ChicoGrid — fixed 3 columns'),
ChicoGrid(
columns: 3,
gap: ChicoSpace.space8,
children: const [
ChicoCard(child: ChicoText('A')),
ChicoCard(child: ChicoText('B')),
ChicoCard(child: ChicoText('C')),
],
),
const _ListCaption('ChicoListTile — leading, trailing, chevron'),
ChicoCard(
padding: EdgeInsets.zero,
child: Column(
children: [
ChicoListTile(
title: 'Ada Lovelace',
subtitle: 'Mathematician',
leading: ChicoAvatar.name('Ada Lovelace'),
onPressed: () {},
),
const ChicoDivider(),
ChicoListTile(
title: 'Wi‑Fi',
trailing: ChicoSwitch(
value: _wifi,
onChanged: (value) => setState(() => _wifi = value),
),
),
const ChicoDivider(),
ChicoListTile(
title: 'Language',
trailing: const ChicoText(
'English',
role: ChicoTextRole.secondary,
),
showChevron: true,
onPressed: () {},
),
const ChicoDivider(),
const ChicoListTile(title: 'Static row'),
],
),
),
const _ListCaption('ChicoListSection — header, footer, grouped rows'),
ChicoListSection(
header: 'Devices',
footer: 'Swipe MacBook toward the start to delete.',
children: [
const ChicoListTile(title: 'iPhone', subtitle: 'This iPhone'),
if (_macbook)
ChicoSwipe(
actions: [
ChicoSwipeAction(
label: 'Delete',
onPressed: () => setState(() => _macbook = false),
),
],
child: const ChicoListTile(
title: 'MacBook Air',
subtitle: 'Online',
),
),
ChicoListTile(
title: 'iCloud',
trailing: const ChicoText('On', role: ChicoTextRole.secondary),
onPressed: () {},
),
],
),
if (!_macbook)
ChicoButton(
label: 'Restore MacBook row',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () => setState(() => _macbook = true),
),
const _ListCaption('ChicoCarousel — horizontal pager + dots'),
ChicoCarousel(
height: 120,
onChanged: (_) {},
children: [
for (final label in ['One', 'Two', 'Three'])
ChicoCard(
child: Center(
child: ChicoText(label, variant: ChicoTextVariant.title),
),
),
],
),
const _ListCaption('ChicoReorderableList — long-press to drag'),
ChicoReorderableList(
onReorder: (oldIndex, newIndex) {
setState(() {
final item = _order.removeAt(oldIndex);
_order.insert(newIndex, item);
});
},
children: [
for (final name in _order)
ChicoListTile(key: ValueKey(name), title: name),
],
),
const _ListCaption('ChicoStickyList — pinned section headers'),
SizedBox(
height: 200,
child: ChicoStickyList(
groups: [
ChicoStickyGroup(
header: 'A',
children: const [
ChicoListTile(title: 'Ada'),
ChicoListTile(title: 'Alan'),
ChicoListTile(title: 'Alice'),
],
),
ChicoStickyGroup(
header: 'B',
children: const [
ChicoListTile(title: 'Barbara'),
ChicoListTile(title: 'Bob'),
],
),
ChicoStickyGroup(
header: 'C',
children: const [ChicoListTile(title: 'Carol')],
),
],
),
),
const _ListCaption('ChicoIndexedList — sticky groups + A–Z rail'),
const SizedBox(
height: 220,
child: ChicoIndexedList(
groups: [
ChicoStickyGroup(
header: 'A',
children: [
ChicoListTile(title: 'Ada'),
ChicoListTile(title: 'Alan'),
],
),
ChicoStickyGroup(
header: 'G',
children: [ChicoListTile(title: 'Grace')],
),
ChicoStickyGroup(
header: 'L',
children: [
ChicoListTile(title: 'Linus'),
ChicoListTile(title: 'Lisa'),
],
),
ChicoStickyGroup(
header: 'T',
children: [ChicoListTile(title: 'Tim')],
),
],
),
),
const _ListCaption('ChicoLoadMore — paged list footer'),
ChicoColumn(
gap: ChicoSpace.space8,
children: [
for (var i = 1; i <= _pageCount * 2; i++)
ChicoListTile(title: 'Row $i'),
ChicoLoadMore(
loading: _loadingMore,
onPressed: _loadingMore
? null
: () async {
setState(() => _loadingMore = true);
await Future<void>.delayed(
const Duration(milliseconds: 700),
);
if (!mounted) {
return;
}
setState(() {
_loadingMore = false;
_pageCount++;
});
},
),
],
),
],
);
}
}
class _ListCaption extends StatelessWidget {
const _ListCaption(this.label);
final String label;
@override
Widget build(BuildContext context) {
return ChicoText(
label,
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
);
}
}
class _ContentPlayground extends StatefulWidget {
const _ContentPlayground();
@override
State<_ContentPlayground> createState() => _ContentPlaygroundState();
}
class _ContentPlaygroundState extends State<_ContentPlayground> {
var _page = 0;
var _empty = false;
var _ticks = 0;
var _step = 1;
@override
Widget build(BuildContext context) {
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
if (_empty)
ChicoEmptyState(
icon: const ChicoGlyph(ChicoGlyphKind.search, size: 36),
title: 'No Results',
message: 'Try a different name or keyword.',
actionLabel: 'Show list',
onAction: () => setState(() => _empty = false),
)
else
ChicoButton(
label: 'Show empty state',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () => setState(() => _empty = true),
),
ChicoDisclosure(
title: 'Advanced',
child: ChicoText(
'VPN is required on public networks.',
role: ChicoTextRole.secondary,
),
),
ChicoStepIndicator(
count: 3,
index: _step,
labels: const ['Account', 'Profile', 'Done'],
onChanged: (index) => setState(() => _step = index),
),
ChicoButton(
label: _step < 2 ? 'Next step' : 'Reset steps',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
setState(() => _step = _step < 2 ? _step + 1 : 0);
},
),
ChicoPageControl(
count: 3,
index: _page,
onChanged: (index) => setState(() => _page = index),
),
ChicoText(
'Page ${_page + 1} of 3 · pull the list to refresh ($_ticks)',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
SizedBox(
height: 160,
child: ChicoRefresh(
onRefresh: () async {
await Future<void>.delayed(const Duration(milliseconds: 600));
if (mounted) {
setState(() => _ticks++);
}
},
child: ListView(
physics: const AlwaysScrollableScrollPhysics(
parent: BouncingScrollPhysics(),
),
children: [
for (var i = 1; i <= 6; i++) ChicoListTile(title: 'Item $i'),
],
),
),
),
],
);
}
}
class _NavigationPlayground extends StatefulWidget {
const _NavigationPlayground();
@override
State<_NavigationPlayground> createState() => _NavigationPlaygroundState();
}
class _NavigationPlaygroundState extends State<_NavigationPlayground> {
var _tab = 1;
var _nav = 0;
static const _tabs = ['Day', 'Week', 'Month'];
static const _navLabels = ['Home', 'Search', 'You'];
@override
Widget build(BuildContext context) {
return ChicoCard(
padding: EdgeInsets.zero,
child: SizedBox(
height: 280,
child: ChicoScaffold(
bar: ChicoBar(
title: 'Inbox',
safeArea: false,
progress: 0.45,
onBack: () {},
trailing: ChicoBarButton(
icon: const ChicoGlyph(ChicoGlyphKind.add),
tooltip: 'Add',
onPressed: () {},
),
),
body: Padding(
padding: const EdgeInsetsDirectional.all(ChicoSpace.space16),
child: ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoTabView(
labels: _tabs,
index: _tab,
onChanged: (index) => setState(() => _tab = index),
children: [
for (final label in _tabs)
ChicoText(label, role: ChicoTextRole.secondary),
],
),
ChicoText(
_navLabels[_nav],
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
],
),
),
bottomNav: ChicoBottomNav(
index: _nav,
safeArea: false,
onChanged: (index) => setState(() => _nav = index),
items: const [
ChicoNavItem(
label: 'Home',
icon: ChicoGlyph(ChicoGlyphKind.home),
),
ChicoNavItem(
label: 'Search',
icon: ChicoGlyph(ChicoGlyphKind.search),
badgeCount: 3,
),
ChicoNavItem(
label: 'You',
icon: ChicoGlyph(ChicoGlyphKind.person),
),
],
),
),
),
);
}
}
class _NavigationPlusPlayground extends StatefulWidget {
const _NavigationPlusPlayground();
@override
State<_NavigationPlusPlayground> createState() =>
_NavigationPlusPlaygroundState();
}
class _NavigationPlusPlaygroundState extends State<_NavigationPlusPlayground> {
var _rail = 0;
@override
Widget build(BuildContext context) {
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
const ChicoText(
'Rail for md+ side navigation. Large title fades the compact bar in as you scroll.',
role: ChicoTextRole.secondary,
variant: ChicoTextVariant.footnote,
),
ChicoCard(
padding: EdgeInsets.zero,
child: SizedBox(
height: 280,
child: ChicoScaffold(
rail: ChicoRail(
extended: true,
safeArea: false,
index: _rail,
onChanged: (index) => setState(() => _rail = index),
items: const [
ChicoNavItem(
label: 'Home',
icon: ChicoGlyph(ChicoGlyphKind.home),
),
ChicoNavItem(
label: 'Search',
icon: ChicoGlyph(ChicoGlyphKind.search),
),
ChicoNavItem(
label: 'You',
icon: ChicoGlyph(ChicoGlyphKind.person),
),
],
),
body: ChicoLargeTitle(
title: ['Home', 'Search', 'You'][_rail],
automaticallyImplyLeading: false,
safeArea: false,
search: const ChicoSearch(hint: 'Filter'),
child: Padding(
padding: const EdgeInsetsDirectional.symmetric(
horizontal: ChicoSpace.space16,
),
child: ChicoColumn(
gap: ChicoSpace.space8,
children: [
for (var i = 1; i <= 8; i++)
ChicoListTile(
title: 'Item $i',
subtitle: 'Scroll to collapse the title',
onPressed: () {},
),
],
),
),
),
),
),
),
],
);
}
}
class _OverlaysPlayground extends StatelessWidget {
const _OverlaysPlayground();
@override
Widget build(BuildContext context) {
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
const ChicoText(
'Hover or long-press the add control. Open the menu from More. Help is a popover.',
role: ChicoTextRole.secondary,
variant: ChicoTextVariant.footnote,
),
ChicoRow(
gap: ChicoSpace.space8,
wrap: true,
children: [
ChicoTooltip(
message: 'Add an item',
child: ChicoIconButton(
icon: const ChicoGlyph(ChicoGlyphKind.add),
tooltip: 'Add',
onPressed: () {},
),
),
Builder(
builder: (context) {
return ChicoButton(
label: 'More',
tone: ChicoButtonTone.neutral,
onPressed: () {
ChicoMenu.show<String>(
context,
actions: const [
ChicoMenuAction(
label: 'Edit',
result: 'edit',
icon: ChicoGlyph(ChicoGlyphKind.check),
),
ChicoMenuAction(label: 'Share', result: 'share'),
ChicoMenuAction(
label: 'Delete',
result: 'delete',
tone: ChicoButtonTone.destructive,
),
],
);
},
);
},
),
Builder(
builder: (context) {
return ChicoButton(
label: 'Help',
tone: ChicoButtonTone.neutral,
onPressed: () {
ChicoPopover.show<void>(
context,
child: const Padding(
padding: EdgeInsets.all(ChicoSpace.space16),
child: SizedBox(
width: 220,
child: ChicoText(
'A popover holds any content. Tap the dimmed area to dismiss.',
role: ChicoTextRole.secondary,
),
),
),
);
},
);
},
),
],
),
],
);
}
}
class _SheetsPlayground extends StatelessWidget {
const _SheetsPlayground();
@override
Widget build(BuildContext context) {
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
const ChicoText(
'Sheet for custom content. Action sheet for a choice plus cancel. Toast is a non-blocking status pill.',
role: ChicoTextRole.secondary,
variant: ChicoTextVariant.footnote,
),
ChicoButton(
label: 'Show sheet',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
ChicoSheet.show<void>(
context,
title: 'Filters',
child: ChicoColumn(
gap: ChicoSpace.space12,
children: [
const ChicoText(
'Narrow the list. Dismiss from Done or the dimmed area.',
role: ChicoTextRole.secondary,
),
ChicoButton(
label: 'Done',
expand: true,
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
},
),
ChicoButton(
label: 'Show half sheet',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
ChicoSheet.show<void>(
context,
title: 'Detents',
detent: ChicoSheetDetent.medium,
child: const ChicoText(
'Drag the grabber up for full, down to dismiss.',
role: ChicoTextRole.secondary,
),
);
},
),
ChicoButton(
label: 'Show action sheet',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
ChicoActionSheet.show<String>(
context,
title: 'Change photo',
message: 'This replaces the current image.',
actions: const [
ChicoDialogAction(label: 'Take photo', result: 'camera'),
ChicoDialogAction(
label: 'Choose from library',
result: 'library',
),
ChicoDialogAction(
label: 'Delete',
result: 'delete',
tone: ChicoButtonTone.destructive,
),
],
cancel: const ChicoDialogAction(
label: 'Cancel',
tone: ChicoButtonTone.neutral,
),
);
},
),
ChicoButton(
label: 'Show toast',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
ChicoToast.show(context, message: 'Saved');
},
),
],
);
}
}
class _PickersPlayground extends StatefulWidget {
const _PickersPlayground();
@override
State<_PickersPlayground> createState() => _PickersPlaygroundState();
}
class _PickersPlaygroundState extends State<_PickersPlayground> {
DateTime _date = DateTime(2024, 6, 15);
ChicoTime _time = const ChicoTime(hour: 9, minute: 30);
@override
Widget build(BuildContext context) {
final use24 = MediaQuery.alwaysUse24HourFormatOf(context);
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoText(
'${_date.year}-${_date.month.toString().padLeft(2, '0')}-${_date.day.toString().padLeft(2, '0')} · ${_time.format(use24Hour: use24)}',
role: ChicoTextRole.secondary,
),
ChicoButton(
label: 'Show date picker',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () async {
final next = await ChicoDatePicker.show(
context,
initialDate: _date,
);
if (next != null) {
setState(() => _date = next);
}
},
),
ChicoButton(
label: 'Show time picker',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () async {
final next = await ChicoTimePicker.show(
context,
initialTime: _time,
);
if (next != null) {
setState(() => _time = next);
}
},
),
ChicoButton(
label: 'Show date and time',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () async {
final next = await ChicoDatePicker.showDateTime(
context,
initialDateTime: DateTime(
_date.year,
_date.month,
_date.day,
_time.hour,
_time.minute,
),
);
if (next != null) {
setState(() {
_date = next;
_time = ChicoTime.fromDateTime(next);
});
}
},
),
ChicoButton(
label: 'Show date range',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
ChicoDatePicker.showRange(context);
},
),
],
);
}
}
class _SelectPlayground extends StatefulWidget {
const _SelectPlayground();
@override
State<_SelectPlayground> createState() => _SelectPlaygroundState();
}
class _SelectPlaygroundState extends State<_SelectPlayground> {
String? _role = 'member';
@override
Widget build(BuildContext context) {
return ChicoSelect<String>(
label: 'Role',
value: _role,
options: const [
ChicoSelectOption(value: 'admin', label: 'Admin'),
ChicoSelectOption(value: 'member', label: 'Member'),
ChicoSelectOption(value: 'guest', label: 'Guest'),
],
onChanged: (value) => setState(() => _role = value),
);
}
}
class _AdaptivePlayground extends StatefulWidget {
const _AdaptivePlayground();
@override
State<_AdaptivePlayground> createState() => _AdaptivePlaygroundState();
}
class _AdaptivePlaygroundState extends State<_AdaptivePlayground> {
var _tab = 0;
String? _thread;
static const _items = [
ChicoNavItem(label: 'Home', icon: ChicoGlyph(ChicoGlyphKind.home)),
ChicoNavItem(label: 'Mail', icon: ChicoGlyph(ChicoGlyphKind.mail)),
ChicoNavItem(label: 'You', icon: ChicoGlyph(ChicoGlyphKind.person)),
];
@override
Widget build(BuildContext context) {
return ChicoCard(
padding: EdgeInsets.zero,
child: SizedBox(
height: 280,
child: ChicoAdaptiveScaffold(
index: _tab,
onDestinationSelected: (index) => setState(() => _tab = index),
destinations: _items,
bar: ChicoBar(
title: _items[_tab].label,
automaticallyImplyLeading: false,
safeArea: false,
bottom: const ChicoSearch(hint: 'Filter'),
),
body: ChicoSplitView(
list: ListView(
padding: const EdgeInsetsDirectional.all(ChicoSpace.space8),
children: [
for (final name in ['Design', 'Launch', 'Review'])
ChicoListTile(
title: name,
onPressed: () => setState(() => _thread = name),
),
],
),
detail: ChicoPad(
all: ChicoSpace.space16,
child: ChicoText(
_thread == null ? 'Pick a thread' : _thread!,
variant: ChicoTextVariant.title,
),
),
showDetailOnCompact: _thread != null,
),
),
),
);
}
}
class _DataPlayground extends StatefulWidget {
const _DataPlayground();
@override
State<_DataPlayground> createState() => _DataPlaygroundState();
}
class _DataPlaygroundState extends State<_DataPlayground> {
@override
Widget build(BuildContext context) {
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoBreadcrumbs(
items: [
ChicoBreadcrumb(label: 'Inbox', onPressed: () {}),
const ChicoBreadcrumb(label: 'Design'),
],
),
ChicoContextMenu(
actions: const [
ChicoMenuAction(label: 'Star', result: 'star'),
ChicoMenuAction(
label: 'Delete',
result: 'delete',
tone: ChicoButtonTone.destructive,
),
],
child: const ChicoListTile(
title: 'Long-press or right-click',
subtitle: 'Opens a context menu',
),
),
const ChicoTable(
columns: [
ChicoTableColumn(label: 'Mailbox'),
ChicoTableColumn(label: 'Count', numeric: true, flex: 1),
],
rows: [
['Inbox', '128'],
['Sent', '42'],
],
),
const SizedBox(
height: 180,
child: ChicoStatusPage.empty(
title: 'No results',
message: 'Try a different filter.',
),
),
ChicoButton(
label: 'Show shortcuts',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
ChicoShortcuts.show(
context,
items: const [
ChicoShortcutItem(keys: '⌘K', label: 'Search'),
ChicoShortcutItem(keys: '⌘N', label: 'New'),
],
);
},
),
],
);
}
}
class _WindowCard extends StatelessWidget {
const _WindowCard({required this.media, required this.theme});
final ChicoMediaData media;
final ChicoThemeData theme;
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: theme.colors.groupedBackground,
borderRadius: ChicoRadius.borderLg,
border: Border.all(
color: theme.colors.separator,
width: media.hairline,
),
),
child: Padding(
padding: EdgeInsets.all(media.space(ChicoSpace.space16)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ChicoText(
'${media.width.round()} × ${media.height.round()} · '
'${media.breakpoint.name} · ${media.isRtl ? 'RTL' : 'LTR'}',
variant: ChicoTextVariant.headline,
),
SizedBox(height: media.space(ChicoSpace.space4)),
ChicoText(
'paddingScale ${media.paddingScale} · '
'displayScale ${media.displayScale} · '
'${media.isCompact ? 'compact' : 'regular'}',
role: ChicoTextRole.secondary,
variant: ChicoTextVariant.footnote,
),
],
),
),
);
}
}
class _ColorGrid extends StatelessWidget {
const _ColorGrid({required this.theme, required this.media});
final ChicoThemeData theme;
final ChicoMediaData media;
@override
Widget build(BuildContext context) {
final colors = theme.colors;
final swatches = <(String, Color)>[
('label', colors.label),
('secondary', colors.secondaryLabel),
('tertiary', colors.tertiaryLabel),
('background', colors.background),
('grouped', colors.groupedBackground),
('fill', colors.fill),
('separator', colors.separator),
('tint', colors.tint),
('destructive', colors.destructive),
('success', colors.success),
('warning', colors.warning),
];
return Wrap(
spacing: media.space(ChicoSpace.space12),
runSpacing: media.space(ChicoSpace.space12),
children: [
for (final swatch in swatches)
SizedBox(
width: 96,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DecoratedBox(
decoration: BoxDecoration(
color: swatch.$2,
borderRadius: ChicoRadius.borderSm,
border: Border.all(
color: theme.colors.separator,
width: media.hairline,
),
),
child: const SizedBox(width: 96, height: 36),
),
SizedBox(height: media.space(ChicoSpace.space4)),
ChicoText(
swatch.$1,
variant: ChicoTextVariant.caption,
role: ChicoTextRole.secondary,
),
],
),
),
],
);
}
}
class _SpacingScale extends StatelessWidget {
const _SpacingScale({required this.theme, required this.media});
final ChicoThemeData theme;
final ChicoMediaData media;
static const _tokens = <(String, double)>[
('2', ChicoSpace.space2),
('4', ChicoSpace.space4),
('8', ChicoSpace.space8),
('12', ChicoSpace.space12),
('16', ChicoSpace.space16),
('20', ChicoSpace.space20),
('24', ChicoSpace.space24),
('32', ChicoSpace.space32),
('40', ChicoSpace.space40),
('48', ChicoSpace.space48),
];
@override
Widget build(BuildContext context) {
return Column(
children: [
for (final token in _tokens)
Padding(
padding: EdgeInsets.only(bottom: media.space(ChicoSpace.space8)),
child: Row(
children: [
SizedBox(
width: 36,
child: ChicoText(
token.$1,
variant: ChicoTextVariant.caption,
role: ChicoTextRole.secondary,
),
),
DecoratedBox(
decoration: BoxDecoration(
color: theme.colors.tint,
borderRadius: ChicoRadius.borderSm,
),
child: SizedBox(height: 8, width: token.$2),
),
],
),
),
],
);
}
}
class _RoadmapItem {
const _RoadmapItem({
required this.category,
required this.widgets,
required this.ready,
});
final String category;
final String widgets;
final bool ready;
}
const _roadmap = <_RoadmapItem>[
_RoadmapItem(
category: 'Foundation',
widgets: 'tokens, theme, media, ChicoApp',
ready: true,
),
_RoadmapItem(category: 'Typography', widgets: 'ChicoText', ready: true),
_RoadmapItem(
category: 'Layout',
widgets: 'Gap, Pad, Row, Column, Page, Grid',
ready: true,
),
_RoadmapItem(
category: 'Actions',
widgets: 'Button, IconButton, Link',
ready: true,
),
_RoadmapItem(
category: 'Forms',
widgets:
'TextField, Form, Select, Autocomplete, Pin, Number, Token, Checkbox, Switch, Radio, Slider, Range, Rating',
ready: true,
),
_RoadmapItem(
category: 'Feedback',
widgets:
'Spinner, Progress, Banner, Dialog, Busy, Skeleton, LoadMore, StatusPage',
ready: true,
),
_RoadmapItem(
category: 'Surfaces',
widgets:
'Divider, Card, ListTile, Avatar, AvatarStack, Badge, Table, Image, Reorderable, Sticky, Indexed',
ready: true,
),
_RoadmapItem(category: 'Lists+', widgets: 'ListSection, Swipe', ready: true),
_RoadmapItem(
category: 'Content+',
widgets: 'EmptyState, Disclosure, PageControl, Refresh, StepIndicator',
ready: true,
),
_RoadmapItem(
category: 'Navigation',
widgets:
'Bar, Tabs, TabView, BottomNav, AdaptiveScaffold, SplitView, Breadcrumbs, PageRoute',
ready: true,
),
_RoadmapItem(
category: 'Overlays',
widgets: 'Tooltip, Menu, Popover, ContextMenu, Shortcuts',
ready: true,
),
_RoadmapItem(
category: 'Sheets',
widgets: 'Sheet, ActionSheet, Toast, detents',
ready: true,
),
_RoadmapItem(
category: 'Inputs+',
widgets: 'Search, Chip, Stepper, TokenField',
ready: true,
),
_RoadmapItem(
category: 'Pickers',
widgets: 'Date, Time, DateRange, DateTime',
ready: true,
),
_RoadmapItem(
category: 'Navigation+',
widgets: 'Rail, large title, carousel, bar search',
ready: true,
),
];
class _RoadmapRow extends StatelessWidget {
const _RoadmapRow({
required this.item,
required this.theme,
required this.media,
});
final _RoadmapItem item;
final ChicoThemeData theme;
final ChicoMediaData media;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: media.space(ChicoSpace.space12)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsetsDirectional.only(
top: 6,
end: ChicoSpace.space12,
),
child: DecoratedBox(
decoration: BoxDecoration(
color: item.ready ? theme.colors.tint : theme.colors.separator,
shape: BoxShape.circle,
),
child: const SizedBox(width: 8, height: 8),
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ChicoText(item.category, variant: ChicoTextVariant.callout),
ChicoText(
item.widgets,
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
],
),
),
],
),
);
}
}