From 95e1c47885819bb27fe9efd96ad1f98f47b768cb Mon Sep 17 00:00:00 2001 From: Hub <79840500+GittHub-d@users.noreply.github.com> Date: Fri, 24 May 2024 13:46:24 +0300 Subject: [PATCH] feat: [MDS-1140] Create Colors page (#404) Co-authored-by: Birgitt Majas --- .../src/storybook/common/color_options.dart | 220 ++++++++----- .../storybook/common/pages/colors_page.dart | 290 ++++++++++++++++++ .../src/storybook/common/pages/home_page.dart | 1 - .../common/widgets/colors_page_options.dart | 35 +++ .../storybook/common/widgets/page_footer.dart | 2 + .../lib/src/storybook/routing/app_router.dart | 10 + .../routing/route_aware_stories.dart | 9 + 7 files changed, 484 insertions(+), 83 deletions(-) create mode 100644 example/lib/src/storybook/common/pages/colors_page.dart create mode 100644 example/lib/src/storybook/common/widgets/colors_page_options.dart diff --git a/example/lib/src/storybook/common/color_options.dart b/example/lib/src/storybook/common/color_options.dart index 619ed764..f71e6352 100644 --- a/example/lib/src/storybook/common/color_options.dart +++ b/example/lib/src/storybook/common/color_options.dart @@ -3,94 +3,150 @@ import 'package:flutter/material.dart'; import 'package:moon_design/moon_design.dart'; import 'package:storybook_flutter/storybook_flutter.dart'; -// The combination of "indexes" and colors in the colorTable() look-up table (LUT) is designed to circumvent -// the issue of a stale closure that would occur with the knob otherwise. +enum MoonColor { + piccolo, + hit, + beerus, + goku, + gohan, + bulma, + trunks, + goten, + popo, + jiren, + heles, + zeno, + krillin, + krillin60, + krillin10, + chichi, + chichi60, + chichi10, + roshi, + roshi60, + roshi10, + frieza, + frieza60, + frieza10, + dodoria, + dodoria60, + dodoria10, + cell, + cell60, + cell10, + raditz, + raditz60, + raditz10, + nappa, + nappa60, + nappa10, + whis, + whis60, + whis10, +} + +List mainColorsList = [ + MoonColor.piccolo, + MoonColor.hit, + MoonColor.beerus, + MoonColor.goku, + MoonColor.gohan, + MoonColor.bulma, + MoonColor.trunks, + MoonColor.goten, + MoonColor.popo, + MoonColor.jiren, + MoonColor.heles, + MoonColor.zeno, +]; + +List supportiveColorsList = [ + MoonColor.krillin, + MoonColor.krillin60, + MoonColor.krillin10, + MoonColor.chichi, + MoonColor.chichi60, + MoonColor.chichi10, + MoonColor.roshi, + MoonColor.roshi60, + MoonColor.roshi10, + MoonColor.cell, + MoonColor.cell60, + MoonColor.cell10, + MoonColor.dodoria, + MoonColor.dodoria60, + MoonColor.dodoria10, + MoonColor.frieza, + MoonColor.frieza60, + MoonColor.frieza10, + MoonColor.nappa, + MoonColor.nappa60, + MoonColor.nappa10, + MoonColor.raditz, + MoonColor.raditz60, + MoonColor.raditz10, + MoonColor.whis, + MoonColor.whis60, + MoonColor.whis10, +]; + +Color getColor(BuildContext context, MoonColor color) => switch (color) { + MoonColor.piccolo => context.moonColors!.piccolo, + MoonColor.hit => context.moonColors!.hit, + MoonColor.beerus => context.moonColors!.beerus, + MoonColor.goku => context.moonColors!.goku, + MoonColor.gohan => context.moonColors!.gohan, + MoonColor.bulma => context.moonColors!.bulma, + MoonColor.trunks => context.moonColors!.trunks, + MoonColor.goten => context.moonColors!.goten, + MoonColor.popo => context.moonColors!.popo, + MoonColor.jiren => context.moonColors!.jiren, + MoonColor.heles => context.moonColors!.heles, + MoonColor.zeno => context.moonColors!.zeno, + MoonColor.krillin => context.moonColors!.krillin, + MoonColor.krillin60 => context.moonColors!.krillin60, + MoonColor.krillin10 => context.moonColors!.krillin10, + MoonColor.chichi => context.moonColors!.chichi, + MoonColor.chichi60 => context.moonColors!.chichi60, + MoonColor.chichi10 => context.moonColors!.chichi10, + MoonColor.roshi => context.moonColors!.roshi, + MoonColor.roshi60 => context.moonColors!.roshi60, + MoonColor.roshi10 => context.moonColors!.roshi10, + MoonColor.frieza => context.moonColors!.frieza, + MoonColor.frieza60 => context.moonColors!.frieza60, + MoonColor.frieza10 => context.moonColors!.frieza10, + MoonColor.dodoria => context.moonColors!.dodoria, + MoonColor.dodoria60 => context.moonColors!.dodoria60, + MoonColor.dodoria10 => context.moonColors!.dodoria10, + MoonColor.cell => context.moonColors!.cell, + MoonColor.cell60 => context.moonColors!.cell60, + MoonColor.cell10 => context.moonColors!.cell10, + MoonColor.raditz => context.moonColors!.raditz, + MoonColor.raditz60 => context.moonColors!.raditz60, + MoonColor.raditz10 => context.moonColors!.raditz10, + MoonColor.nappa => context.moonColors!.nappa, + MoonColor.nappa60 => context.moonColors!.nappa60, + MoonColor.nappa10 => context.moonColors!.nappa10, + MoonColor.whis => context.moonColors!.whis, + MoonColor.whis60 => context.moonColors!.whis60, + MoonColor.whis10 => context.moonColors!.whis10, + }; /// The index of the color options. -List> colorOptions = const [ - Option(label: "piccolo", value: 0), - Option(label: "hit", value: 1), - Option(label: "beerus", value: 2), - Option(label: "goku", value: 3), - Option(label: "gohan", value: 4), - Option(label: "bulma", value: 5), - Option(label: "trunks", value: 6), - Option(label: "goten", value: 7), - Option(label: "popo", value: 8), - Option(label: "jiren", value: 9), - Option(label: "heles", value: 10), - Option(label: "zeno", value: 11), - Option(label: "krillin", value: 12), - Option(label: "krillin60", value: 13), - Option(label: "krillin10", value: 14), - Option(label: "chichi", value: 15), - Option(label: "chichi60", value: 16), - Option(label: "chichi10", value: 17), - Option(label: "roshi", value: 18), - Option(label: "roshi60", value: 19), - Option(label: "roshi10", value: 20), - Option(label: "frieza", value: 21), - Option(label: "frieza60", value: 22), - Option(label: "frieza10", value: 23), - Option(label: "dodoria", value: 24), - Option(label: "dodoria60", value: 25), - Option(label: "dodoria10", value: 26), - Option(label: "cell", value: 27), - Option(label: "cell60", value: 28), - Option(label: "cell10", value: 29), - Option(label: "raditz", value: 30), - Option(label: "raditz60", value: 31), - Option(label: "raditz10", value: 32), - Option(label: "nappa", value: 33), - Option(label: "nappa60", value: 34), - Option(label: "nappa10", value: 35), - Option(label: "whis", value: 36), - Option(label: "whis60", value: 37), - Option(label: "whis10", value: 38), - Option(label: "transparent", value: 39), +List> colorOptions = [ + ...MoonColor.values.map( + (MoonColor color) => Option(label: color.name, value: color.index), + ), + Option(label: 'transparent', value: MoonColor.values.length), ]; +// The combination of "indexes" and colors in the colorTable() look-up table +// (LUT) is designed to circumvent the issue of a stale closure that would +// occur with the knob otherwise. + /// The look-up table for the color options. List colorTable(BuildContext context) => [ - context.moonColors!.piccolo, - context.moonColors!.hit, - context.moonColors!.beerus, - context.moonColors!.goku, - context.moonColors!.gohan, - context.moonColors!.bulma, - context.moonColors!.trunks, - context.moonColors!.goten, - context.moonColors!.popo, - context.moonColors!.jiren, - context.moonColors!.heles, - context.moonColors!.zeno, - context.moonColors!.krillin, - context.moonColors!.krillin60, - context.moonColors!.krillin10, - context.moonColors!.chichi, - context.moonColors!.chichi60, - context.moonColors!.chichi10, - context.moonColors!.roshi, - context.moonColors!.roshi60, - context.moonColors!.roshi10, - context.moonColors!.frieza, - context.moonColors!.frieza60, - context.moonColors!.frieza10, - context.moonColors!.dodoria, - context.moonColors!.dodoria60, - context.moonColors!.dodoria10, - context.moonColors!.cell, - context.moonColors!.cell60, - context.moonColors!.cell10, - context.moonColors!.raditz, - context.moonColors!.raditz60, - context.moonColors!.raditz10, - context.moonColors!.nappa, - context.moonColors!.nappa60, - context.moonColors!.nappa10, - context.moonColors!.whis, - context.moonColors!.whis60, - context.moonColors!.whis10, + ...MoonColor.values.map((MoonColor color) => getColor(context, color)), Colors.transparent, null, ]; diff --git a/example/lib/src/storybook/common/pages/colors_page.dart b/example/lib/src/storybook/common/pages/colors_page.dart new file mode 100644 index 00000000..db21919e --- /dev/null +++ b/example/lib/src/storybook/common/pages/colors_page.dart @@ -0,0 +1,290 @@ +import 'package:example/src/storybook/common/color_options.dart'; +import 'package:example/src/storybook/common/constants.dart'; +import 'package:example/src/storybook/common/widgets/colors_page_options.dart'; +import 'package:example/src/storybook/common/widgets/page_footer.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:moon_design/moon_design.dart'; + +typedef ItemsPerRow = int Function(int); + +class ColorsPage extends StatelessWidget { + static const path = '/colors'; + + const ColorsPage({super.key}); + + static const int _defaultColorContainersInRow = 3; + static const double _colorContainerSizeValue = 128.0; + static const double _colorContainerGap = 24.0; + + Widget _buildMainColorsSection(BuildContext context) { + return _buildColorsSection( + context: context, + rows: 6, + itemsPerRow: (int rowIndex) => switch (rowIndex) { + 1 => 1, + 5 => _defaultColorContainersInRow, + _ => 2, + }, + section: ColorsPageSection.mainColors, + colors: mainColorsList, + ); + } + + Widget _buildSupportiveColorsSection(BuildContext context) { + return _buildColorsSection( + context: context, + rows: 9, + itemsPerRow: (int _) => _defaultColorContainersInRow, + section: ColorsPageSection.supportiveColors, + colors: supportiveColorsList, + ); + } + + double _getFooterWidth(BuildContext context) { + return MediaQuery.of(context).size.width < storybookAutoLayoutThreshold + ? mediumScreenWidth + : _colorContainerSizeValue * _defaultColorContainersInRow + + (_defaultColorContainersInRow - 1) * _colorContainerGap; + } + + int _getColorIndex(int rowIndex, int childIndex, ItemsPerRow getItemsPerRow) { + int colorIndex = 0; + + for (int i = 0; i < rowIndex; i++) { + colorIndex += getItemsPerRow(i); + } + + return colorIndex + childIndex; + } + + Widget _highlightedBodyText(BuildContext context, String text) { + if (text.isEmpty) return const Text(''); + + final TextStyle textStyle = context.moonTypography!.body.text16; + + final String pattern = + MoonColor.values.map((color) => color.name).toList().join('|'); + final regex = RegExp(pattern, caseSensitive: false); + + final List spans = []; + int start = 0; + + for (final match in regex.allMatches(text)) { + if (start == match.start) continue; + + spans.addAll([ + TextSpan( + text: text.substring(start, match.start), + style: textStyle, + ), + WidgetSpan( + alignment: PlaceholderAlignment.baseline, + baseline: TextBaseline.alphabetic, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + decoration: BoxDecoration( + color: context.moonColors!.gohan, + border: Border.all( + color: context.moonColors!.goten, + width: 0, + ), + borderRadius: BorderRadius.circular(4.0), + ), + child: Text( + match.group(0)!, + style: textStyle, + ), + ), + ), + ]); + + start = match.end; + } + + spans.add( + TextSpan( + text: text.substring(start), + style: textStyle, + ), + ); + + return RichText( + text: TextSpan( + style: textStyle, + children: spans, + ), + ); + } + + Widget _buildSectionHeader(BuildContext context, ColorsPageSection section) { + final bool isHeader = section == ColorsPageSection.header; + final bool showLogo = + MediaQuery.of(context).size.width < storybookAutoLayoutThreshold; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isHeader && showLogo) + Padding( + padding: const EdgeInsets.only(left: 12.0, bottom: 24.0), + child: SvgPicture.asset( + 'assets/svg/moon-logo-long.svg', + height: 16.0, + ), + ), + Text( + section.titleText, + style: isHeader + ? context.moonTypography!.heading.text40 + : context.moonTypography!.heading.text24, + ), + const SizedBox(height: 24), + _highlightedBodyText(context, section.bodyText), + const SizedBox(height: 24), + ], + ); + } + + Widget _buildColorsSection({ + required BuildContext context, + required int rows, + required ItemsPerRow itemsPerRow, + required ColorsPageSection section, + required List colors, + }) { + return ListView.builder( + shrinkWrap: true, + itemCount: rows * 2 - 1, + itemBuilder: (BuildContext context, int rowIndex) { + final int derivedRowIndex = rowIndex ~/ 2; + final int itemsInRow = itemsPerRow(derivedRowIndex); + + return rowIndex.isEven + ? Row( + children: List.generate( + _defaultColorContainersInRow * 2 - 1, + (int rowItemIndex) { + if (rowItemIndex.isEven) { + final int derivedItemIndex = rowItemIndex ~/ 2; + + if (derivedItemIndex < itemsInRow) { + final int colorIndex = _getColorIndex( + derivedRowIndex, + derivedItemIndex, + itemsPerRow, + ); + + final MoonColor moonColor = + colors[colorIndex % colors.length]; + + return _buildColorContainer(context, moonColor); + } else { + return const Flexible( + child: SizedBox( + height: _colorContainerSizeValue, + width: _colorContainerSizeValue, + ), + ); + } + } else { + return const SizedBox(width: _colorContainerGap); + } + }, + ), + ) + : const SizedBox(height: _colorContainerGap); + }, + ); + } + + Widget _buildColorContainer(BuildContext context, MoonColor moonColor) { + final double colorContainerWidth = + MediaQuery.of(context).size.width < storybookAutoLayoutThreshold + ? double.infinity + : _colorContainerSizeValue; + + return Flexible( + child: Column( + children: [ + Container( + height: _colorContainerSizeValue, + width: colorContainerWidth, + decoration: ShapeDecoration( + color: getColor(context, moonColor), + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + side: BorderSide( + color: context.moonColors!.beerus, + ), + ), + ), + ), + const SizedBox(height: 8.0), + Text(moonColor.name), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + final bool showLogo = + MediaQuery.of(context).size.width < storybookAutoLayoutThreshold; + + return Scaffold( + backgroundColor: Colors.white, + body: SingleChildScrollView( + padding: EdgeInsets.only( + top: showLogo ? 40.0 : 80, + left: 24, + right: 24, + bottom: 16.0, + ), + child: Center( + child: SizedBox( + width: largeScreenWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: mediumScreenWidth, + ), + child: Column( + children: [ + _buildSectionHeader( + context, + ColorsPageSection.header, + ), + const SizedBox(height: 24.0), + _buildSectionHeader( + context, + ColorsPageSection.mainColors, + ), + _buildMainColorsSection(context), + const SizedBox(height: 48.0), + _buildSectionHeader( + context, + ColorsPageSection.supportiveColors, + ), + _buildSupportiveColorsSection(context), + ], + ), + ), + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: _getFooterWidth(context), + ), + child: const Center( + child: PageFooter(), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/example/lib/src/storybook/common/pages/home_page.dart b/example/lib/src/storybook/common/pages/home_page.dart index 5ad03d1e..09cc1036 100644 --- a/example/lib/src/storybook/common/pages/home_page.dart +++ b/example/lib/src/storybook/common/pages/home_page.dart @@ -242,7 +242,6 @@ class HomePage extends StatelessWidget { return Column( children: [ _buildCards(context, constraints.maxWidth), - const SizedBox(height: 128.0), const PageFooter(), ], ); diff --git a/example/lib/src/storybook/common/widgets/colors_page_options.dart b/example/lib/src/storybook/common/widgets/colors_page_options.dart new file mode 100644 index 00000000..2b99d29a --- /dev/null +++ b/example/lib/src/storybook/common/widgets/colors_page_options.dart @@ -0,0 +1,35 @@ +enum ColorsPageSection { + header, + mainColors, + supportiveColors; + + String get titleText => switch (this) { + header => 'Colors', + mainColors => 'Main colors', + supportiveColors => 'Supportive colors', + }; + + String get bodyText => switch (this) { + header => + 'Moon Design System is decentralized and intended to support multiple ' + 'products. The use of different-color naming conventions and ' + 'numbers makes maintenance more difficult. We decided to give ' + 'each of our colors a distinct name, adopting the Dragon Ball Z ' + 'approach. Each color name is assigned for a specific purpose, ' + 'and the values vary according to the product. Please do not use ' + 'hex values; they will not change if you need theme support.', + mainColors => + 'The main colors serve a specific purpose. piccolo and hit are used ' + 'for accent colors; beerus for borders and lines; goku and gohan ' + 'for backgrounds; bulma and trunks for text and icons; goten and ' + 'popo for forced theme-less colors; and jiren, heles, and zeno ' + 'for semi-transparent overlays.', + supportiveColors => + 'Every HEX color in all supportive colors is represented by three ' + 'shades. The difference between shades is in their transparency ' + 'levels. Some colors may have a semantic purpose. krillin is ' + 'usually used for warnings, chichi for errors, and roshi for ' + 'success colors. Other colors are used for variety of secondary ' + 'reasons.' + }; +} diff --git a/example/lib/src/storybook/common/widgets/page_footer.dart b/example/lib/src/storybook/common/widgets/page_footer.dart index b5f78b77..dded95af 100644 --- a/example/lib/src/storybook/common/widgets/page_footer.dart +++ b/example/lib/src/storybook/common/widgets/page_footer.dart @@ -11,6 +11,7 @@ class PageFooter extends StatelessWidget { Widget _buildFooterSocialMediaButtons(BuildContext context) { return Wrap( crossAxisAlignment: WrapCrossAlignment.center, + alignment: WrapAlignment.center, children: List.generate( SocialMedia.values.length * 2 - 1, (int index) { @@ -41,6 +42,7 @@ class PageFooter extends StatelessWidget { Widget build(BuildContext context) { return Column( children: [ + const SizedBox(height: 112.0), Text.rich( textAlign: TextAlign.center, TextSpan( diff --git a/example/lib/src/storybook/routing/app_router.dart b/example/lib/src/storybook/routing/app_router.dart index 57989ad7..9e1c6485 100644 --- a/example/lib/src/storybook/routing/app_router.dart +++ b/example/lib/src/storybook/routing/app_router.dart @@ -1,3 +1,4 @@ +import 'package:example/src/storybook/common/pages/colors_page.dart'; import 'package:example/src/storybook/common/pages/home_page.dart'; import 'package:example/src/storybook/common/widgets/routing_error_widget.dart'; import 'package:example/src/storybook/stories/composites/combobox_multi_select.dart'; @@ -79,12 +80,21 @@ GoRouter router = GoRouter( } }, routes: [ + // Pages. GoRoute( path: HomePage.path, pageBuilder: (BuildContext _, GoRouterState __) => const NoTransitionPage( child: HomePage(), ), ), + GoRoute( + path: ColorsPage.path, + pageBuilder: (BuildContext _, GoRouterState __) => const NoTransitionPage( + child: ColorsPage(), + ), + ), + + // Stories. GoRoute( path: AccordionStory.path, pageBuilder: (BuildContext _, GoRouterState __) => const NoTransitionPage( diff --git a/example/lib/src/storybook/routing/route_aware_stories.dart b/example/lib/src/storybook/routing/route_aware_stories.dart index 4970ea11..cf7b00f4 100644 --- a/example/lib/src/storybook/routing/route_aware_stories.dart +++ b/example/lib/src/storybook/routing/route_aware_stories.dart @@ -1,3 +1,4 @@ +import 'package:example/src/storybook/common/pages/colors_page.dart'; import 'package:example/src/storybook/common/pages/home_page.dart'; import 'package:example/src/storybook/routing/app_router.dart'; import 'package:example/src/storybook/stories/composites/combobox_multi_select.dart'; @@ -36,6 +37,7 @@ import 'package:example/src/storybook/stories/primitives/text_input.dart'; import 'package:example/src/storybook/stories/primitives/text_input_group.dart'; import 'package:example/src/storybook/stories/primitives/toast.dart'; import 'package:example/src/storybook/stories/primitives/tooltip.dart'; + import 'package:flutter/services.dart'; import 'package:storybook_flutter/storybook_flutter.dart'; @@ -50,6 +52,13 @@ final List routeAwareStories = [ isPage: true, ), + Story.asRoute( + name: 'Colors', + routePath: ColorsPage.path, + router: router, + isPage: true, + ), + // Composite stories. Story.asRoute( name: 'Composites/Combobox/single select combobox',