sd_utils 1.0.1
sd_utils: ^1.0.1 copied to clipboard
A Flutter package providing customizable chip UI components and utility formatters (date, amount, nullable handling)
import 'package:flutter/material.dart';
import 'package:sd_utils/sd_utils.dart';
void main() {
runApp(const ChipDemoApp());
}
/// A demo app showcasing all the Chip widgets.
class ChipDemoApp extends StatelessWidget {
const ChipDemoApp({super.key});
@override
Widget build(BuildContext context) {
final baseTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF0F9B8E)),
useMaterial3: true,
);
final darkBaseTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF0F9B8E),
brightness: Brightness.dark,
),
useMaterial3: true,
);
return MaterialApp(
title: 'Chip Widget Demo',
debugShowCheckedModeBanner: false,
theme: baseTheme.withChipTheme(SdChipThemeData.light(baseTheme)),
darkTheme: darkBaseTheme.withChipTheme(
SdChipThemeData.dark(darkBaseTheme),
),
themeMode: ThemeMode.system,
home: const ChipDemoScreen(),
);
}
}
/// A screen demonstrating all available chip widgets.
class ChipDemoScreen extends StatefulWidget {
const ChipDemoScreen({super.key});
@override
State<ChipDemoScreen> createState() => _ChipDemoScreenState();
}
class _ChipDemoScreenState extends State<ChipDemoScreen> {
bool _basicSelected = false;
bool _iconSelected = false;
bool _toggleValue = false;
String? _choiceSelectedId = 'medium';
Set<String> _multiSelectedIds = {'flutter'};
Set<String> _filterSelectedIds = {'flutter'};
late final TextEditingController _amountController;
late final TextEditingController _dateController;
@override
void initState() {
super.initState();
_amountController = TextEditingController(text: '12,50,000');
_dateController = TextEditingController(text: '20/04/2026');
_amountController.addListener(_handleFieldChange);
_dateController.addListener(_handleFieldChange);
}
@override
void dispose() {
_amountController.removeListener(_handleFieldChange);
_dateController.removeListener(_handleFieldChange);
_amountController.dispose();
_dateController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(title: const Text('Chip Widget Library')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeroCard(),
const SizedBox(height: 28),
_buildSectionHeader('Core Variants'),
const SizedBox(height: 8),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
BasicChip(
label: 'Default',
selected: _basicSelected,
onSelected: (value) => setState(() => _basicSelected = value),
),
const BasicChip(label: 'Stable'),
const BasicChip(label: 'Disabled', enabled: false),
IconChip(
label: 'Wallet',
icon: Icons.account_balance_wallet_outlined,
selected: _iconSelected,
onSelected: (value) => setState(() => _iconSelected = value),
),
const SelectableChip(
label: 'Verified',
icon: Icons.verified_rounded,
initialSelected: true,
),
ToggleChip(
label: 'Smart alerts',
value: _toggleValue,
onToggle: (value) => setState(() => _toggleValue = value),
),
],
),
const SizedBox(height: 28),
_buildSectionHeader('Group Selection'),
const SizedBox(height: 10),
ChoiceChipGroup(
chips: const [
ChoiceChipConfig(label: 'Small', id: 'small'),
ChoiceChipConfig(label: 'Medium', id: 'medium'),
ChoiceChipConfig(label: 'Large', id: 'large'),
],
selectedId: _choiceSelectedId,
onSelectionChanged: (id) =>
setState(() => _choiceSelectedId = id),
),
const SizedBox(height: 14),
MultiSelectChipGroup(
selectedIds: _multiSelectedIds,
onSelectionChanged: (ids) =>
setState(() => _multiSelectedIds = ids),
chips: const [
MultiSelectChipConfig(
label: 'Dart',
id: 'dart',
icon: Icons.code,
),
MultiSelectChipConfig(
label: 'Flutter',
id: 'flutter',
icon: Icons.flutter_dash,
),
MultiSelectChipConfig(
label: 'Payments',
id: 'payments',
icon: Icons.payments_outlined,
),
MultiSelectChipConfig(
label: 'Growth',
id: 'growth',
icon: Icons.trending_up_rounded,
),
],
),
const SizedBox(height: 14),
FilterChipGroup(
chips: const [
FilterChipConfig(
label: 'Flutter',
id: 'flutter',
icon: Icons.flutter_dash,
),
FilterChipConfig(label: 'Dart', id: 'dart', icon: Icons.code),
FilterChipConfig(
label: 'Firebase',
id: 'firebase',
icon: Icons.cloud_outlined,
),
],
selectedIds: _filterSelectedIds,
onSelectionChanged: (ids) =>
setState(() => _filterSelectedIds = ids),
),
const SizedBox(height: 28),
_buildSectionHeader('Advanced Examples'),
const SizedBox(height: 10),
_buildExampleCard(
title: 'Team Assignment',
subtitle:
'Avatar, input, and themed chips in one reusable block.',
child: Wrap(
spacing: 12,
runSpacing: 12,
children: [
const ContactChip(
name: 'Maya Chen',
initials: 'MC',
selected: true,
),
SdInputChip(
label: 'ops@acme.io',
icon: Icons.alternate_email_rounded,
selected: true,
onDelete: () {},
),
const SdInputChipWithAvatar(
label: 'Growth Pod',
selected: true,
avatar: ChipAvatar(initials: 'GP'),
),
],
),
),
const SizedBox(height: 16),
_buildExampleCard(
title: 'Premium Commerce Badges',
subtitle:
'Gradient, outlined, and toggle variants with stronger visual hierarchy.',
child: Wrap(
spacing: 12,
runSpacing: 12,
children: [
GradientChipBuilder.featured(
label: 'Featured',
icon: Icons.auto_awesome_rounded,
),
GradientChipBuilder.newBadge(label: 'New Drop'),
GradientChipBuilder.hotBadge(label: 'Hot Sale'),
GradientChipBuilder.premium(label: 'VIP'),
OutlinedChip(
label: 'Invite only',
icon: Icons.lock_outline_rounded,
selected: true,
borderColor: scheme.primary,
textColor: scheme.primary,
),
],
),
),
const SizedBox(height: 16),
_buildExampleCard(
title: 'Scoped Theme Override',
subtitle: 'Local chip theming without touching the app theme.',
child: SdChipTheme(
data: SdChipThemeData.light(Theme.of(context)).copyWith(
backgroundColor: const Color(0xFF101828),
selectedColor: const Color(0xFF16A34A),
textColor: Colors.white,
selectedTextColor: Colors.white,
borderColor: const Color(0xFF344054),
selectedBorderColor: const Color(0xFF16A34A),
borderRadius: BorderRadius.circular(24),
elevation: 4,
selectedElevation: 12,
),
child: Wrap(
spacing: 12,
runSpacing: 12,
children: const [
BasicChip(label: 'Risk Low'),
BasicChip(label: 'KYC Cleared', selected: true),
SelectableChip(
label: 'Auto Invest',
icon: Icons.bolt_rounded,
initialSelected: true,
),
],
),
),
),
const SizedBox(height: 28),
_buildSectionHeader('Formatter Utilities'),
const SizedBox(height: 10),
_buildExampleCard(
title: 'Date, amount, and null-safe helpers',
subtitle:
'Reusable formatters and extensions for consistent UI rendering.',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(
'API date',
DateFormatter.fromApi('2026-04-20T10:15:30Z'),
),
_buildInfoRow(
'Readable date',
DateTime(2026, 4, 20).toFormatted(pattern: 'dd MMM, yyyy'),
),
_buildInfoRow('Currency', 1250000.toCurrency()),
_buildInfoRow(
'Compact',
AmountFormatter.formatCompact(1250000),
),
_buildInfoRow('Nullable text', (null as String?).orDash()),
_buildInfoRow(
'Title case',
'sd utils mini sdk'.toTitleCase(),
),
],
),
),
const SizedBox(height: 16),
_buildExampleCard(
title: 'Interactive Input Formatters',
subtitle:
'Typing auto-inserts commas and date separators while preserving cursor position.',
child: Column(
children: [
TextField(
controller: _amountController,
keyboardType: TextInputType.number,
inputFormatters: [AmountInputFormatter()],
decoration: const InputDecoration(
labelText: 'Amount',
hintText: 'Enter amount',
prefixText: '₹ ',
),
),
const SizedBox(height: 14),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Preview: ${AmountFormatter.format(_parseNum(_amountController.text), decimalDigits: 0)}',
),
),
const SizedBox(height: 18),
TextField(
controller: _dateController,
keyboardType: TextInputType.number,
inputFormatters: [DateInputFormatter()],
decoration: const InputDecoration(
labelText: 'Date',
hintText: 'dd/MM/yyyy',
),
),
const SizedBox(height: 14),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Parsed: ${DateFormatter.fromApi(_dateController.text, inputPatterns: const ['"'
"'dd/MM/yyyy'"
'"'])}',
),
),
],
),
),
const SizedBox(height: 28),
_buildSectionHeader('Compatibility Variants'),
const SizedBox(height: 10),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
const OutlinedChip(
label: 'Outlined',
icon: Icons.sell_outlined,
),
const GradientChip(label: 'Launch mode'),
const DisabledChip(label: 'Unavailable'),
const ConditionalChip(label: 'Paused', isDisabled: true),
],
),
const SizedBox(height: 40),
],
),
),
);
}
Widget _buildHeroCard() {
final scheme = Theme.of(context).colorScheme;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(28),
gradient: LinearGradient(
colors: [scheme.primary, scheme.primary.withValues(alpha: 0.72)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Production-ready animated chips',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: scheme.onPrimary,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 8),
Text(
'BaseChip + theme extensions + scalable variants for fintech and commerce workflows.',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: scheme.onPrimary.withValues(alpha: 0.92),
),
),
const SizedBox(height: 18),
Wrap(
spacing: 12,
runSpacing: 12,
children: const [
GradientChip(
label: 'Animated',
icon: Icons.motion_photos_auto_rounded,
),
GradientChip(
label: 'Theme aware',
icon: Icons.dark_mode_outlined,
),
GradientChip(
label: 'Accessible',
icon: Icons.accessibility_new_rounded,
),
],
),
],
),
);
}
Widget _buildSectionHeader(String title) {
return Text(
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
);
}
Widget _buildExampleCard({
required String title,
required String subtitle,
required Widget child,
}) {
final scheme = Theme.of(context).colorScheme;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: scheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: scheme.outlineVariant),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700),
),
const SizedBox(height: 4),
Text(subtitle),
const SizedBox(height: 14),
child,
],
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
SizedBox(
width: 120,
child: Text(
label,
style: const TextStyle(fontWeight: FontWeight.w600),
),
),
Expanded(child: Text(value)),
],
),
);
}
num? _parseNum(String value) {
final sanitized = value.replaceAll(',', '').trim();
return num.tryParse(sanitized);
}
void _handleFieldChange() {
if (mounted) {
setState(() {});
}
}
}