diff --git a/packages/uni_ui/lib/calendar/calendar.dart b/packages/uni_ui/lib/calendar/calendar.dart new file mode 100644 index 000000000..3f77770e0 --- /dev/null +++ b/packages/uni_ui/lib/calendar/calendar.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:uni_ui/calendar/calendar_item.dart'; + +class CalendarLine extends StatelessWidget { + const CalendarLine({ + super.key, + required this.calendarItemsCount, + }); + + final int calendarItemsCount; + + @override + Widget build(BuildContext context) { + if (calendarItemsCount == 0) return Container(); + + return Container( + margin: EdgeInsets.only(top: 44), + child: Row( + children: [ + Container( + width: 60, + height: 4, + margin: EdgeInsets.only(right: 15), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: BorderRadius.only( + topRight: Radius.circular(2), + bottomRight: Radius.circular(2), + ), + ), + ), + ...List.filled( + calendarItemsCount - 1, + Container( + width: 120, + height: 4, + margin: EdgeInsets.symmetric(horizontal: 15), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: BorderRadius.circular(2)), + ), + ), + Container( + width: 60, + height: 4, + margin: EdgeInsets.only(left: 15), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(2), + bottomLeft: Radius.circular(2), + ), + ), + ), + ], + ), + ); + } +} + +class Calendar extends StatelessWidget { + const Calendar({ + super.key, + required this.items, + }); + + final List items; + + @override + Widget build(BuildContext context) { + // Row + SingleChildScrollView is used, instead of ListView, to avoid + // the widget from expanding vertically + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Stack( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: items, + ), + CalendarLine( + calendarItemsCount: items.length, + ), + ], + )); + } +} diff --git a/packages/uni_ui/lib/calendar/calendar_item.dart b/packages/uni_ui/lib/calendar/calendar_item.dart new file mode 100644 index 000000000..0081e557c --- /dev/null +++ b/packages/uni_ui/lib/calendar/calendar_item.dart @@ -0,0 +1,167 @@ +import 'package:figma_squircle/figma_squircle.dart'; +import 'package:flutter/material.dart'; + +class _CalendarItemDate extends StatelessWidget { + const _CalendarItemDate({this.eventPeriod, this.endYear}); + + final String? eventPeriod; + final String? endYear; + + @override + Widget build(BuildContext context) { + if (eventPeriod != null) { + return Column( + children: [ + Text( + eventPeriod!, + style: TextStyle( + fontSize: 15, + height: 1, + fontWeight: FontWeight.w500, + ), + ), + Text( + endYear ?? '', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + fontSize: 11, + ), + ), + ], + ); + } else { + return Padding( + padding: EdgeInsets.only(top: 10), + child: Text( + "TBD", + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + ); + } + } + +/* TODO: move this to uni_app later + static String monthToString(int month) { + // TODO: Support Portuguese + const strMonths = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" + ]; + return strMonths[month - 1]; + } + + static String parsePeriod(DateTimeRange period) { + final start = period.start, end = period.end; + + if (start.month == end.month) { + return start.day == end.day + ? "${start.day} ${monthToString(start.month)}" + : "${start.day} - ${end.day} ${monthToString(start.month)}"; + } else { + return "${start.day} ${monthToString(start.month)} - ${end.day} ${monthToString(end.month)}"; + } + } + + */ +} + +class CalendarItem extends StatelessWidget { + const CalendarItem({ + super.key, + required this.eventName, + this.eventPeriod, + this.endYear, + this.onTap, + }); + + final String eventName; + final String? eventPeriod; + final String? endYear; + final void Function()? onTap; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _CalendarItemDate( + eventPeriod: eventPeriod, + endYear: endYear, + ), + Stack( + alignment: Alignment.bottomCenter, + children: [ + Container( + margin: EdgeInsets.only(top: 5, bottom: 10), + width: 20, + height: 20, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: Theme.of(context).colorScheme.primary, + width: 4.0, + ), + ), + ), + Container( + width: 4, + height: 12, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(2), + bottomRight: Radius.circular(2)), + shape: BoxShape.rectangle, + color: Theme.of(context).primaryColor, + ), + ), + ], + ), + GestureDetector( + onTap: onTap, + child: Container( + margin: const EdgeInsets.only(left: 5, right: 5, bottom: 5), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 15), + width: 140, + decoration: ShapeDecoration( + color: Theme.of(context).colorScheme.secondary, + shape: SmoothRectangleBorder( + borderRadius: SmoothBorderRadius( + cornerRadius: 12, + cornerSmoothing: 1, + ), + ), + shadows: [ + BoxShadow( + color: Theme.of(context).colorScheme.shadow.withAlpha(0x3f), + blurRadius: 6, + ) + ], + ), + child: Text( + eventName, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontSize: 16, + fontWeight: FontWeight.w500, + height: 1, + ), + ), + ), + ), + ], + ); + } +} diff --git a/packages/uni_ui/lib/main.dart b/packages/uni_ui/lib/main.dart deleted file mode 100644 index fc1d906a2..000000000 --- a/packages/uni_ui/lib/main.dart +++ /dev/null @@ -1,329 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:uni_ui/timeline/timeline.dart'; - -void main() { - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - theme: ThemeData.light(), - home: Scaffold( - appBar: AppBar( - title: Text('Timeline Example'), - ), - body: Timeline( - content: [ - Container( - color: Colors.red[100], - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Content for Tab 1', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - SizedBox(height: 8), - Text( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' - 'Suspendisse eget tincidunt sapien. Phasellus sed ligula id ' - 'turpis vulputate efficitur. Donec ut arcu vel leo blandit ' - 'dictum. Cras ut massa nisi. Nulla facilisi. Quisque porta ' - 'lobortis diam, at interdum orci.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem ' - 'accusantium doloremque laudantium, totam rem aperiam, eaque ' - 'ipsa quae ab illo inventore veritatis et quasi architecto ' - 'beatae vitae dicta sunt explicabo.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'At vero eos et accusamus et iusto odio dignissimos ducimus ' - 'qui blanditiis praesentium voluptatum deleniti atque corrupti ' - 'quos dolores et quas molestias excepturi sint occaecati ' - 'cupiditate non provident.', - style: TextStyle(fontSize: 16), - ), - ], - ), - ), - Container( - color: Colors.green[100], - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Content for Tab 2', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - SizedBox(height: 8), - Text( - 'Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut ' - 'odit aut fugit, sed quia consequuntur magni dolores eos qui ' - 'ratione voluptatem sequi nesciunt.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, ' - 'consectetur, adipisci velit, sed quia non numquam eius modi ' - 'tempora incidunt ut labore et dolore magnam aliquam quaerat ' - 'voluptatem.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'Ut enim ad minima veniam, quis nostrum exercitationem ullam ' - 'corporis suscipit laboriosam, nisi ut aliquid ex ea commodi ' - 'consequatur?', - style: TextStyle(fontSize: 16), - ), - ], - ), - ), - Container( - color: Colors.blue[100], - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Content for Tab 3', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - SizedBox(height: 8), - Text( - 'Quis autem vel eum iure reprehenderit qui in ea voluptate ' - 'velit esse quam nihil molestiae consequatur, vel illum qui ' - 'dolorem eum fugiat quo voluptas nulla pariatur?', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'But I must explain to you how all this mistaken idea of ' - 'denouncing pleasure and praising pain was born and I will ' - 'give you a complete account of the system, and expound the ' - 'actual teachings of the great explorer of the truth.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'Nor again is there anyone who loves or pursues or desires to ' - 'obtain pain of itself, because it is pain, but because ' - 'occasionally circumstances occur in which toil and pain can ' - 'procure him some great pleasure.', - style: TextStyle(fontSize: 16), - ), - ], - ), - ), - Container( - color: Colors.red[100], - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Content for Tab 4', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - SizedBox(height: 8), - Text( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' - 'Suspendisse eget tincidunt sapien. Phasellus sed ligula id ' - 'turpis vulputate efficitur. Donec ut arcu vel leo blandit ' - 'dictum. Cras ut massa nisi. Nulla facilisi. Quisque porta ' - 'lobortis diam, at interdum orci.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem ' - 'accusantium doloremque laudantium, totam rem aperiam, eaque ' - 'ipsa quae ab illo inventore veritatis et quasi architecto ' - 'beatae vitae dicta sunt explicabo.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'At vero eos et accusamus et iusto odio dignissimos ducimus ' - 'qui blanditiis praesentium voluptatum deleniti atque corrupti ' - 'quos dolores et quas molestias excepturi sint occaecati ' - 'cupiditate non provident.', - style: TextStyle(fontSize: 16), - ), - ], - ), - ), - Container( - color: Colors.red[100], - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Content for Tab 5', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - SizedBox(height: 8), - Text( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' - 'Suspendisse eget tincidunt sapien. Phasellus sed ligula id ' - 'turpis vulputate efficitur. Donec ut arcu vel leo blandit ' - 'dictum. Cras ut massa nisi. Nulla facilisi. Quisque porta ' - 'lobortis diam, at interdum orci.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem ' - 'accusantium doloremque laudantium, totam rem aperiam, eaque ' - 'ipsa quae ab illo inventore veritatis et quasi architecto ' - 'beatae vitae dicta sunt explicabo.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'At vero eos et accusamus et iusto odio dignissimos ducimus ' - 'qui blanditiis praesentium voluptatum deleniti atque corrupti ' - 'quos dolores et quas molestias excepturi sint occaecati ' - 'cupiditate non provident.', - style: TextStyle(fontSize: 16), - ), - ], - ), - ), - Container( - color: Colors.red[100], - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Content for Tab 6', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - SizedBox(height: 8), - Text( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' - 'Suspendisse eget tincidunt sapien. Phasellus sed ligula id ' - 'turpis vulputate efficitur. Donec ut arcu vel leo blandit ' - 'dictum. Cras ut massa nisi. Nulla facilisi. Quisque porta ' - 'lobortis diam, at interdum orci.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem ' - 'accusantium doloremque laudantium, totam rem aperiam, eaque ' - 'ipsa quae ab illo inventore veritatis et quasi architecto ' - 'beatae vitae dicta sunt explicabo.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'At vero eos et accusamus et iusto odio dignissimos ducimus ' - 'qui blanditiis praesentium voluptatum deleniti atque corrupti ' - 'quos dolores et quas molestias excepturi sint occaecati ' - 'cupiditate non provident.', - style: TextStyle(fontSize: 16), - ), - ], - ), - ), - Container( - color: Colors.red[100], - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Content for Tab 7', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - SizedBox(height: 8), - Text( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' - 'Suspendisse eget tincidunt sapien. Phasellus sed ligula id ' - 'turpis vulputate efficitur. Donec ut arcu vel leo blandit ' - 'dictum. Cras ut massa nisi. Nulla facilisi. Quisque porta ' - 'lobortis diam, at interdum orci.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem ' - 'accusantium doloremque laudantium, totam rem aperiam, eaque ' - 'ipsa quae ab illo inventore veritatis et quasi architecto ' - 'beatae vitae dicta sunt explicabo.', - style: TextStyle(fontSize: 16), - ), - SizedBox(height: 16), - Text( - 'At vero eos et accusamus et iusto odio dignissimos ducimus ' - 'qui blanditiis praesentium voluptatum deleniti atque corrupti ' - 'quos dolores et quas molestias excepturi sint occaecati ' - 'cupiditate non provident.', - style: TextStyle(fontSize: 16), - ), - ], - ), - ), - ], - tabs: [ - Column( - children: [ - Text('Mon'), - Text('1'), - ], - ), - Column( - children: [ - Text('Tue'), - Text('2'), - ], - ), - Column( - children: [ - Text('Wed'), - Text('3'), - ], - ), - Column( - children: [ - Text('Thu'), - Text('4'), - ], - ), - Column( - children: [ - Text('Fri'), - Text('5'), - ], - ), - Column( - children: [ - Text('Sat'), - Text('6'), - ], - ), - Column( - children: [ - Text('Sun'), - Text('7'), - ], - ), - ], - ), - ), - ); - } -}