hsds_mobile 0.3.0
hsds_mobile: ^0.3.0 copied to clipboard
HeadSpace Design System — mobile component library (Flutter / Dart)
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:hsds_mobile/hsds_mobile.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'HSDS Mobile — Components',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF3D3A5C)),
useMaterial3: true,
),
home: const ComponentGallery(),
);
}
}
// ---------------------------------------------------------------------------
// Top-level gallery — tab bar switches between component sections
// ---------------------------------------------------------------------------
class ComponentGallery extends StatelessWidget {
const ComponentGallery({super.key});
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
appBar: AppBar(
title: const Text('HSDS Components'),
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
elevation: 0,
bottom: const TabBar(
tabs: [
Tab(text: 'Text Input'),
Tab(text: 'Bottom Nav'),
],
labelColor: Color(0xFF3D3A5C),
unselectedLabelColor: Color(0xFF63605D),
indicatorColor: Color(0xFF3D3A5C),
),
),
body: const TabBarView(
children: [
TextInputGallery(),
BottomNavGallery(),
],
),
),
);
}
}
// ---------------------------------------------------------------------------
// Text Input Gallery
// ---------------------------------------------------------------------------
class TextInputGallery extends StatelessWidget {
const TextInputGallery({super.key});
@override
Widget build(BuildContext context) {
return HsdsThemeProvider(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ── HsdsTextField ──────────────────────────────────────────────
_sectionLabel('HsdsTextField — States'),
const SizedBox(height: 16),
const HsdsTextField(label: 'Enabled'),
const SizedBox(height: 16),
const HsdsTextField(
label: 'With helper text',
helperText: "We'll never share your email",
),
const SizedBox(height: 16),
const HsdsTextField(
label: 'Error — empty',
error: true,
errorMessage: 'This field is required',
),
const SizedBox(height: 16),
const HsdsTextField(
label: 'Error — with message',
error: true,
errorMessage: 'Invalid email format',
initialValue: 'bad@',
),
const SizedBox(height: 16),
const HsdsTextField(
label: 'Disabled',
disabled: true,
initialValue: "Can't touch this",
),
const SizedBox(height: 16),
const HsdsTextField(
label: 'Read only',
readOnly: true,
initialValue: 'Read-only value',
),
const SizedBox(height: 32),
_sectionLabel('HsdsTextField — Password'),
const SizedBox(height: 16),
const HsdsTextField(
label: 'Password',
obscureText: true,
),
const SizedBox(height: 16),
const HsdsTextField(
label: 'Password — with value',
obscureText: true,
initialValue: 'supersecret',
),
const SizedBox(height: 32),
// ── HsdsTextArea ───────────────────────────────────────────────
_sectionLabel('HsdsTextArea — States'),
const SizedBox(height: 16),
const HsdsTextArea(label: 'Notes'),
const SizedBox(height: 16),
const HsdsTextArea(
label: 'Default (250 char limit)',
),
const SizedBox(height: 16),
const HsdsTextArea(
label: 'With value',
initialValue: 'Some initial content here.',
),
const SizedBox(height: 16),
const HsdsTextArea(
label: 'Error',
error: true,
errorMessage: 'Content is too long',
initialValue: 'This is way too long...',
),
const SizedBox(height: 16),
const HsdsTextArea(
label: 'Disabled',
disabled: true,
),
const SizedBox(height: 16),
const HsdsTextArea(
label: 'Custom limit',
maxLength: 100,
),
const SizedBox(height: 40),
],
),
),
);
}
}
Widget _sectionLabel(String text) {
return Text(
text.toUpperCase(),
style: HsdsTextStyles.mobileEyebrow,
);
}
// ---------------------------------------------------------------------------
// Bottom Nav Gallery (unchanged)
// ---------------------------------------------------------------------------
class BottomNavGallery extends StatefulWidget {
const BottomNavGallery({super.key});
@override
State<BottomNavGallery> createState() => _BottomNavGalleryState();
}
class _BottomNavGalleryState extends State<BottomNavGallery> {
int _sel3 = 0;
int _sel5 = 0;
int _selNoIndicator = 0;
static final _items3 = [
HsdsBottomNavItem(
label: 'Today',
icon: HsdsBottomNavIcons.today.inactive,
activeIcon: HsdsBottomNavIcons.today.active,
),
HsdsBottomNavItem(
label: 'Explore',
icon: HsdsBottomNavIcons.explore.inactive,
activeIcon: HsdsBottomNavIcons.explore.active,
),
HsdsBottomNavItem(
label: 'Profile',
icon: HsdsBottomNavIcons.profile.inactive,
activeIcon: HsdsBottomNavIcons.profile.active,
),
];
static final _items5 = [
HsdsBottomNavItem(
label: 'Today',
icon: HsdsBottomNavIcons.today.inactive,
activeIcon: HsdsBottomNavIcons.today.active,
),
HsdsBottomNavItem(
label: 'Explore',
icon: HsdsBottomNavIcons.explore.inactive,
activeIcon: HsdsBottomNavIcons.explore.active,
),
HsdsBottomNavItem(
label: 'Ebb',
icon: HsdsBottomNavIcons.ebb.inactive,
activeIcon: HsdsBottomNavIcons.ebb.active,
),
HsdsBottomNavItem(
label: 'Care',
icon: HsdsBottomNavIcons.care.inactive,
activeIcon: HsdsBottomNavIcons.care.active,
),
HsdsBottomNavItem(
label: 'Profile',
icon: HsdsBottomNavIcons.profile.inactive,
activeIcon: HsdsBottomNavIcons.profile.active,
),
];
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_VariantCard(
label: '3 tabs — with home indicator',
child: HsdsBottomNav(
items: _items3,
selectedIndex: _sel3,
onTabSelected: (i) => setState(() => _sel3 = i),
),
),
_VariantCard(
label: '5 tabs — with home indicator',
child: HsdsBottomNav(
items: _items5,
selectedIndex: _sel5,
onTabSelected: (i) => setState(() => _sel5 = i),
),
),
_VariantCard(
label: '3 tabs — no home indicator',
child: HsdsBottomNav(
items: _items3,
selectedIndex: _selNoIndicator,
onTabSelected: (i) => setState(() => _selNoIndicator = i),
showHomeIndicator: false,
),
),
],
),
);
}
}
// ---------------------------------------------------------------------------
// Helper widget — section label + full-width component (no card frame)
// ---------------------------------------------------------------------------
class _VariantCard extends StatelessWidget {
const _VariantCard({required this.label, required this.child});
final String label;
final Widget child;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 32, 16, 8),
child: Text(
label,
style: HsdsTextStyles.mobileEyebrow,
),
),
child,
],
);
}
}