sd_utils 1.0.1 copy "sd_utils: ^1.0.1" to clipboard
sd_utils: ^1.0.1 copied to clipboard

A Flutter package providing customizable chip UI components and utility formatters (date, amount, nullable handling)

example/lib/main.dart

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(() {});
    }
  }
}
2
likes
155
points
7
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter package providing customizable chip UI components and utility formatters (date, amount, nullable handling)

Repository (GitHub)
View/report issues

Topics

#flutter #ui #chips #formatter #utils

License

MIT (license)

Dependencies

flutter, intl

More

Packages that depend on sd_utils