From 417430d9965e2d67749bbf72ad69708a2459a84a Mon Sep 17 00:00:00 2001 From: Mike Kell Date: Sat, 4 Apr 2026 12:08:51 -0400 Subject: [PATCH] feat(flutter): add Kell Creations design system and inventory first slice --- .../apps/kell_web/lib/main.dart | 119 ++---------------- .../apps/kell_web/test/widget_test.dart | 28 +---- .../design_system/lib/design_system.dart | 12 +- .../lib/src/theme/kc_colors.dart | 17 +++ .../lib/src/theme/kc_spacing.dart | 7 ++ .../design_system/lib/src/theme/kc_theme.dart | 47 +++++++ .../lib/src/widgets/kc_card.dart | 24 ++++ .../lib/src/widgets/kc_status_chip.dart | 27 ++++ .../test/design_system_test.dart | 14 +-- .../lib/feature_inventory.dart | 10 +- .../src/application/get_inventory_items.dart | 10 ++ .../src/application/inventory_controller.dart | 28 +++++ .../src/data/fake_inventory_repository.dart | 61 +++++++++ .../lib/src/domain/inventory_item.dart | 19 +++ .../lib/src/domain/inventory_repository.dart | 5 + .../lib/src/domain/inventory_status.dart | 1 + .../lib/src/presentation/inventory_page.dart | 88 +++++++++++++ .../widgets/inventory_item_card.dart | 45 +++++++ .../packages/feature_inventory/pubspec.yaml | 2 + 19 files changed, 414 insertions(+), 150 deletions(-) create mode 100644 kell_creations_apps/packages/design_system/lib/src/theme/kc_colors.dart create mode 100644 kell_creations_apps/packages/design_system/lib/src/theme/kc_spacing.dart create mode 100644 kell_creations_apps/packages/design_system/lib/src/theme/kc_theme.dart create mode 100644 kell_creations_apps/packages/design_system/lib/src/widgets/kc_card.dart create mode 100644 kell_creations_apps/packages/design_system/lib/src/widgets/kc_status_chip.dart create mode 100644 kell_creations_apps/packages/feature_inventory/lib/src/application/get_inventory_items.dart create mode 100644 kell_creations_apps/packages/feature_inventory/lib/src/application/inventory_controller.dart create mode 100644 kell_creations_apps/packages/feature_inventory/lib/src/data/fake_inventory_repository.dart create mode 100644 kell_creations_apps/packages/feature_inventory/lib/src/domain/inventory_item.dart create mode 100644 kell_creations_apps/packages/feature_inventory/lib/src/domain/inventory_repository.dart create mode 100644 kell_creations_apps/packages/feature_inventory/lib/src/domain/inventory_status.dart create mode 100644 kell_creations_apps/packages/feature_inventory/lib/src/presentation/inventory_page.dart create mode 100644 kell_creations_apps/packages/feature_inventory/lib/src/presentation/widgets/inventory_item_card.dart diff --git a/kell_creations_apps/apps/kell_web/lib/main.dart b/kell_creations_apps/apps/kell_web/lib/main.dart index 244a702..9d14739 100644 --- a/kell_creations_apps/apps/kell_web/lib/main.dart +++ b/kell_creations_apps/apps/kell_web/lib/main.dart @@ -1,122 +1,21 @@ +import 'package:design_system/design_system.dart'; +import 'package:feature_inventory/feature_inventory.dart'; import 'package:flutter/material.dart'; void main() { - runApp(const MyApp()); + runApp(const KellWebApp()); } -class MyApp extends StatelessWidget { - const MyApp({super.key}); +class KellWebApp extends StatelessWidget { + const KellWebApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: .fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: .center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), + title: 'Kell Creations', + debugShowCheckedModeBanner: false, + theme: buildKcTheme(), + home: const InventoryPage(), ); } } diff --git a/kell_creations_apps/apps/kell_web/test/widget_test.dart b/kell_creations_apps/apps/kell_web/test/widget_test.dart index cc76849..58145d3 100644 --- a/kell_creations_apps/apps/kell_web/test/widget_test.dart +++ b/kell_creations_apps/apps/kell_web/test/widget_test.dart @@ -1,30 +1,12 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; - import 'package:kell_web/main.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + testWidgets('app renders inventory page', (WidgetTester tester) async { + await tester.pumpWidget(const KellWebApp()); + await tester.pumpAndSettle(); - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + expect(find.text('Kell Creations'), findsOneWidget); + expect(find.text('Inventory'), findsOneWidget); }); } diff --git a/kell_creations_apps/packages/design_system/lib/design_system.dart b/kell_creations_apps/packages/design_system/lib/design_system.dart index 298576d..269b6f5 100644 --- a/kell_creations_apps/packages/design_system/lib/design_system.dart +++ b/kell_creations_apps/packages/design_system/lib/design_system.dart @@ -1,5 +1,7 @@ -/// A Calculator. -class Calculator { - /// Returns [value] plus 1. - int addOne(int value) => value + 1; -} +library; + +export 'src/theme/kc_colors.dart'; +export 'src/theme/kc_spacing.dart'; +export 'src/theme/kc_theme.dart'; +export 'src/widgets/kc_card.dart'; +export 'src/widgets/kc_status_chip.dart'; diff --git a/kell_creations_apps/packages/design_system/lib/src/theme/kc_colors.dart b/kell_creations_apps/packages/design_system/lib/src/theme/kc_colors.dart new file mode 100644 index 0000000..2fcc09b --- /dev/null +++ b/kell_creations_apps/packages/design_system/lib/src/theme/kc_colors.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +abstract final class KcColors { + static const skyBlue = Color(0xFF55DDE0); + static const denimBlue = Color(0xFF33658A); + static const deepTeal = Color(0xFF2F4858); + static const honeyGold = Color(0xFFF6AE2D); + static const sunsetOrange = Color(0xFFF26419); + + static const background = Color(0xFFF8FBFC); + static const surface = Colors.white; + static const border = Color(0xFFD9E4EA); + static const success = Color(0xFF2E7D32); + static const warning = Color(0xFFF9A825); + static const danger = Color(0xFFC62828); + static const neutral = Color(0xFF607D8B); +} diff --git a/kell_creations_apps/packages/design_system/lib/src/theme/kc_spacing.dart b/kell_creations_apps/packages/design_system/lib/src/theme/kc_spacing.dart new file mode 100644 index 0000000..653e121 --- /dev/null +++ b/kell_creations_apps/packages/design_system/lib/src/theme/kc_spacing.dart @@ -0,0 +1,7 @@ +abstract final class KcSpacing { + static const xs = 4.0; + static const sm = 8.0; + static const md = 16.0; + static const lg = 24.0; + static const xl = 32.0; +} diff --git a/kell_creations_apps/packages/design_system/lib/src/theme/kc_theme.dart b/kell_creations_apps/packages/design_system/lib/src/theme/kc_theme.dart new file mode 100644 index 0000000..82b5cab --- /dev/null +++ b/kell_creations_apps/packages/design_system/lib/src/theme/kc_theme.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'kc_colors.dart'; + +ThemeData buildKcTheme() { + final base = ThemeData(useMaterial3: true); + + return base.copyWith( + scaffoldBackgroundColor: KcColors.background, + colorScheme: ColorScheme.fromSeed( + seedColor: KcColors.denimBlue, + primary: KcColors.denimBlue, + secondary: KcColors.skyBlue, + surface: KcColors.surface, + ), + appBarTheme: const AppBarTheme( + backgroundColor: KcColors.surface, + foregroundColor: KcColors.deepTeal, + elevation: 0, + centerTitle: false, + ), + cardTheme: const CardThemeData( + color: KcColors.surface, + elevation: 0, + margin: EdgeInsets.zero, + ), + textTheme: base.textTheme.copyWith( + headlineMedium: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.w700, + color: KcColors.deepTeal, + ), + titleLarge: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: KcColors.deepTeal, + ), + bodyLarge: const TextStyle( + fontSize: 16, + color: KcColors.deepTeal, + ), + bodyMedium: const TextStyle( + fontSize: 14, + color: KcColors.deepTeal, + ), + ), + ); +} \ No newline at end of file diff --git a/kell_creations_apps/packages/design_system/lib/src/widgets/kc_card.dart b/kell_creations_apps/packages/design_system/lib/src/widgets/kc_card.dart new file mode 100644 index 0000000..2ea1340 --- /dev/null +++ b/kell_creations_apps/packages/design_system/lib/src/widgets/kc_card.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import '../theme/kc_colors.dart'; +import '../theme/kc_spacing.dart'; + +class KcCard extends StatelessWidget { + final Widget child; + final EdgeInsetsGeometry? padding; + + const KcCard({super.key, required this.child, this.padding}); + + @override + Widget build(BuildContext context) { + return Container( + padding: padding ?? const EdgeInsets.all(KcSpacing.md), + decoration: BoxDecoration( + color: KcColors.surface, + border: Border.all(color: KcColors.border), + borderRadius: BorderRadius.circular(16), + boxShadow: const [BoxShadow(blurRadius: 8, offset: Offset(0, 2), color: Color(0x11000000))], + ), + child: child, + ); + } +} diff --git a/kell_creations_apps/packages/design_system/lib/src/widgets/kc_status_chip.dart b/kell_creations_apps/packages/design_system/lib/src/widgets/kc_status_chip.dart new file mode 100644 index 0000000..69d2736 --- /dev/null +++ b/kell_creations_apps/packages/design_system/lib/src/widgets/kc_status_chip.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import '../theme/kc_colors.dart'; + +class KcStatusChip extends StatelessWidget { + final String label; + final Color background; + final Color foreground; + + const KcStatusChip({ + super.key, + required this.label, + required this.background, + required this.foreground, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration(color: background, borderRadius: BorderRadius.circular(999)), + child: Text( + label, + style: TextStyle(color: foreground, fontWeight: FontWeight.w600, fontSize: 12), + ), + ); + } +} diff --git a/kell_creations_apps/packages/design_system/test/design_system_test.dart b/kell_creations_apps/packages/design_system/test/design_system_test.dart index c2beb8d..58145d3 100644 --- a/kell_creations_apps/packages/design_system/test/design_system_test.dart +++ b/kell_creations_apps/packages/design_system/test/design_system_test.dart @@ -1,12 +1,12 @@ import 'package:flutter_test/flutter_test.dart'; - -import 'package:design_system/design_system.dart'; +import 'package:kell_web/main.dart'; void main() { - test('adds one to input values', () { - final calculator = Calculator(); - expect(calculator.addOne(2), 3); - expect(calculator.addOne(-7), -6); - expect(calculator.addOne(0), 1); + testWidgets('app renders inventory page', (WidgetTester tester) async { + await tester.pumpWidget(const KellWebApp()); + await tester.pumpAndSettle(); + + expect(find.text('Kell Creations'), findsOneWidget); + expect(find.text('Inventory'), findsOneWidget); }); } diff --git a/kell_creations_apps/packages/feature_inventory/lib/feature_inventory.dart b/kell_creations_apps/packages/feature_inventory/lib/feature_inventory.dart index 298576d..d6dbd7b 100644 --- a/kell_creations_apps/packages/feature_inventory/lib/feature_inventory.dart +++ b/kell_creations_apps/packages/feature_inventory/lib/feature_inventory.dart @@ -1,5 +1,5 @@ -/// A Calculator. -class Calculator { - /// Returns [value] plus 1. - int addOne(int value) => value + 1; -} +library; + +export 'src/presentation/inventory_page.dart'; +export 'src/domain/inventory_item.dart'; +export 'src/domain/inventory_status.dart'; diff --git a/kell_creations_apps/packages/feature_inventory/lib/src/application/get_inventory_items.dart b/kell_creations_apps/packages/feature_inventory/lib/src/application/get_inventory_items.dart new file mode 100644 index 0000000..c8b2f22 --- /dev/null +++ b/kell_creations_apps/packages/feature_inventory/lib/src/application/get_inventory_items.dart @@ -0,0 +1,10 @@ +import '../domain/inventory_item.dart'; +import '../domain/inventory_repository.dart'; + +class GetInventoryItems { + final InventoryRepository repository; + + GetInventoryItems(this.repository); + + Future> call() => repository.getInventoryItems(); +} diff --git a/kell_creations_apps/packages/feature_inventory/lib/src/application/inventory_controller.dart b/kell_creations_apps/packages/feature_inventory/lib/src/application/inventory_controller.dart new file mode 100644 index 0000000..7e2363c --- /dev/null +++ b/kell_creations_apps/packages/feature_inventory/lib/src/application/inventory_controller.dart @@ -0,0 +1,28 @@ +import 'package:flutter/foundation.dart'; +import '../domain/inventory_item.dart'; +import 'get_inventory_items.dart'; + +class InventoryController extends ChangeNotifier { + final GetInventoryItems _getInventoryItems; + + InventoryController(this._getInventoryItems); + + bool isLoading = false; + List items = []; + Object? error; + + Future load() async { + isLoading = true; + error = null; + notifyListeners(); + + try { + items = await _getInventoryItems(); + } catch (e) { + error = e; + } finally { + isLoading = false; + notifyListeners(); + } + } +} diff --git a/kell_creations_apps/packages/feature_inventory/lib/src/data/fake_inventory_repository.dart b/kell_creations_apps/packages/feature_inventory/lib/src/data/fake_inventory_repository.dart new file mode 100644 index 0000000..9d09ec7 --- /dev/null +++ b/kell_creations_apps/packages/feature_inventory/lib/src/data/fake_inventory_repository.dart @@ -0,0 +1,61 @@ +import '../domain/inventory_item.dart'; +import '../domain/inventory_repository.dart'; +import '../domain/inventory_status.dart'; + +class FakeInventoryRepository implements InventoryRepository { + @override + Future> getInventoryItems() async { + await Future.delayed(const Duration(milliseconds: 300)); + + return const [ + InventoryItem( + id: '1', + sku: 'BC-FLR-001', + name: 'Floral Bowl Cozy', + quantityOnHand: 18, + unitPrice: 12.99, + status: InventoryStatus.inStock, + ), + InventoryItem( + id: '2', + sku: 'CS-CIT-002', + name: 'Citrus Coaster Set', + quantityOnHand: 7, + unitPrice: 16.50, + status: InventoryStatus.lowStock, + ), + InventoryItem( + id: '3', + sku: 'NL-OCN-003', + name: 'Ocean Nightlight', + quantityOnHand: 0, + unitPrice: 19.99, + status: InventoryStatus.outOfStock, + ), + InventoryItem( + id: '4', + sku: 'JG-BLU-004', + name: 'Fabric Jar Gripper', + quantityOnHand: 23, + unitPrice: 8.50, + status: InventoryStatus.inStock, + ), + InventoryItem( + id: '5', + sku: 'SH-SUN-005', + name: 'Skillet Handle Sleeve', + quantityOnHand: 5, + unitPrice: 10.99, + status: InventoryStatus.lowStock, + ), + InventoryItem( + id: '6', + sku: 'SC-SUB-006', + name: 'Sublimated Slate Coaster', + quantityOnHand: 0, + unitPrice: 14.99, + status: InventoryStatus.draft, + ), + ]; + } +} diff --git a/kell_creations_apps/packages/feature_inventory/lib/src/domain/inventory_item.dart b/kell_creations_apps/packages/feature_inventory/lib/src/domain/inventory_item.dart new file mode 100644 index 0000000..5e9e393 --- /dev/null +++ b/kell_creations_apps/packages/feature_inventory/lib/src/domain/inventory_item.dart @@ -0,0 +1,19 @@ +import 'inventory_status.dart'; + +class InventoryItem { + final String id; + final String sku; + final String name; + final int quantityOnHand; + final double unitPrice; + final InventoryStatus status; + + const InventoryItem({ + required this.id, + required this.sku, + required this.name, + required this.quantityOnHand, + required this.unitPrice, + required this.status, + }); +} diff --git a/kell_creations_apps/packages/feature_inventory/lib/src/domain/inventory_repository.dart b/kell_creations_apps/packages/feature_inventory/lib/src/domain/inventory_repository.dart new file mode 100644 index 0000000..f5c716f --- /dev/null +++ b/kell_creations_apps/packages/feature_inventory/lib/src/domain/inventory_repository.dart @@ -0,0 +1,5 @@ +import 'inventory_item.dart'; + +abstract class InventoryRepository { + Future> getInventoryItems(); +} diff --git a/kell_creations_apps/packages/feature_inventory/lib/src/domain/inventory_status.dart b/kell_creations_apps/packages/feature_inventory/lib/src/domain/inventory_status.dart new file mode 100644 index 0000000..964c65f --- /dev/null +++ b/kell_creations_apps/packages/feature_inventory/lib/src/domain/inventory_status.dart @@ -0,0 +1 @@ +enum InventoryStatus { inStock, lowStock, outOfStock, draft } diff --git a/kell_creations_apps/packages/feature_inventory/lib/src/presentation/inventory_page.dart b/kell_creations_apps/packages/feature_inventory/lib/src/presentation/inventory_page.dart new file mode 100644 index 0000000..c184abd --- /dev/null +++ b/kell_creations_apps/packages/feature_inventory/lib/src/presentation/inventory_page.dart @@ -0,0 +1,88 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import '../application/get_inventory_items.dart'; +import '../application/inventory_controller.dart'; +import '../data/fake_inventory_repository.dart'; +import 'widgets/inventory_item_card.dart'; + +class InventoryPage extends StatefulWidget { + const InventoryPage({super.key}); + + @override + State createState() => _InventoryPageState(); +} + +class _InventoryPageState extends State { + late final InventoryController controller; + + @override + void initState() { + super.initState(); + controller = InventoryController(GetInventoryItems(FakeInventoryRepository())); + controller.load(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, _) { + return Scaffold( + appBar: AppBar(title: const Text('Kell Creations')), + body: Padding( + padding: const EdgeInsets.all(KcSpacing.lg), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Inventory', style: Theme.of(context).textTheme.headlineMedium), + const SizedBox(height: KcSpacing.sm), + Text( + 'Manage handmade products, stock levels, and readiness.', + style: Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(height: KcSpacing.lg), + if (controller.isLoading) + const Expanded(child: Center(child: CircularProgressIndicator())) + else if (controller.error != null) + Expanded(child: Center(child: Text('Failed to load inventory data.'))) + else + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + final width = constraints.maxWidth; + int crossAxisCount = 1; + if (width >= 1200) { + crossAxisCount = 3; + } else if (width >= 700) { + crossAxisCount = 2; + } + + return GridView.builder( + itemCount: controller.items.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + crossAxisSpacing: KcSpacing.md, + mainAxisSpacing: KcSpacing.md, + childAspectRatio: 1.5, + ), + itemBuilder: (context, index) { + return InventoryItemCard(item: controller.items[index]); + }, + ); + }, + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/kell_creations_apps/packages/feature_inventory/lib/src/presentation/widgets/inventory_item_card.dart b/kell_creations_apps/packages/feature_inventory/lib/src/presentation/widgets/inventory_item_card.dart new file mode 100644 index 0000000..814d0e8 --- /dev/null +++ b/kell_creations_apps/packages/feature_inventory/lib/src/presentation/widgets/inventory_item_card.dart @@ -0,0 +1,45 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import '../../domain/inventory_item.dart'; +import '../../domain/inventory_status.dart'; + +class InventoryItemCard extends StatelessWidget { + final InventoryItem item; + + const InventoryItemCard({super.key, required this.item}); + + @override + Widget build(BuildContext context) { + final (label, bg, fg) = _statusStyle(item.status); + + return KcCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(item.name, style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: KcSpacing.sm), + Text('SKU: ${item.sku}'), + const SizedBox(height: KcSpacing.md), + KcStatusChip(label: label, background: bg, foreground: fg), + const SizedBox(height: KcSpacing.md), + Text('Quantity on hand: ${item.quantityOnHand}'), + const SizedBox(height: KcSpacing.sm), + Text('Unit price: \$${item.unitPrice.toStringAsFixed(2)}'), + ], + ), + ); + } + + (String, Color, Color) _statusStyle(InventoryStatus status) { + switch (status) { + case InventoryStatus.inStock: + return ('In stock', const Color(0xFFE8F5E9), KcColors.success); + case InventoryStatus.lowStock: + return ('Low stock', const Color(0xFFFFF8E1), KcColors.warning); + case InventoryStatus.outOfStock: + return ('Out of stock', const Color(0xFFFFEBEE), KcColors.danger); + case InventoryStatus.draft: + return ('Draft', const Color(0xFFECEFF1), KcColors.neutral); + } + } +} diff --git a/kell_creations_apps/packages/feature_inventory/pubspec.yaml b/kell_creations_apps/packages/feature_inventory/pubspec.yaml index dd0625e..a53c66c 100644 --- a/kell_creations_apps/packages/feature_inventory/pubspec.yaml +++ b/kell_creations_apps/packages/feature_inventory/pubspec.yaml @@ -10,6 +10,8 @@ environment: dependencies: flutter: sdk: flutter + design_system: + path: ../design_system dev_dependencies: flutter_test: