From f4a535545fc0bcfe9aefcf781b81c31cc6a3ce50 Mon Sep 17 00:00:00 2001 From: Luke Walton Date: Mon, 5 Feb 2024 10:35:14 +0000 Subject: [PATCH] Chore/updates (#10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Tidy, reorganise and prepare repo docs: update changelog and documentation feat(type): Add xSmall and conform to latest figma designs. * Update to mirror 0.1.0+1 from ZebraDevs * Cleanup dart warnings; fix text scale (#23) * fix endtemplate in comments; upgrade flutter packages * remove unnecessary text scaling * [automated commit] lint format and import sort --------- Co-authored-by: Atanas Yordanov Co-authored-by: github-actions * chore(deps): bump tj-actions/branch-names in /.github/workflows (#26) Bumps [tj-actions/branch-names](https://github.com/tj-actions/branch-names) from 5.1 to 7.0.7. - [Release notes](https://github.com/tj-actions/branch-names/releases) - [Changelog](https://github.com/tj-actions/branch-names/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/branch-names/compare/v5.1...v7.0.7) --- updated-dependencies: - dependency-name: tj-actions/branch-names dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * status_label (#25) * status_label * [automated commit] lint format and import sort * spacing changes * extract BorderType in utils * chore(deps): bump tj-actions/branch-names in /.github/workflows (#26) Bumps [tj-actions/branch-names](https://github.com/tj-actions/branch-names) from 5.1 to 7.0.7. - [Release notes](https://github.com/tj-actions/branch-names/releases) - [Changelog](https://github.com/tj-actions/branch-names/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/branch-names/compare/v5.1...v7.0.7) --- updated-dependencies: - dependency-name: tj-actions/branch-names dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [automated commit] lint format and import sort --------- Signed-off-by: dependabot[bot] Co-authored-by: github-actions Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * add icons (#24) * add icons * [automated commit] lint format and import sort * [automated commit] lint format and import sort --------- Co-authored-by: Atanas Yordanov Co-authored-by: github-actions * Priority Pill (#28) * Priority Pill * [automated commit] lint format and import sort * remove unnecessary param * [automated commit] lint format and import sort --------- Co-authored-by: github-actions * Component indicator (#30) * add icons * [automated commit] lint format and import sort * create ZetaIndicator component with examples * fix value * add more comments * add widgetbook * [automated commit] lint format and import sort --------- Co-authored-by: Atanas Yordanov Co-authored-by: github-actions * Badge (#29) * initial * Badge * fix test * [automated commit] lint format and import sort * change default border type * fix test * [automated commit] lint format and import sort * [automated commit] lint format and import sort * [automated commit] lint format and import sort * fix example * fix sizing * add foreground colors * [automated commit] lint format and import sort --------- Co-authored-by: github-actions * some sizings were not exactly as shown in Figma (#33) * . * [automated commit] lint format and import sort --------- Co-authored-by: github-actions * Merge from zebradevs (#27) * Merge from zebradevs * spelling and finals * [automated commit] lint format and import sort --------- Co-authored-by: github-actions * Component ZetaAvatar (#32) * add empty avatar example page; upgrade packages * add icons * [automated commit] lint format and import sort * create avatar component with examples * create ZetaIndicator component with examples * fix value * fix & improve; add badges (indicators) * add more comments * add factory constructors for the different types * add widgetbook * add avatar to widgetbook * [automated commit] lint format and import sort * Badge (#29) * initial * Badge * fix test * [automated commit] lint format and import sort * change default border type * fix test * [automated commit] lint format and import sort * [automated commit] lint format and import sort * [automated commit] lint format and import sort * fix example * fix sizing * add foreground colors * [automated commit] lint format and import sort --------- Co-authored-by: github-actions * [automated commit] lint format and import sort * replace photo with image * [automated commit] lint format and import sort --------- Co-authored-by: Atanas Yordanov Co-authored-by: github-actions Co-authored-by: Genoveva Georgieva <151932404+genovevageorgieva@users.noreply.github.com> * App bar (#35) * create ZetaAppBar in four variants with example screens * [automated commit] lint format and import sort * remove Flexible * rename * titleIcon should be of type Icon instead of Widget --------- Co-authored-by: Atanas Yordanov Co-authored-by: github-actions * Workcloud indicators (#34) * initial * workcloud indicator * typo * . * [automated commit] lint format and import sort * . * . * . * [automated commit] lint format and import sort * [automated commit] lint format and import sort * add default values * [automated commit] lint format and import sort --------- Co-authored-by: github-actions * fix the border of the indicator component (#31) * fix the border of the component * [automated commit] lint format and import sort * inverseBorder * [automated commit] lint format and import sort * rounded = true by default * running on iPhone * try to adjust font height * horizontal alignment * try to remove the line height * stick to Material 2 for now --------- Co-authored-by: Atanas Yordanov Co-authored-by: github-actions * Button component (#36) * initial * format_error * [automated commit] lint format and import sort * button component optimizations * [automated commit] lint format and import sort * [automated commit] lint format and import sort * colors change * colors change * [automated commit] lint format and import sort --------- Co-authored-by: github-actions * force Material 3 (#38) * force Material 3 * [automated commit] lint format and import sort --------- Co-authored-by: Atanas Yordanov Co-authored-by: github-actions * Tag component (#37) * initial * inital * initial * tag component * tag component * [automated commit] lint format and import sort --------- Co-authored-by: github-actions Co-authored-by: Atanas Yordanov * Component accordion (#39) * first draft of the ZetaAccordion * create ZetaAccordion component * add icon padding * add list separator & margin * Tag component (#37) * initial * inital * initial * tag component * tag component * [automated commit] lint format and import sort --------- Co-authored-by: github-actions Co-authored-by: Atanas Yordanov * add comments and more examples * pubspec * [automated commit] lint format and import sort --------- Co-authored-by: Atanas Yordanov Co-authored-by: Genoveva Georgieva <151932404+genovevageorgieva@users.noreply.github.com> Co-authored-by: github-actions * fix sizings and replace icons with zeta icons (#43) * fix sizings and replace icons with zeta icons * [automated commit] lint format and import sort --------- Co-authored-by: github-actions * Menu Items - horizontal & vertical (#44) * Menu Items - horizontal & vertical * [automated commit] lint format and import sort --------- Co-authored-by: Atanas Yordanov Co-authored-by: github-actions * component floating action button (#41) Co-authored-by: github-actions * Page banner (#40) Co-authored-by: github-actions * create bottom sheet (#45) Co-authored-by: Atanas Yordanov Co-authored-by: github-actions * Password input (#47) Co-authored-by: github-actions Co-authored-by: Atanas Yordanov * Component chip (#46) Co-authored-by: Atanas Yordanov Co-authored-by: github-actions * chore(deps): bump tj-actions/changed-files from 35 to 41 in /.github/workflows (#48) Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 35 to 41.
Release notes

Sourced from tj-actions/changed-files's releases.

v41

Changes in v41.0.1

What's Changed

Full Changelog: https://github.com/tj-actions/changed-files/compare/v41...v41.0.1


Changes in v41.0.0

🔥 🔥 BREAKING CHANGE 🔥 🔥

A new safe_output input is now available to prevent outputting unsafe filename characters (Enabled by default). This would escape characters in the filename that could be used for command injection.

[!NOTE] This can be disabled by setting the safe_output to false this comes with a recommendation to store all outputs generated in an environment variable first before using them.

Example

...
    - name: Get changed files
      id: changed-files
      uses: tj-actions/changed-files@v40
      with:
safe_output: false # set to false because we are using an environment
variable to store the output and avoid command injection.
- name: List all added files
  env:
    ADDED_FILES: ${{ steps.changed-files.outputs.added_files }}
  run: |
    for file in &quot;$ADDED_FILES&quot;; do
      echo &quot;$file was added&quot;
    done

...

What's Changed

... (truncated)

Changelog

Sourced from tj-actions/changed-files's changelog.

Changelog

41.0.1 - (2023-12-24)

🐛 Bug Fixes

  • Update characters escaped by safe output (#1815) (716b1e1) - (Tonye Jack)

⚙️ Miscellaneous Tasks

  • deps: Update dependency eslint-plugin-prettier to v5.1.2 (7aaf10d) - (renovate[bot])

⬆️ Upgrades

  • Upgraded to v41 (#1811)

Co-authored-by: jackton1 jackton1@users.noreply.github.com (cc08e17) - (tj-actions[bot])

41.0.0 - (2023-12-23)

🐛 Bug Fixes

  • Update safe output regex and the docs (#1805) (ff2f6e6) - (tj-actions[bot])

⏪ Reverts

  • Revert "chore(deps): update actions/download-artifact action to v4" (#1806)

(4f573fe) - (Tonye Jack)

🔄 Update

  • Update README.md (6e79d6e) - (Tonye Jack)
  • Update README.md (d13ac19) - (Tonye Jack)
  • Update README.md (bb89f97) - (Tonye Jack)
  • Updated README.md (#1810)

Co-authored-by: renovate[bot] (1864078) - (tj-actions[bot])

  • Update README.md (#1808)

(47371c5) - (Tonye Jack)

📝 Other

  • Merge pull request from GHSA-mcph-m25j-8j63
  • feat: add safe_output input enabled by default

  • fix: migrate README to safe uses of interpolation

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tj-actions/changed-files&package-manager=github_actions&previous-version=35&new-version=41)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/zebratechnologies/zeta-flutter/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Organize ilb/ to match web (#49) https://jira.zebra.com/browse/UX-911 --------- Co-authored-by: github-actions * feat: Update text styles to match latest designs (#50) https://jira.zebra.com/browse/UX-808 --------- Co-authored-by: github-actions * feat: Remove legacy code (#51) BREAKING CHANGE: Removed ZetaGrid and ZetaSpacing components, renamed widget padding extensions https://jira.zebra.com/browse/UX-910 Remove ZetaGrid, ZetaSpacing and ZetaText widgets as these are no longer part of the library. Functionality is retained in some cases (text styles, padding extensions) but widgets themselves are removed. --------- Co-authored-by: github-actions * chore: Update Widgetbook to correct functionality (#52) https://jira.zebra.com/browse/UX-912 --------- Co-authored-by: github-actions * chore: Tidy up example app (#53) https://jira.zebra.com/browse/UX-920 --------- Co-authored-by: github-actions * chore: Update Github action (#54) https://jira.zebra.com/browse/UX-913 * chore: Refactor components part 1 (#55) https://jira.zebra.com/browse/UX-918 Part 1 of 2 due to size. - [x] Accordion - [ ] Avatar - [x] Badges - [x] Banner - [ ] Bottom Sheet - [x] Button - [ ] Checkbox - [ ] Chip - [ ] Password Input * chore: UX-918 part 2. Refactor components (#56) - [x] Accordion - [x] Avatar - [x] Badges - [x] Banners - [x] Bottom Sheets - [x] Buttons - [x] FABs - [x] Checkbox - [x] Chip - [x] Password Input * chore: Add publish action. (#58) This action should fail when run in internal repository, and only run from ZebraDevs. Whenever a tag is pushed, this should run checks, format and publish package, dependent on admin approval. * chore: Tidy, reorganise and prepare repo docs: update changelog and documentation feat(type): Add xSmall and conform to latest figma designs. * System theme support (#1) * chore: Tidy, reorganise and prepare repo docs: update changelog and documentation feat(type): Add xSmall and conform to latest figma designs. * "Refactored the ZetaColors class for better customization The ZetaColors class was heavily refactored for better customization of variables such as brightness, contrast, color swatches, and additional color attributes. Several fields were made final for the overall class safety. This change improves color control on different themes for the application." * "Updated the ZetaColor and Theme setup to use InheritedWidget The ZetaDefaults class was updated to Zeta inheriting from InheritedWidget. This change allows easy access to the Zeta theme settings (contrast, theme mode, theme data, color set) from anywhere in the widget tree. The ZetaAppBuilder function was updated to take in ThemeData and ThemeMode. The ZetaProvider was added to provide Zeta theming and contrast data down the widget tree. The code for the color and typography examples was adjusted to use the new Zeta context extension, instead of using Theme.of(context) to get colorScheme. This change was crucial to simplify the process of adapting the application visuals to different themes." * "Improve theme management functionality in Zeta Removed code concerning getting a color's RGB hex code from 'color_extensions.dart', as it was seldom used. Updated 'zeta_flutter.dart' to unhide ZetaColorGetters. Adaptations in 'zeta.dart' included switching mediaBrightness to _mediaBrightness for internal use and adding methods for accurate determination of color set and brightness settings based on the theme mode. Also, ZetaProvider was updated for 'system' theme mode support. example/lib/main.dart and example/lib/widgets.dart were updated to support these changes, including UI updates for seamless theme switching." * "Add theme update function and extend ZetaColorGetters Implemented a method in 'zeta.dart' to support updating the current theme data dynamically. Extended 'color_scheme.dart' by introducing _ZetaColorProperties and updating ZetaColorGetters. These changes increase flexibility for theme management and provide a structured and accessible way to get Zeta colors through the theme context." * Remove theme_extensions.dart and move contents to colors.dart Theme extensions were deleted and its contents were moved to colors.dart to consolidate all color-related codes in one file for easier navigation and editing. Additional enhancements include optimizing color assignments and making ZetaColors immutable for more robust color management. * Refactor code for color theme and add theme switcher Refactored codebase to improve the color theme handling: relocated theme related methods to colors.dart from theme_extensions.dart for consolidated color theme data. Optimized color assignments by leveraging the 'copyWith' method, allowing more efficient color management. Introduced the immutability of ZetaColors to enhance robustness. Bumped version in pubspec.yaml to 0.0.1+13 due to these changes. Renamed theme.dart to theme_data.dart for more semantic file naming. Added 'identifier' to the ZetaThemeData for easier theme identification. The visible application change is an added ThemeSwitcher in the example app, offering a UI to switch between different predefined themes. * Add ZetaThemeService and theme switcher in example app Implemented ZetaThemeService as an abstract class, providing structure for loading and saving themes within the app. Removed an obsolete comment within the contrast.dart and made necessary imports in zeta.dart. Asynchronous theme loading is added during app startup and saving is done upon theme updates. Also, for user-interaction, an exclusive ThemeSwitcher widget is added in the example app allowing users to select between available themes. This improves user experience, and optimizes theme handling and application performance. * Refactor color swatch generation to utilize zeta Refactored color swatch generation in color_example.dart to use Zeta instead of directly using the Theme. Now the brightness for ZetaColorSwatch is being pulled from zeta object rather than theme. This ensures consistency across different parts of the application where Zeta is used. Also changed theme.colorScheme.surface to colors.surfacePrimary for better readability, and alignment with use of zeta object. * Add icon colors to color scheme Extended the color scheme in colors.dart to include default, subtle, disabled, and inverse icon colors. These were added to ensure consistent icon colors across the application and support dark mode by allowing inverted color swatches. * Refactor theme switch settings and add new features Renamed 'theme_switch.dart' to 'theme_color_switch.dart' and added two new files 'theme_contrast_switch.dart' and 'theme_mode_switch.dart' in order to separate the theme settings logically into distinct features - Theme Color Switch, Theme Contrast Switch and Theme Mode Switch respectively. Also, the theme application feature has been refactored within 'widgets.dart' to use the newly created theme features instead of the old theme switch. This enhances modularity and the user's ability to switch theme settings easily. * Update method naming for consistent architecture in text.dart Changed the method name 'withColor' to 'themeWithColor' in text.dart for consistency with other part of the architecture and for better readability. This change supports the shift towards a consistently designed application architecture and helps developers easily decipher the role of the method in the code. * Update color scheme mapping and library version in colors.dart and pubspec.lock Refined the color mapping in ZetaColorScheme in colors.dart by replacing effectiveSurfaceTertiary with textDefault, enhancing the clarity of backdrop's color role. Concurrently, version of multiple dependencies in pubspec.lock are updated to benefit from recent fixes and improvements in those libraries. * Change `Color` to `ZetaColorSwatch` in theme files Adjusted the class references in colors.dart from `Color` to `ZetaColorSwatch` to provide a more consistent color swatch across the app. The swatch allows for more flexibility in using color variations. Adjustments were also made in color_scheme.dart and color_swatch.dart to include better explanatory messages and use standard dart documentation format. Changes in custom_docs/components/Color/flutter.md were made to align with these updates. * Enhance contrast and color handling in theme files Removed 'flutter.md' as it is no longer required due to improvements made in contrast and color handling. For better accessibility support, 'contrast.dart' was refactored for better contrast handling and 'color_extensions.dart' now includes a mechanism to generate color swatch based on contrast ratio. Also, 'zeta.dart' was updated to adapt to the system's brightness providing better user experience. Overall, these adjustments aim to enhance accessibility and user experience, apart from simplifying the codebase. * Add LICENSE-3RD-PARTY for third-party libraries Introduced license details for third-party libraries used in the project. MIT license applies to 'tinycolor' and SIL Open Font License applies to 'IBMPlexSans'. This ensures proper acknowledgement and licensure compliance for used third-party resources. --------- Co-authored-by: Luke Walton * Refactor theme declaration and introduce theme service (#8) In theme_color_switch.dart, restructured the declaration of themes into a global constant 'appThemes', freeing it from a class field in ZetaThemeColorSwitch. This increases application efficiency and adheres to the DRY (Don't Repeat Yourself) principle. A new file 'theme_service.dart' was also added, serving as an abstract class for loading and saving theme data. Various typos in 'colors.dart' and 'zeta.dart' were fixed to ensure correct execution. Theme data loading and saving operations in 'main.dart' were also updated to use this new service. * Checkbox (#9) * checkbox * remove comment * widgetbook * widgetbook * [automated commit] lint format and import sort --------- Signed-off-by: dependabot[bot] Co-authored-by: atanasyordanov21 <63714308+atanasyordanov21@users.noreply.github.com> Co-authored-by: Atanas Yordanov Co-authored-by: github-actions Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Genoveva Georgieva <151932404+genovevageorgieva@users.noreply.github.com> Co-authored-by: Prashant Sawant <75819349+ps9310@users.noreply.github.com> --- .firebaserc | 5 + .github/workflows/on-release.yml | 41 + .github/workflows/onMerge.yml | 136 -- .../workflows/{pr.yml => pull-request.yml} | 71 +- .github/workflows/release-please.yml | 17 + .pubignore | 10 +- .release-please-manifest.json | 3 + CHANGELOG.md | 1625 ++++++++++++++++- LICENSE | 2 +- README.md | 2 + custom_docs/components/Grid/flutter.md | 59 - custom_docs/components/Spacing/flutter.md | 198 -- custom_docs/components/Typography/flutter.md | 98 - example/ios/Podfile.lock | 9 +- example/lib/home.dart | 89 +- example/lib/main.dart | 12 +- example/lib/pages/assets/icons_example.dart | 1015 ++++++++++ .../pages/components/accordion_example.dart | 93 + .../lib/pages/components/avatar_example.dart | 595 ++++++ .../lib/pages/components/badges_example.dart | 265 +++ .../lib/pages/components/banner_example.dart | 198 ++ .../components/bottom_sheet_example.dart | 83 + .../lib/pages/components/button_example.dart | 139 ++ .../{ => components}/checkbox_example.dart | 57 +- .../lib/pages/components/chip_example.dart | 228 +++ .../components/password_input_example.dart | 83 + example/lib/pages/grid_example.dart | 112 -- example/lib/pages/spacing_example.dart | 132 -- .../lib/pages/{ => theme}/color_example.dart | 65 +- .../lib/pages/theme/typography_example.dart | 115 ++ example/lib/pages/typography_example.dart | 188 -- example/lib/theme_service.dart | 2 +- .../{pages => utils}/theme_color_switch.dart | 6 +- .../theme_constrast_switch.dart | 7 +- .../{pages => utils}/theme_mode_switch.dart | 2 +- example/lib/widgets.dart | 71 +- .../Flutter/GeneratedPluginRegistrant.swift | 16 + example/macos/Podfile.lock | 16 +- .../macos/Runner.xcodeproj/project.pbxproj | 1 - example/pubspec.yaml | 1 + example/test/badge_test.dart | 24 + example/test/button_test.dart | 36 + example/test/checkbox_test.dart | 43 +- example/test/fab_test.dart | 46 + example/test/grid_test.dart | 42 - example/test/in_page_banner_test.dart | 88 + example/test/password_input_test.dart | 54 + example/test/priority_pill_test.dart | 19 + example/test/spacing_test.dart | 188 -- example/test/status_label_test.dart | 54 + example/test/tag_test.dart | 40 + example/test/test_components.dart | 34 +- example/test/typography_test.dart | 73 +- example/test/workcloud_indicator_test.dart | 15 + .../components/accordion_widgetbook.dart | 39 + .../components/avatar_widgetbook.dart | 68 + .../components/badges_widgetbook.dart | 183 ++ .../components/banner_widgetbook.dart | 109 ++ .../components/bottom_sheet_widgetbook.dart | 63 + .../components/button_widgetbook.dart | 101 + .../components/checkbox_widgetbook.dart | 42 +- .../components/chip_widgetbook.dart | 83 + .../components/color_widgetbook.dart | 129 -- .../components/grid_widgetbook.dart | 84 - .../components/password_input_widgetbook.dart | 82 + .../components/spacing_widgetbook.dart | 119 -- .../components/typography_widgetbook.dart | 114 -- .../widgetbook/theme/color_widgetbook.dart | 144 ++ .../theme/typography_widgetbook.dart | 46 + example/widgetbook/widgetbook.dart | 38 +- firebase.json | 11 + lib/src/assets/fonts/zeta-icons-round.ttf | Bin 0 -> 145320 bytes lib/src/assets/fonts/zeta-icons-sharp.ttf | Bin 0 -> 116564 bytes lib/src/assets/icons.dart | 1008 ++++++++++ lib/src/components/accordion/accordion.dart | 185 ++ lib/src/components/avatars/avatar.dart | 235 +++ lib/src/components/badges/badge.dart | 57 + lib/src/components/badges/indicator.dart | 161 ++ lib/src/components/badges/priority_pill.dart | 72 + lib/src/components/badges/status_label.dart | 68 + lib/src/components/badges/tag.dart | 171 ++ .../badges/workcloud_indicator.dart | 164 ++ .../components/banners/in_page_banner.dart | 140 ++ lib/src/components/banners/system_banner.dart | 101 + .../bottom sheets/bottom_sheet.dart | 108 ++ .../components/bottom sheets/menu_items.dart | 150 ++ lib/src/components/buttons/button.dart | 300 +++ lib/src/components/buttons/fab.dart | 211 +++ lib/src/components/checkbox.dart | 201 -- lib/src/components/checkbox/checkbox.dart | 243 +++ lib/src/components/chips/assist_chip.dart | 14 + lib/src/components/chips/chip.dart | 177 ++ lib/src/components/chips/filter_chip.dart | 14 + lib/src/components/chips/input_chip.dart | 15 + lib/src/components/grid.dart | 156 -- .../components/password/password_input.dart | 244 +++ lib/src/components/spacing.dart | 191 -- lib/src/components/text.dart | 772 -------- lib/src/theme/colors.dart | 8 +- lib/src/{ => theme}/tokens.dart | 64 +- lib/src/theme/typography.dart | 132 ++ lib/src/utils/enums.dart | 43 + lib/src/utils/extensions.dart | 79 + lib/zeta_flutter.dart | 26 +- pubspec.yaml | 14 +- release-please-config.json | 7 + 106 files changed, 10310 insertions(+), 3299 deletions(-) create mode 100644 .firebaserc create mode 100644 .github/workflows/on-release.yml delete mode 100644 .github/workflows/onMerge.yml rename .github/workflows/{pr.yml => pull-request.yml} (57%) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json delete mode 100644 custom_docs/components/Grid/flutter.md delete mode 100644 custom_docs/components/Spacing/flutter.md delete mode 100644 custom_docs/components/Typography/flutter.md create mode 100644 example/lib/pages/assets/icons_example.dart create mode 100644 example/lib/pages/components/accordion_example.dart create mode 100644 example/lib/pages/components/avatar_example.dart create mode 100644 example/lib/pages/components/badges_example.dart create mode 100644 example/lib/pages/components/banner_example.dart create mode 100644 example/lib/pages/components/bottom_sheet_example.dart create mode 100644 example/lib/pages/components/button_example.dart rename example/lib/pages/{ => components}/checkbox_example.dart (54%) create mode 100644 example/lib/pages/components/chip_example.dart create mode 100644 example/lib/pages/components/password_input_example.dart delete mode 100644 example/lib/pages/grid_example.dart delete mode 100644 example/lib/pages/spacing_example.dart rename example/lib/pages/{ => theme}/color_example.dart (83%) create mode 100644 example/lib/pages/theme/typography_example.dart delete mode 100644 example/lib/pages/typography_example.dart rename example/lib/{pages => utils}/theme_color_switch.dart (92%) rename example/lib/{pages => utils}/theme_constrast_switch.dart (89%) rename example/lib/{pages => utils}/theme_mode_switch.dart (98%) create mode 100644 example/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 example/test/badge_test.dart create mode 100644 example/test/button_test.dart create mode 100644 example/test/fab_test.dart delete mode 100644 example/test/grid_test.dart create mode 100644 example/test/in_page_banner_test.dart create mode 100644 example/test/password_input_test.dart create mode 100644 example/test/priority_pill_test.dart delete mode 100644 example/test/spacing_test.dart create mode 100644 example/test/status_label_test.dart create mode 100644 example/test/tag_test.dart create mode 100644 example/test/workcloud_indicator_test.dart create mode 100644 example/widgetbook/components/accordion_widgetbook.dart create mode 100644 example/widgetbook/components/avatar_widgetbook.dart create mode 100644 example/widgetbook/components/badges_widgetbook.dart create mode 100644 example/widgetbook/components/banner_widgetbook.dart create mode 100644 example/widgetbook/components/bottom_sheet_widgetbook.dart create mode 100644 example/widgetbook/components/button_widgetbook.dart create mode 100644 example/widgetbook/components/chip_widgetbook.dart delete mode 100644 example/widgetbook/components/color_widgetbook.dart delete mode 100644 example/widgetbook/components/grid_widgetbook.dart create mode 100644 example/widgetbook/components/password_input_widgetbook.dart delete mode 100644 example/widgetbook/components/spacing_widgetbook.dart delete mode 100644 example/widgetbook/components/typography_widgetbook.dart create mode 100644 example/widgetbook/theme/color_widgetbook.dart create mode 100644 example/widgetbook/theme/typography_widgetbook.dart create mode 100644 firebase.json create mode 100644 lib/src/assets/fonts/zeta-icons-round.ttf create mode 100644 lib/src/assets/fonts/zeta-icons-sharp.ttf create mode 100644 lib/src/assets/icons.dart create mode 100644 lib/src/components/accordion/accordion.dart create mode 100644 lib/src/components/avatars/avatar.dart create mode 100644 lib/src/components/badges/badge.dart create mode 100644 lib/src/components/badges/indicator.dart create mode 100644 lib/src/components/badges/priority_pill.dart create mode 100644 lib/src/components/badges/status_label.dart create mode 100644 lib/src/components/badges/tag.dart create mode 100644 lib/src/components/badges/workcloud_indicator.dart create mode 100644 lib/src/components/banners/in_page_banner.dart create mode 100644 lib/src/components/banners/system_banner.dart create mode 100644 lib/src/components/bottom sheets/bottom_sheet.dart create mode 100644 lib/src/components/bottom sheets/menu_items.dart create mode 100644 lib/src/components/buttons/button.dart create mode 100644 lib/src/components/buttons/fab.dart delete mode 100644 lib/src/components/checkbox.dart create mode 100644 lib/src/components/checkbox/checkbox.dart create mode 100644 lib/src/components/chips/assist_chip.dart create mode 100644 lib/src/components/chips/chip.dart create mode 100644 lib/src/components/chips/filter_chip.dart create mode 100644 lib/src/components/chips/input_chip.dart delete mode 100644 lib/src/components/grid.dart create mode 100644 lib/src/components/password/password_input.dart delete mode 100644 lib/src/components/spacing.dart delete mode 100644 lib/src/components/text.dart rename lib/src/{ => theme}/tokens.dart (60%) create mode 100644 lib/src/theme/typography.dart create mode 100644 lib/src/utils/enums.dart create mode 100644 release-please-config.json diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 00000000..91581093 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "zeta-ds" + } +} \ No newline at end of file diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml new file mode 100644 index 00000000..4d6d1bde --- /dev/null +++ b/.github/workflows/on-release.yml @@ -0,0 +1,41 @@ +name: "CI - Release" +on: + workflow_dispatch: + release: + types: [released] + +jobs: + check-package: + runs-on: ubuntu-latest + permissions: + id-token: write + environment: pub.dev + steps: + - uses: actions/checkout@v4 + - uses: dart-lang/setup-dart@v1 + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: "3.16.x" + channel: "stable" + - name: Install dependencies + run: flutter pub get + - name: Format code + run: dart format --fix . + - name: Check Publish Warnings + run: dart pub publish --dry-run + - name: Publish + run: dart pub publish --force + deploy-website: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v6 + with: + github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + script: | + await github.rest.actions.createworkflowDispatch({ + owner: 'zebratechnologies', + repo: 'zeta', + workflow_id: 'deploy-prod.yml', + ref: 'main' + }) diff --git a/.github/workflows/onMerge.yml b/.github/workflows/onMerge.yml deleted file mode 100644 index 18736e3c..00000000 --- a/.github/workflows/onMerge.yml +++ /dev/null @@ -1,136 +0,0 @@ -name: "Merge" - -on: - pull_request: - types: [closed] - -jobs: - changes: - if: github.event.pull_request.merged - runs-on: ubuntu-latest - timeout-minutes: 5 - outputs: - files: ${{steps.changed-files.outputs.any_changed}} - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: main - persist-credentials: false - - name: Get all changed *.dart, files in docs or pubspec.yaml - id: changed-files - uses: tj-actions/changed-files@v35 - with: - files: | - **/*.dart - custom-docs/* - **/*.yaml - **/*.yml - files_ignore: example/* - update-version: - runs-on: ubuntu-latest - timeout-minutes: 5 - needs: changes - if: needs.changes.outputs.files == 'true' - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: main - persist-credentials: false - - name: Run step if any *.js file(s) or any file in the static folder change - if: steps.changed-files-excluded.outputs.any_changed == 'true' - run: | - echo "One or more *.js file(s) or any file in the static folder but not in the custom-docs folder has changed." - echo "List all the files that have changed: ${{ steps.changed-files-excluded.outputs.all_changed_files }}" - - name: Change flutter version tag - uses: BentEngbers/flutter-change-version@v1.0.3 - - name: push change - id: update - run: | - git remote set-url origin https://${{ secrets.PAT}}@github.com/zebratechnologies/zeta-flutter.git - git config --global user.name "github-actions" - git config --global user.email "github-actions@github.com" - git add -A - git commit --amend --no-edit - git push -f - publish-changelog: - needs: [changes, update-version] - if: needs.changes.outputs.files == 'true' - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v3 - with: - ref: main - fetch-depth: 0 - persist-credentials: false - - id: read-version - uses: NiklasLehnfeld/flutter-version-number-action@main - - name: Create tag - uses: actions/github-script@v5 - with: - script: | - github.rest.git.createRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: 'refs/tags/${{steps.read-version.outputs.version-number}}', - sha: "${{github.sha}}" - }) - - name: Update CHANGELOG - id: changelog - uses: requarks/changelog-action@v1 - with: - token: ${{ secrets.PAT }} - tag: ${{ steps.read-version.outputs.version-number }} - excludeTypes: "" - includeInvalidCommits: true - - name: Check for modified files - id: git-check - run: echo "modified=$(if [ -n "$(git status --porcelain)" ]; then echo "true"; else echo "false"; fi)" >> $GITHUB_ENV - - name: Update changes in zeta-flutter repository - if: env.modified == 'true' - run: | - git remote set-url origin https://${{ secrets.PAT}}@github.com/zebratechnologies/zeta-flutter.git - git config --global user.name "github-actions" - git config --global user.email "github-actions@github.com" - git add -A - git commit --amend --no-edit - git push -f - - name: Make changelog flutter - run: cp CHANGELOG.md CHANGELOG_FLUTTER.md - - name: Push changelog to Zeta - uses: dmnemec/copy_file_to_another_repo_action@main - env: - API_TOKEN_GITHUB: ${{ secrets.PAT }} - with: - source_file: "CHANGELOG_FLUTTER.md" - destination_repo: "zebratechnologies/zeta" - destination_folder: "./" - destination_branch_create: "flutter/${{ steps.read-version.outputs.version-number }}" - user_email: "github-actions@github.com" - user_name: "github-actions" - commit_message: "flutter changelog" - - name: Push docs to Zeta - uses: dmnemec/copy_file_to_another_repo_action@main - env: - API_TOKEN_GITHUB: ${{ secrets.PAT }} - with: - source_file: "custom-docs/" - destination_repo: "zebratechnologies/zeta" - destination_folder: "./" - destination_branch: "flutter/${{ steps.read-version.outputs.version-number }}" - user_email: "github-actions@github.com" - user_name: "github-actions" - commit_message: "flutter doc" - - name: Open Zeta PR - uses: thecanadianroot/open-pull-request-action@v1.0.3 - with: - token: ${{secrets.PAT}} - base: main - head: "flutter/${{ steps.read-version.outputs.version-number }}" - title: "flutter/${{ steps.read-version.outputs.version-number }}" - labels: flutter - reviewers: benken - owner: zebratechnologies - repository: zebratechnologies/zeta diff --git a/.github/workflows/pr.yml b/.github/workflows/pull-request.yml similarity index 57% rename from .github/workflows/pr.yml rename to .github/workflows/pull-request.yml index 6b5bcf3a..df90b783 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pull-request.yml @@ -1,22 +1,42 @@ -name: "PR" -on: - pull_request: +name: CI - Pull Request +on: pull_request jobs: + up-to-date: + name: "Check branch is up to date" + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.head_ref }} + - name: Check branch is up to date + run: | + if git merge-base --is-ancestor ${{ github.event.pull_request.base.sha}} ${{ github.event.pull_request.head.sha}} + then + echo "Your branch is up to date." + exit 0 + else + echo "You need to merge / rebase." + exit 1 + fi changes: + name: "Check for changes in code" + needs: up-to-date runs-on: ubuntu-latest timeout-minutes: 5 outputs: files: ${{steps.changed-files.outputs.any_changed}} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 ref: main persist-credentials: false - name: Get all changed *.dart, files in custom-docs or pubspec.yaml id: changed-files - uses: tj-actions/changed-files@v35 + uses: tj-actions/changed-files@v41 with: base_sha: ${{ github.event.pull_request.base.sha }} sha: ${{ github.event.pull_request.head.sha }} @@ -24,32 +44,22 @@ jobs: **/*.dart custom-docs pubspec.yaml - analyze: + code-quality: runs-on: ubuntu-latest timeout-minutes: 20 needs: changes if: needs.changes.outputs.files == 'true' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.head_ref }} - - name: Check branch is up to date - run: | - if git merge-base --is-ancestor ${{ github.event.pull_request.base.sha}} ${{ github.event.pull_request.head.sha}} - then - echo "Your branch is up to date." - exit 0 - else - echo "You need to merge / rebase." - exit 1 - fi - name: Get branch name id: branch-name - uses: tj-actions/branch-names@v5.1 + uses: tj-actions/branch-names@v7.0.7 - uses: subosito/flutter-action@v2 with: - flutter-version: "3.13.x" + flutter-version: "3.16.x" channel: "stable" - name: Setup flutter run: flutter pub get @@ -70,3 +80,26 @@ jobs: git add -A git commit -m '[automated commit] lint format and import sort' git push + deploy-preview: + name: Deploy preview version of the storybook on firebase + needs: code-quality + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + flutter-version: "3.16.x" + channel: "stable" + - name: Setup flutter + run: flutter pub get + - name: Build example app + run: | + cd example + flutter build web -o ../build --no-tree-shake-icons + - uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: "${{ secrets.GITHUB_TOKEN }}" + firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_ZETA_DS }}" + expires: 7d + projectId: zeta-ds + channelId: "pr-${{ github.event.number }}-${{ github.event.pull_request.head.ref }}" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 00000000..197446e0 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,17 @@ +name: release-please +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: google-github-actions/release-please-action@v4 + with: + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} diff --git a/.pubignore b/.pubignore index 33e41fc7..6df95062 100644 --- a/.pubignore +++ b/.pubignore @@ -1 +1,9 @@ -custom_docs/ \ No newline at end of file +custom_docs/ +example/build +example/android +example/ios +example/linux +example/macos +example/web +example/windows +example/widgetbook diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..10f30916 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.2.0" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a39bb59..21d91aa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,86 +1,1595 @@ -## [0.1.1+1] - 2023-12-01 +## [0.1.1+23] - 2024-01-19 -- feature: Refactor theme declaration and introduce theme service +### :wrench: Chores + +- [`ed1210d`](https://github.com/zebratechnologies/zeta-flutter/commit/ed1210d6cbc173aef8b1adf0d20ee91d3e87f2c3) - Update Widgetbook to correct functionality _(PR [#52](https://github.com/zebratechnologies/zeta-flutter/pull/52) by [@thelukewalton](https://github.com/thelukewalton))_ +- [`c627d7a`](https://github.com/zebratechnologies/zeta-flutter/commit/c627d7ab19d3f33e30c406441702ff6134557f14) - Tidy up example app _(PR [#53](https://github.com/zebratechnologies/zeta-flutter/pull/53) by [@thelukewalton](https://github.com/thelukewalton))_ + +## [0.1.1+22] - 2024-01-19 + +### :boom: BREAKING CHANGES + +- due to [`7b543ac`](https://github.com/zebratechnologies/zeta-flutter/commit/7b543ac7b92dc53a866af4de313c36b5728e912e) - Remove legacy code _(PR [#51](https://github.com/zebratechnologies/zeta-flutter/pull/51) by [@thelukewalton](https://github.com/thelukewalton))_: + + Removed ZetaGrid and ZetaSpacing components, renamed widget padding extensions + https://jira.zebra.com/browse/UX-910 + Remove ZetaGrid, ZetaSpacing and ZetaText widgets as these are no longer + part of the library. Functionality is retained in some cases (text + styles, padding extensions) but widgets themselves are removed. -## [0.1.0+1] - 2023-11-28 + *** -- chore: Tidy, reorganise and prepare repo +### :sparkles: New Features + +- [`7b543ac`](https://github.com/zebratechnologies/zeta-flutter/commit/7b543ac7b92dc53a866af4de313c36b5728e912e) - Remove legacy code _(PR [#51](https://github.com/zebratechnologies/zeta-flutter/pull/51) by [@thelukewalton](https://github.com/thelukewalton))_ + +### :wrench: Chores -docs: update changelog and documentation +- [`ccbdeb9`](https://github.com/zebratechnologies/zeta-flutter/commit/ccbdeb93b6995eb3a3ff66562d957bc7e2470432) - Update Widgetbook to correct functionality _(PR [#52](https://github.com/zebratechnologies/zeta-flutter/pull/52) by [@thelukewalton](https://github.com/thelukewalton))_ -feat(type): Add xSmall and conform to latest figma designs. +## [0.1.1+21] - 2024-01-18 -- "Refactored the ZetaColors class for better customization +### :sparkles: New Features + +- [`6ae1269`](https://github.com/zebratechnologies/zeta-flutter/commit/6ae1269422180bbf2ecbfd05814969bfcbb725f1) - Update text styles to match latest designs _(PR [#50](https://github.com/zebratechnologies/zeta-flutter/pull/50) by [@thelukewalton](https://github.com/thelukewalton))_ +- [`977d7e6`](https://github.com/zebratechnologies/zeta-flutter/commit/977d7e6f7cb0c2b67085ab40f5cf1d410e5680c8) - Remove legacy code _(PR [#51](https://github.com/zebratechnologies/zeta-flutter/pull/51) by [@thelukewalton](https://github.com/thelukewalton))_ -The ZetaColors class was heavily refactored for better customization of variables such as brightness, contrast, color swatches, and additional color attributes. Several fields were made final for the overall class safety. This change improves color control on different themes for the application." +## [0.1.1+20] - 2024-01-17 + +### :sparkles: New Features -- "Updated the ZetaColor and Theme setup to use InheritedWidget +- [`35d1d7e`](https://github.com/zebratechnologies/zeta-flutter/commit/35d1d7eed509d4b89593c17e2a48a4a2c79a6ce4) - Update text styles to match latest designs _(PR [#50](https://github.com/zebratechnologies/zeta-flutter/pull/50) by [@thelukewalton](https://github.com/thelukewalton))_ + +### :wrench: Chores + +- [`6787220`](https://github.com/zebratechnologies/zeta-flutter/commit/67872203f0dc96de02e6945f0ac4409f95872262) - Organize ilb/ to match web _(PR [#49](https://github.com/zebratechnologies/zeta-flutter/pull/49) by [@thelukewalton](https://github.com/thelukewalton))_ + +## [0.1.1+19] - 2024-01-17 + +### :wrench: Chores + +- [`d1c0125`](https://github.com/zebratechnologies/zeta-flutter/commit/d1c012523e16ebbf3f38c7028598660cfc7b7ea9) - Organize ilb/ to match web _(PR [#49](https://github.com/zebratechnologies/zeta-flutter/pull/49) by [@thelukewalton](https://github.com/thelukewalton))_ + +### :flying_saucer: Other Changes + +- [`aff41a3`](https://github.com/zebratechnologies/zeta-flutter/commit/aff41a372a2e3ba7eef06e1d69dc3e57b423c3dd) - chore(deps): bump tj-actions/changed-files from 35 to 41 in /.github/workflows ([#48](https://github.com/zebratechnologies/zeta-flutter/pull/48)) + +Bumps +[tj-actions/changed-files](https://github.com/tj-actions/changed-files) +from 35 to 41. + +
+Release notes +

Sourced from tj-actions/changed-files's +releases.

+
+

v41

+

Changes in v41.0.1

+

What's Changed

+ +

Full Changelog: https://github.com/tj-actions/changed-files/compare/v41...v41.0.1

+
+

Changes in v41.0.0

+

🔥 🔥 BREAKING CHANGE 🔥 🔥

+

A new safe_output input is now available to prevent +outputting unsafe filename characters (Enabled by default). This would +escape characters in the filename that could be used for command +injection.

+
+

[!NOTE] +This can be disabled by setting the safe_output to false +this comes with a recommendation to store all outputs generated in an +environment variable first before using them.

+
+

Example

+
...
+    - name: Get changed files
+      id: changed-files
+      uses: tj-actions/changed-files@v40
+      with:
+safe_output: false # set to false because we are using an environment
+variable to store the output and avoid command injection.
+
- name: List all added files
+  env:
+    ADDED_FILES: ${{ steps.changed-files.outputs.added_files }}
+  run: |
+    for file in &quot;$ADDED_FILES&quot;; do
+      echo &quot;$file was added&quot;
+    done
+
+

... +

+

What's Changed

+ + +
+

... (truncated)

+
+
+Changelog +

Sourced from tj-actions/changed-files's +changelog.

+
+

Changelog

+

41.0.1 +- (2023-12-24)

+

🐛 Bug Fixes

+ +

⚙️ Miscellaneous Tasks

+
    +
  • deps: Update dependency eslint-plugin-prettier to +v5.1.2 (7aaf10d) +- (renovate[bot])
  • +
+

⬆️ Upgrades

+ +

Co-authored-by: jackton1 jackton1@users.noreply.github.com +(cc08e17) +- (tj-actions[bot])

+

41.0.0 +- (2023-12-23)

+

🐛 Bug Fixes

+ +

⏪ Reverts

+ +

(4f573fe) +- (Tonye Jack)

+

🔄 Update

+ +

Co-authored-by: renovate[bot] (1864078) +- (tj-actions[bot])

+ +

(47371c5) +- (Tonye Jack)

+

📝 Other

+
    +
  • Merge pull request from GHSA-mcph-m25j-8j63
  • +
+
    +
  • +

    feat: add safe_output input enabled by default

    +
  • +
  • +

    fix: migrate README to safe uses of interpolation

    +
  • +
+ +
+

... (truncated)

+
+
+Commits + +
+
+ +[![Dependabot compatibility +score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tj-actions/changed-files&package-manager=github_actions&previous-version=35&new-version=41)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't +alter it yourself. You can also trigger a rebase manually by commenting +`@dependabot rebase`. + +[//]: # "dependabot-automerge-start" +[//]: # "dependabot-automerge-end" + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: + +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits + that have been made to it +- `@dependabot merge` will merge this PR after your CI passes on it +- `@dependabot squash and merge` will squash and merge this PR after + your CI passes on it +- `@dependabot cancel merge` will cancel a previously requested merge + and block automerging +- `@dependabot reopen` will reopen this PR if it is closed +- `@dependabot close` will close this PR and stop Dependabot recreating + it. You can achieve the same result by closing it manually +- `@dependabot show ignore conditions` will show all + of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop + Dependabot creating any more for this major version (unless you reopen + the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop + Dependabot creating any more for this minor version (unless you reopen + the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop + Dependabot creating any more for this dependency (unless you reopen the + PR or upgrade to it yourself) + You can disable automated security fix PRs for this repo from the + [Security Alerts + page](https://github.com/zebratechnologies/zeta-flutter/network/alerts). + +
+ +Signed-off-by: dependabot[bot] +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> _(commit by [@dependabot[bot]](https://github.com/apps/dependabot))_ + +## [0.1.1+18] - 2024-01-10 + +### :flying_saucer: Other Changes + +- [`caea7bd`](https://github.com/zebratechnologies/zeta-flutter/commit/caea7bdc061149db64344526bd16cd5232a219c2) - chore(deps): bump tj-actions/changed-files from 35 to 41 in /.github/workflows ([#48](https://github.com/zebratechnologies/zeta-flutter/pull/48)) + +Bumps +[tj-actions/changed-files](https://github.com/tj-actions/changed-files) +from 35 to 41. + +
+Release notes +

Sourced from tj-actions/changed-files's +releases.

+
+

v41

+

Changes in v41.0.1

+

What's Changed

+ +

Full Changelog: https://github.com/tj-actions/changed-files/compare/v41...v41.0.1

+
+

Changes in v41.0.0

+

🔥 🔥 BREAKING CHANGE 🔥 🔥

+

A new safe_output input is now available to prevent +outputting unsafe filename characters (Enabled by default). This would +escape characters in the filename that could be used for command +injection.

+
+

[!NOTE] +This can be disabled by setting the safe_output to false +this comes with a recommendation to store all outputs generated in an +environment variable first before using them.

+
+

Example

+
...
+    - name: Get changed files
+      id: changed-files
+      uses: tj-actions/changed-files@v40
+      with:
+safe_output: false # set to false because we are using an environment
+variable to store the output and avoid command injection.
+
- name: List all added files
+  env:
+    ADDED_FILES: ${{ steps.changed-files.outputs.added_files }}
+  run: |
+    for file in &quot;$ADDED_FILES&quot;; do
+      echo &quot;$file was added&quot;
+    done
+
+

... +

+

What's Changed

+ + +
+

... (truncated)

+
+
+Changelog +

Sourced from tj-actions/changed-files's +changelog.

+
+

Changelog

+

41.0.1 +- (2023-12-24)

+

🐛 Bug Fixes

+ +

⚙️ Miscellaneous Tasks

+
    +
  • deps: Update dependency eslint-plugin-prettier to +v5.1.2 (7aaf10d) +- (renovate[bot])
  • +
+

⬆️ Upgrades

+ +

Co-authored-by: jackton1 jackton1@users.noreply.github.com +(cc08e17) +- (tj-actions[bot])

+

41.0.0 +- (2023-12-23)

+

🐛 Bug Fixes

+ +

⏪ Reverts

+ +

(4f573fe) +- (Tonye Jack)

+

🔄 Update

+ +

Co-authored-by: renovate[bot] (1864078) +- (tj-actions[bot])

+ +

(47371c5) +- (Tonye Jack)

+

📝 Other

+
    +
  • Merge pull request from GHSA-mcph-m25j-8j63
  • +
+
    +
  • +

    feat: add safe_output input enabled by default

    +
  • +
  • +

    fix: migrate README to safe uses of interpolation

    +
  • +
+ +
+

... (truncated)

+
+
+Commits + +
+
+ +[![Dependabot compatibility +score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tj-actions/changed-files&package-manager=github_actions&previous-version=35&new-version=41)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) + +Dependabot will resolve any conflicts with this PR as long as you don't +alter it yourself. You can also trigger a rebase manually by commenting +`@dependabot rebase`. + +[//]: # "dependabot-automerge-start" +[//]: # "dependabot-automerge-end" + +--- + +
+Dependabot commands and options +
+ +You can trigger Dependabot actions by commenting on this PR: + +- `@dependabot rebase` will rebase this PR +- `@dependabot recreate` will recreate this PR, overwriting any edits + that have been made to it +- `@dependabot merge` will merge this PR after your CI passes on it +- `@dependabot squash and merge` will squash and merge this PR after + your CI passes on it +- `@dependabot cancel merge` will cancel a previously requested merge + and block automerging +- `@dependabot reopen` will reopen this PR if it is closed +- `@dependabot close` will close this PR and stop Dependabot recreating + it. You can achieve the same result by closing it manually +- `@dependabot show ignore conditions` will show all + of the ignore conditions of the specified dependency +- `@dependabot ignore this major version` will close this PR and stop + Dependabot creating any more for this major version (unless you reopen + the PR or upgrade to it yourself) +- `@dependabot ignore this minor version` will close this PR and stop + Dependabot creating any more for this minor version (unless you reopen + the PR or upgrade to it yourself) +- `@dependabot ignore this dependency` will close this PR and stop + Dependabot creating any more for this dependency (unless you reopen the + PR or upgrade to it yourself) + You can disable automated security fix PRs for this repo from the + [Security Alerts + page](https://github.com/zebratechnologies/zeta-flutter/network/alerts). + +
+ +Signed-off-by: dependabot[bot] +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> _(commit by [@dependabot[bot]](https://github.com/apps/dependabot))_ + +## [0.1.1+16] - 2024-01-02 + +### :flying_saucer: Other Changes + +- [`225a059`](https://github.com/zebratechnologies/zeta-flutter/commit/225a059aa191f595db4982960c854c2e94c5bdc4) - create bottom sheet ([#45](https://github.com/zebratechnologies/zeta-flutter/pull/45)) + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +- [`656ca24`](https://github.com/zebratechnologies/zeta-flutter/commit/656ca2470112a171064f0d2d7477bf087a4f98bd) - Password input ([#47](https://github.com/zebratechnologies/zeta-flutter/pull/47)) + +Co-authored-by: github-actions +Co-authored-by: Atanas Yordanov _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +## [0.1.1+15] - 2024-01-02 + +### :flying_saucer: Other Changes + +- [`36b9808`](https://github.com/zebratechnologies/zeta-flutter/commit/36b980896602320114c845b68a99a1c4c2e03ecb) - Page banner ([#40](https://github.com/zebratechnologies/zeta-flutter/pull/40)) + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +- [`03f2ed7`](https://github.com/zebratechnologies/zeta-flutter/commit/03f2ed7fb7f34dd995cb850f67582b76a2132111) - create bottom sheet ([#45](https://github.com/zebratechnologies/zeta-flutter/pull/45)) + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +## [0.1.1+14] - 2023-12-21 + +### :flying_saucer: Other Changes + +- [`be7cf58`](https://github.com/zebratechnologies/zeta-flutter/commit/be7cf587995ef4a51c57fc497d91abfccf82c65b) - component floating action button ([#41](https://github.com/zebratechnologies/zeta-flutter/pull/41)) + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +- [`a5d7181`](https://github.com/zebratechnologies/zeta-flutter/commit/a5d71816ff9d6aa9426cda38ee9d6f1116a36496) - Page banner ([#40](https://github.com/zebratechnologies/zeta-flutter/pull/40)) + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +## [0.1.1+13] - 2023-12-21 + +### :flying_saucer: Other Changes + +- [`51caca7`](https://github.com/zebratechnologies/zeta-flutter/commit/51caca71fce57681cabeb82e979499da9a16f4c1) - Menu Items - horizontal & vertical ([#44](https://github.com/zebratechnologies/zeta-flutter/pull/44)) + +* Menu Items - horizontal & vertical The ZetaDefaults class was updated to Zeta inheriting from InheritedWidget. This change allows easy access to the Zeta theme settings (contrast, theme mode, theme data, color set) from anywhere in the widget tree. The ZetaAppBuilder function was updated to take in ThemeData and ThemeMode. The ZetaProvider was added to provide Zeta theming and contrast data down the widget tree. The code for the color and typography examples was adjusted to use the new Zeta context extension, instead of using Theme.of(context) to get colorScheme. This change was crucial to simplify the process of adapting the application visuals to different themes." -- "Improve theme management functionality in Zeta +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +- [`8fe8750`](https://github.com/zebratechnologies/zeta-flutter/commit/8fe87502764834cdbdcc7580bddaa5def36ae518) - component floating action button ([#41](https://github.com/zebratechnologies/zeta-flutter/pull/41)) + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +## [0.1.1+12] - 2023-12-20 + +### :flying_saucer: Other Changes + +- [`dcafe1f`](https://github.com/zebratechnologies/zeta-flutter/commit/dcafe1f2f0436adf75e9cb36e0c4dd1bb8015def) - fix sizings and replace icons with zeta icons ([#43](https://github.com/zebratechnologies/zeta-flutter/pull/43)) + +* fix sizings and replace icons with zeta icons + +- Refactor color swatch generation to utilize zeta + +--- + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +- [`231fa4b`](https://github.com/zebratechnologies/zeta-flutter/commit/231fa4b860d4ecbb551ba7ec65b783d26fed43a0) - Menu Items - horizontal & vertical ([#44](https://github.com/zebratechnologies/zeta-flutter/pull/44)) + +* Menu Items - horizontal & vertical + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +## [0.1.1+11] - 2023-12-19 + +### :flying_saucer: Other Changes + +- [`c506b81`](https://github.com/zebratechnologies/zeta-flutter/commit/c506b81c0f00b23192ddef4e484dabf8a0f36890) - Component accordion ([#39](https://github.com/zebratechnologies/zeta-flutter/pull/39)) + +* first draft of the ZetaAccordion + +* create ZetaAccordion component + +* add icon padding + +* add list separator & margin + +* Tag component ([#37](https://github.com/zebratechnologies/zeta-flutter/pull/37)) + +* initial + +* inital + +* initial + +* tag component + +* tag component + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions +Co-authored-by: Atanas Yordanov + +- add comments and more examples + +- pubspec + +- [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: Genoveva Georgieva <151932404+genovevageorgieva@users.noreply.github.com> +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +- [`10a075e`](https://github.com/zebratechnologies/zeta-flutter/commit/10a075ed9dc7aac6021ccc08fde574f520b2fc9e) - fix sizings and replace icons with zeta icons ([#43](https://github.com/zebratechnologies/zeta-flutter/pull/43)) + +* fix sizings and replace icons with zeta icons + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +## [0.1.1+10] - 2023-12-19 + +### :flying_saucer: Other Changes + +- [`c79686a`](https://github.com/zebratechnologies/zeta-flutter/commit/c79686a8221a029280332e144da3fdb6eca0ead8) - Tag component ([#37](https://github.com/zebratechnologies/zeta-flutter/pull/37)) + +* initial + +* inital + +* initial + +* tag component + +* tag component + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions +Co-authored-by: Atanas Yordanov _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +- [`df9eb66`](https://github.com/zebratechnologies/zeta-flutter/commit/df9eb66803c2f005099aa67a9394e520626401d3) - Component accordion ([#39](https://github.com/zebratechnologies/zeta-flutter/pull/39)) + +* first draft of the ZetaAccordion + +* create ZetaAccordion component + +* add icon padding + +* add list separator & margin + +* Tag component ([#37](https://github.com/zebratechnologies/zeta-flutter/pull/37)) + +* initial + +* inital + +* initial + +* tag component + +* tag component + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions +Co-authored-by: Atanas Yordanov + +- add comments and more examples + +- pubspec + +- [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: Genoveva Georgieva <151932404+genovevageorgieva@users.noreply.github.com> +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +## [0.1.1+9] - 2023-12-18 + +### :flying_saucer: Other Changes + +- [`541ec63`](https://github.com/zebratechnologies/zeta-flutter/commit/541ec633f31f117b685671a33342e57c89823434) - force Material 3 ([#38](https://github.com/zebratechnologies/zeta-flutter/pull/38)) + +* force Material 3 + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +- [`0be43ac`](https://github.com/zebratechnologies/zeta-flutter/commit/0be43acce7c8f3b69a778f95420f542f92e725cf) - Tag component ([#37](https://github.com/zebratechnologies/zeta-flutter/pull/37)) + +* initial + +* inital + +* initial + +* tag component + +* tag component + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions +Co-authored-by: Atanas Yordanov _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +## [0.1.1+8] - 2023-12-18 + +### :flying_saucer: Other Changes + +- [`4ca71f3`](https://github.com/zebratechnologies/zeta-flutter/commit/4ca71f3f83535e55c3af36961a5332fced0ecdbf) - Button component ([#36](https://github.com/zebratechnologies/zeta-flutter/pull/36)) + +* initial + +* format_error + +* [automated commit] lint format and import sort + +* button component optimizations + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +* colors change + +* colors change + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +- [`ba9062b`](https://github.com/zebratechnologies/zeta-flutter/commit/ba9062bb2268310a8c22d5230227c04afa178166) - force Material 3 ([#38](https://github.com/zebratechnologies/zeta-flutter/pull/38)) + +* force Material 3 + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +## [0.1.1+7] - 2023-12-18 + +### :flying_saucer: Other Changes + +- [`ac2b269`](https://github.com/zebratechnologies/zeta-flutter/commit/ac2b269c5806d2af310a063231abc1e412aea1a1) - fix the border of the indicator component ([#31](https://github.com/zebratechnologies/zeta-flutter/pull/31)) + +* fix the border of the component + +* [automated commit] lint format and import sort + +* inverseBorder + +* [automated commit] lint format and import sort + +* rounded = true by default + +* running on iPhone + +* try to adjust font height + +* horizontal alignment + +* try to remove the line height + +* stick to Material 2 for now + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +- [`cb330c7`](https://github.com/zebratechnologies/zeta-flutter/commit/cb330c7c1952ca2493b5d6c9272e2ade1050f53b) - Button component ([#36](https://github.com/zebratechnologies/zeta-flutter/pull/36)) + +* initial + +* format_error + +* [automated commit] lint format and import sort + +* button component optimizations + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +* colors change + +* colors change + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +## [0.1.1+6] - 2023-12-18 + +### :flying_saucer: Other Changes + +- [`354040c`](https://github.com/zebratechnologies/zeta-flutter/commit/354040c91731f53f9186ce0cbce341706dc30b3a) - Workcloud indicators ([#34](https://github.com/zebratechnologies/zeta-flutter/pull/34)) + +* initial + +* workcloud indicator + +* typo + +* . + +* [automated commit] lint format and import sort + +* . + +* . + +* . + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +* add default values + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +- [`88080ad`](https://github.com/zebratechnologies/zeta-flutter/commit/88080ada71811b47fe20f73dfba23c52e5223d38) - fix the border of the indicator component ([#31](https://github.com/zebratechnologies/zeta-flutter/pull/31)) + +* fix the border of the component + +* [automated commit] lint format and import sort + +* inverseBorder + +* [automated commit] lint format and import sort + +* rounded = true by default + +* running on iPhone + +* try to adjust font height + +* horizontal alignment + +* try to remove the line height + +* stick to Material 2 for now + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +## [0.1.1+5] - 2023-12-18 + +### :flying_saucer: Other Changes + +- [`1268c92`](https://github.com/zebratechnologies/zeta-flutter/commit/1268c92c7e0057cd166c40bc9ccd53ccf80c2fef) - App bar ([#35](https://github.com/zebratechnologies/zeta-flutter/pull/35)) + +* create ZetaAppBar in four variants with example screens + +* [automated commit] lint format and import sort + +* remove Flexible + +* rename + +* titleIcon should be of type Icon instead of Widget + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +- [`077d161`](https://github.com/zebratechnologies/zeta-flutter/commit/077d16138d5d3f579fbc2bfcba3048b1c5d28afc) - Workcloud indicators ([#34](https://github.com/zebratechnologies/zeta-flutter/pull/34)) + +* initial + +* workcloud indicator + +* typo + +* . + +* [automated commit] lint format and import sort + +* . -Removed code concerning getting a color's RGB hex code from 'color_extensions.dart', as it was seldom used. Updated 'zeta_flutter.dart' to unhide ZetaColorGetters. Adaptations in 'zeta.dart' included switching mediaBrightness to \_mediaBrightness for internal use and adding methods for accurate determination of color set and brightness settings based on the theme mode. Also, ZetaProvider was updated for 'system' theme mode support. example/lib/main.dart and example/lib/widgets.dart were updated to support these changes, including UI updates for seamless theme switching." +* . -- "Add theme update function and extend ZetaColorGetters +* . -Implemented a method in 'zeta.dart' to support updating the current theme data dynamically. Extended 'color_scheme.dart' by introducing \_ZetaColorProperties and updating ZetaColorGetters. These changes increase flexibility for theme management and provide a structured and accessible way to get Zeta colors through the theme context." +* [automated commit] lint format and import sort -- Remove theme_extensions.dart and move contents to colors.dart +* [automated commit] lint format and import sort + +* add default values + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +## [0.1.1+4] - 2023-12-15 + +### :flying_saucer: Other Changes + +- [`8f621dc`](https://github.com/zebratechnologies/zeta-flutter/commit/8f621dc82bd58ace9c994057420cdb1f41a74200) - Component ZetaAvatar ([#32](https://github.com/zebratechnologies/zeta-flutter/pull/32)) + +* add empty avatar example page; upgrade packages + +* add icons + +* [automated commit] lint format and import sort + +* create avatar component with examples + +* create ZetaIndicator component with examples + +* fix value + +* fix & improve; add badges (indicators) + +* add more comments + +* add factory constructors for the different types + +* add widgetbook + +* add avatar to widgetbook + +* [automated commit] lint format and import sort + +* Badge ([#29](https://github.com/zebratechnologies/zeta-flutter/pull/29)) + +* initial + +* Badge + +* fix test + +* [automated commit] lint format and import sort + +* change default border type + +* fix test + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +* fix example + +* fix sizing + +* add foreground colors + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions + +- [automated commit] lint format and import sort + +- replace photo with image + +- [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions +Co-authored-by: Genoveva Georgieva <151932404+genovevageorgieva@users.noreply.github.com> _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +- [`350adec`](https://github.com/zebratechnologies/zeta-flutter/commit/350adecf4b0498afc6a476a653d2223a7cb74e0f) - App bar ([#35](https://github.com/zebratechnologies/zeta-flutter/pull/35)) + +* create ZetaAppBar in four variants with example screens + +* [automated commit] lint format and import sort + +* remove Flexible + +* rename + +* titleIcon should be of type Icon instead of Widget + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +## [0.1.1+3] - 2023-12-15 + +### :flying_saucer: Other Changes + +- [`5f0a33e`](https://github.com/zebratechnologies/zeta-flutter/commit/5f0a33e8e3b92de83d83170f267e6cd3f5fca068) - Merge from zebradevs ([#27](https://github.com/zebratechnologies/zeta-flutter/pull/27)) + +* Merge from zebradevs + +* spelling and finals + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions _(commit by [@thelukewalton](https://github.com/thelukewalton))_ + +- [`b20aef1`](https://github.com/zebratechnologies/zeta-flutter/commit/b20aef109944e6cfc02db9b0041e7a6009c9e52e) - Component ZetaAvatar ([#32](https://github.com/zebratechnologies/zeta-flutter/pull/32)) + +* add empty avatar example page; upgrade packages + +* add icons + +* [automated commit] lint format and import sort + +* create avatar component with examples + +* create ZetaIndicator component with examples + +* fix value + +* fix & improve; add badges (indicators) + +* add more comments + +* add factory constructors for the different types + +* add widgetbook + +* add avatar to widgetbook + +* [automated commit] lint format and import sort + +* Badge ([#29](https://github.com/zebratechnologies/zeta-flutter/pull/29)) + +* initial + +* Badge + +* fix test + +* [automated commit] lint format and import sort + +* change default border type + +* fix test + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +* fix example + +* fix sizing + +* add foreground colors + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions + +- [automated commit] lint format and import sort + +- replace photo with image + +- [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions +Co-authored-by: Genoveva Georgieva <151932404+genovevageorgieva@users.noreply.github.com> _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +## [0.1.1+2] - 2023-12-13 + +### :flying_saucer: Other Changes + +- [`063386f`](https://github.com/zebratechnologies/zeta-flutter/commit/063386f83490bf3bfb6d26fc6a95dd445006d656) - some sizings were not exactly as shown in Figma ([#33](https://github.com/zebratechnologies/zeta-flutter/pull/33)) + +* . + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +- [`1c8ca4c`](https://github.com/zebratechnologies/zeta-flutter/commit/1c8ca4c4b5dbd8378710a18765303a6d37472cf0) - Merge from zebradevs ([#27](https://github.com/zebratechnologies/zeta-flutter/pull/27)) + +* Merge from zebradevs + +* spelling and finals + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions _(commit by [@thelukewalton](https://github.com/thelukewalton))_ + +## [0.1.1+1] - 2023-12-01 + +- feature: Refactor theme declaration and introduce theme service Theme extensions were deleted and its contents were moved to colors.dart to consolidate all color-related codes in one file for easier navigation and editing. Additional enhancements include optimizing color assignments and making ZetaColors immutable for more robust color management. -- Refactor code for color theme and add theme switcher +## [0.1.0+9] - 2023-12-12 -Refactored codebase to improve the color theme handling: relocated theme related methods to colors.dart from theme_extensions.dart for consolidated color theme data. Optimized color assignments by leveraging the 'copyWith' method, allowing more efficient color management. Introduced the immutability of ZetaColors to enhance robustness. Bumped version in pubspec.yaml to 0.0.1+13 due to these changes. Renamed theme.dart to theme_data.dart for more semantic file naming. Added 'identifier' to the ZetaThemeData for easier theme identification. -The visible application change is an added ThemeSwitcher in the example app, offering a UI to switch between different predefined themes. +### :flying_saucer: Other Changes -- Add ZetaThemeService and theme switcher in example app +- [`b97fd3b`](https://github.com/zebratechnologies/zeta-flutter/commit/b97fd3bd881a85c8d4ee4f5ea5856e7a8ebf2d09) - Badge ([#29](https://github.com/zebratechnologies/zeta-flutter/pull/29)) -Implemented ZetaThemeService as an abstract class, providing structure for loading and saving themes within the app. Removed an obsolete comment within the contrast.dart and made necessary imports in zeta.dart. Asynchronous theme loading is added during app startup and saving is done upon theme updates. Also, for user-interaction, an exclusive ThemeSwitcher widget is added in the example app allowing users to select between available themes. This improves user experience, and optimizes theme handling and application performance. +* initial -- Refactor color swatch generation to utilize zeta +* Badge + +* fix test + +* [automated commit] lint format and import sort + +* change default border type + +* fix test + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +* fix example + +* fix sizing -Refactored color swatch generation in color_example.dart to use Zeta instead of directly using the Theme. Now the brightness for ZetaColorSwatch is being pulled from zeta object rather than theme. This ensures consistency across different parts of the application where Zeta is used. Also changed theme.colorScheme.surface to colors.surfacePrimary for better readability, and alignment with use of zeta object. +* add foreground colors -- Add icon colors to color scheme +* [automated commit] lint format and import sort -Extended the color scheme in colors.dart to include default, subtle, disabled, and inverse icon colors. These were added to ensure consistent icon colors across the application and support dark mode by allowing inverted color swatches. +--- -- Refactor theme switch settings and add new features +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ -Renamed 'theme_switch.dart' to 'theme_color_switch.dart' and added two new files 'theme_contrast_switch.dart' and 'theme_mode_switch.dart' in order to separate the theme settings logically into distinct features - Theme Color Switch, Theme Contrast Switch and Theme Mode Switch respectively. Also, the theme application feature has been refactored within 'widgets.dart' to use the newly created theme features instead of the old theme switch. This enhances modularity and the user's ability to switch theme settings easily. +- [`30fd121`](https://github.com/zebratechnologies/zeta-flutter/commit/30fd12131af2549c0b5b31bf89e12553557a009b) - some sizings were not exactly as shown in Figma ([#33](https://github.com/zebratechnologies/zeta-flutter/pull/33)) -- Update method naming for consistent architecture in text.dart +* . -Changed the method name 'withColor' to 'themeWithColor' in text.dart for consistency with other part of the architecture and for better readability. This change supports the shift towards a consistently designed application architecture and helps developers easily decipher the role of the method in the code. +* [automated commit] lint format and import sort -- Update color scheme mapping and library version in colors.dart and pubspec.lock +--- -Refined the color mapping in ZetaColorScheme in colors.dart by replacing effectiveSurfaceTertiary with textDefault, enhancing the clarity of backdrop's color role. Concurrently, version of multiple dependencies in pubspec.lock are updated to benefit from recent fixes and improvements in those libraries. +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ -- Change `Color` to `ZetaColorSwatch` in theme files +## [0.1.0+8] - 2023-12-11 -Adjusted the class references in colors.dart from `Color` to `ZetaColorSwatch` to provide a more consistent color swatch across the app. The swatch allows for more flexibility in using color variations. Adjustments were also made in color_scheme.dart and color_swatch.dart to include better explanatory messages and use standard dart documentation format. Changes in custom_docs/components/Color/flutter.md were made to align with these updates. +### :flying_saucer: Other Changes + +- [`36f72c1`](https://github.com/zebratechnologies/zeta-flutter/commit/36f72c1df09fecc9d3eeb6dbf1bbf889493529c0) - Component indicator ([#30](https://github.com/zebratechnologies/zeta-flutter/pull/30)) + +* add icons + +* [automated commit] lint format and import sort + +* create ZetaIndicator component with examples + +* fix value + +* add more comments + +* add widgetbook + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +- [`895bbfa`](https://github.com/zebratechnologies/zeta-flutter/commit/895bbfa876c7fea331d6bdb14c77e25632aab28d) - Badge ([#29](https://github.com/zebratechnologies/zeta-flutter/pull/29)) + +* initial + +* Badge + +* fix test + +* [automated commit] lint format and import sort + +* change default border type + +* fix test + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +* fix example + +* fix sizing + +* add foreground colors + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +## [0.1.0+7] - 2023-12-11 + +### :flying_saucer: Other Changes + +- [`19e7d93`](https://github.com/zebratechnologies/zeta-flutter/commit/19e7d933456938d473f1f5c6ef2696e02349e196) - Priority Pill ([#28](https://github.com/zebratechnologies/zeta-flutter/pull/28)) -- Enhance contrast and color handling in theme files +* Priority Pill -Removed 'flutter.md' as it is no longer required due to improvements made in contrast and color handling. For better accessibility support, 'contrast.dart' was refactored for better contrast handling and 'color_extensions.dart' now includes a mechanism to generate color swatch based on contrast ratio. Also, 'zeta.dart' was updated to adapt to the system's brightness providing better user experience. Overall, these adjustments aim to enhance accessibility and user experience, apart from simplifying the codebase. +* [automated commit] lint format and import sort -- Add LICENSE-3RD-PARTY for third-party libraries +* remove unnecessary param -Introduced license details for third-party libraries used in the project. MIT license applies to 'tinycolor' and SIL Open Font License applies to 'IBMPlexSans'. This ensures proper acknowledgement and licensure compliance for used third-party resources. +* [automated commit] lint format and import sort -- Set up with ZDS Analysis +--- + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +- [`fe4fe8d`](https://github.com/zebratechnologies/zeta-flutter/commit/fe4fe8d088fddf9443ce5810a1d2c068b13c75d9) - Component indicator ([#30](https://github.com/zebratechnologies/zeta-flutter/pull/30)) + +* add icons + +* [automated commit] lint format and import sort + +* create ZetaIndicator component with examples + +* fix value + +* add more comments + +* add widgetbook + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +## [0.1.0+6] - 2023-12-08 + +### :flying_saucer: Other Changes + +- [`2f21a18`](https://github.com/zebratechnologies/zeta-flutter/commit/2f21a18e30425fe91fe7bb0ee3c4df7ab8baca35) - add icons ([#24](https://github.com/zebratechnologies/zeta-flutter/pull/24)) + +* add icons + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +- [`2442420`](https://github.com/zebratechnologies/zeta-flutter/commit/2442420da15739e538d50e14c2a18c63de8d6f96) - Priority Pill ([#28](https://github.com/zebratechnologies/zeta-flutter/pull/28)) + +* Priority Pill + +* [automated commit] lint format and import sort + +* remove unnecessary param + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: github-actions _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +## [0.1.0+5] - 2023-12-08 + +### :flying_saucer: Other Changes -## [0.0.1+12] - 2023-09-06 +- [`ef44d24`](https://github.com/zebratechnologies/zeta-flutter/commit/ef44d244589a7cc652a934f25a5122bbd1657c05) - status_label ([#25](https://github.com/zebratechnologies/zeta-flutter/pull/25)) + +* status_label + +* [automated commit] lint format and import sort + +* spacing changes + +* extract BorderType in utils + +* chore(deps): bump tj-actions/branch-names in /.github/workflows ([#26](https://github.com/zebratechnologies/zeta-flutter/pull/26)) + +Bumps [tj-actions/branch-names](https://github.com/tj-actions/branch-names) from 5.1 to 7.0.7. + +- [Release notes](https://github.com/tj-actions/branch-names/releases) +- [Changelog](https://github.com/tj-actions/branch-names/blob/main/HISTORY.md) +- [Commits](https://github.com/tj-actions/branch-names/compare/v5.1...v7.0.7) + +--- + +updated-dependencies: + +- dependency-name: tj-actions/branch-names + dependency-type: direct:production + ... + +Signed-off-by: dependabot[bot] +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> + +- [automated commit] lint format and import sort + +--- + +Signed-off-by: dependabot[bot] +Co-authored-by: github-actions +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +- [`c90349b`](https://github.com/zebratechnologies/zeta-flutter/commit/c90349b104f3000956f821934b01dfd74f37a5e2) - add icons ([#24](https://github.com/zebratechnologies/zeta-flutter/pull/24)) + +* add icons + +* [automated commit] lint format and import sort + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +## [0.1.0+4] - 2023-12-06 + +### :wrench: Chores + +- [`0794b08`](https://github.com/zebratechnologies/zeta-flutter/commit/0794b08d029e9954457dbfac56bd576aaf8f0e82) - **deps**: bump tj-actions/branch-names in /.github/workflows _(PR [#26](https://github.com/zebratechnologies/zeta-flutter/pull/26) by [@dependabot[bot]](https://github.com/apps/dependabot))_ + +### :flying_saucer: Other Changes + +- [`65bf57f`](https://github.com/zebratechnologies/zeta-flutter/commit/65bf57fc1d7f13d4017b0e21f5f52d62552b502c) - status_label ([#25](https://github.com/zebratechnologies/zeta-flutter/pull/25)) + +* status_label + +* [automated commit] lint format and import sort + +* spacing changes + +* extract BorderType in utils + +* chore(deps): bump tj-actions/branch-names in /.github/workflows ([#26](https://github.com/zebratechnologies/zeta-flutter/pull/26)) + +Bumps [tj-actions/branch-names](https://github.com/tj-actions/branch-names) from 5.1 to 7.0.7. + +- [Release notes](https://github.com/tj-actions/branch-names/releases) +- [Changelog](https://github.com/tj-actions/branch-names/blob/main/HISTORY.md) +- [Commits](https://github.com/tj-actions/branch-names/compare/v5.1...v7.0.7) + +--- + +updated-dependencies: + +- dependency-name: tj-actions/branch-names + dependency-type: direct:production + ... + +Signed-off-by: dependabot[bot] +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> + +- [automated commit] lint format and import sort + +--- + +Signed-off-by: dependabot[bot] +Co-authored-by: github-actions +Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> _(commit by [@genovevageorgieva](https://github.com/genovevageorgieva))_ + +## [0.1.0+3] - 2023-12-06 + +### :wrench: Chores + +- [`63b94a6`](https://github.com/zebratechnologies/zeta-flutter/commit/63b94a60da570ce3848aa9c1af7a129a2c952399) - **deps**: bump tj-actions/branch-names in /.github/workflows _(PR [#26](https://github.com/zebratechnologies/zeta-flutter/pull/26) by [@dependabot[bot]](https://github.com/apps/dependabot))_ + +### :flying_saucer: Other Changes + +- [`f333429`](https://github.com/zebratechnologies/zeta-flutter/commit/f333429083f0cf790627211788e69285bcff3a37) - Cleanup dart warnings; fix text scale ([#23](https://github.com/zebratechnologies/zeta-flutter/pull/23)) + +* fix endtemplate in comments; upgrade flutter packages + +* remove unnecessary text scaling + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + +## [0.1.0+2] - 2023-12-01 ### :wrench: Chores -- [`6a2834e`](https://github.com/zebratechnologies/zeta-flutter/commit/6a2834e762c238d3927d83a239490250b1687b64) - Tidy, reorganise and prepare repo _(commit by [@thelukewalton](https://github.com/thelukewalton))_ +- [`d22dd29`](https://github.com/zebratechnologies/zeta-flutter/commit/d22dd29b506affccc8cdc7ecfb15a57fcf330646) - Tidy, reorganise and prepare repo _(commit by [@thelukewalton](https://github.com/thelukewalton))_ ### :flying_saucer: Other Changes +- [`199328c`](https://github.com/zebratechnologies/zeta-flutter/commit/199328c28ccfa8a05a1494c08ad93aaf13dd3b28) - Update to mirror 0.1.0+1 from ZebraDevs _(commit by [@thelukewalton](https://github.com/thelukewalton))_ +- [`0ee6171`](https://github.com/zebratechnologies/zeta-flutter/commit/0ee6171e779c6db9995308aa35f2f598b9db372c) - Cleanup dart warnings; fix text scale ([#23](https://github.com/zebratechnologies/zeta-flutter/pull/23)) + +* fix endtemplate in comments; upgrade flutter packages + +* remove unnecessary text scaling + +* [automated commit] lint format and import sort + +--- + +Co-authored-by: Atanas Yordanov +Co-authored-by: github-actions _(commit by [@atanasyordanov21](https://github.com/atanasyordanov21))_ + - [`f91e8ef`](https://github.com/zebratechnologies/zeta-flutter/commit/f91e8ef85c0a1670227d66bd441513bc33e6242c) - Feature/color ([#21](https://github.com/zebratechnologies/zeta-flutter/pull/21)) * feat(color): Adding color defs @@ -95,15 +1604,15 @@ Introduced license details for third-party libraries used in the project. MIT li * bug(type): Fixing reset height and tests failing _(commit by [@thelukewalton](https://github.com/thelukewalton))_ -## 0.0.1+11 - 2023-08-09 +## [0.0.1+11] - 2023-08-09 ### :sparkles: New Features - [`193dc42`](https://github.com/zebratechnologies/zeta-flutter/commit/193dc42c8e7419d9087afdffce0eae915af12819) - Color ([#21](https://github.com/zebratechnologies/zeta-flutter/pull/21)) _(commit by [@thelukewalton](https://github.com/thelukewalton))_ - - [`a605819`](https://github.com/zebratechnologies/zeta-flutter/commit/a60581973764b5d06711fe6470f9963af934b7ad) - Adding color defs by [@thelukewalton](https://github.com/thelukewalton) - - [`f519cd8`](https://github.com/zebratechnologies/zeta-flutter/commit/f519cd856c7b4793ea7e24dc16f3abba0cffcf66) - starting colorswatch util by [@thelukewalton](https://github.com/thelukewalton) - - [`7445db0`](https://github.com/zebratechnologies/zeta-flutter/commit/7445db0b7da2434f5a55d3067369b3bd35df363b) - adding widgetbook and tests by [@thelukewalton](https://github.com/thelukewalton) +- [`a605819`](https://github.com/zebratechnologies/zeta-flutter/commit/a60581973764b5d06711fe6470f9963af934b7ad) - Adding color defs by [@thelukewalton](https://github.com/thelukewalton) +- [`f519cd8`](https://github.com/zebratechnologies/zeta-flutter/commit/f519cd856c7b4793ea7e24dc16f3abba0cffcf66) - starting colorswatch util by [@thelukewalton](https://github.com/thelukewalton) +- [`7445db0`](https://github.com/zebratechnologies/zeta-flutter/commit/7445db0b7da2434f5a55d3067369b3bd35df363b) - adding widgetbook and tests by [@thelukewalton](https://github.com/thelukewalton) ### :bug: Bug Fixes @@ -111,7 +1620,7 @@ Introduced license details for third-party libraries used in the project. MIT li - [`3479adb`](https://github.com/zebratechnologies/zeta-flutter/commit/3479adb574c9ec1073552f888631f7cee12fe4cb) -bug(platforms): adding windows into example by [@thelukewalton](https://github.com/thelukewalton) - [`70a6144`](https://github.com/zebratechnologies/zeta-flutter/commit/70a614446c4d526315eb3229478d89dbd1c031de) - bug(type): Fixing reset height and tests failing by [@thelukewalton](https://github.com/thelukewalton) -## 0.0.1+10 - 2023-07-11 +## [0.0.1+10] - 2023-07-11 ### :sparkles: New Features @@ -124,7 +1633,7 @@ Introduced license details for third-party libraries used in the project. MIT li - [`988964e`](https://github.com/zebratechnologies/zeta-flutter/commit/988964e122128c4f9e4423fd849b70b6283ccea7) - removing unused dependency; by [@thelukewalton](https://github.com/thelukewalton) -## 0.0.1+9 - 2023-03-28 +## [0.0.1+9]- 2023-03-28 ### :sparkles: New Features @@ -144,7 +1653,7 @@ Introduced license details for third-party libraries used in the project. MIT li - [`d591856`](https://github.com/zebratechnologies/zeta-flutter/commit/d59185680879bf2f938c4f2a6bd2328f29a3ddd2) - test _(commit by [@thelukewalton](https://github.com/thelukewalton))_ - [`1dcbcae`](https://github.com/zebratechnologies/zeta-flutter/commit/1dcbcaec2600210efcefc80861c29aaa7e44c27e) - removing hardcoded shas _(PR [#19](https://github.com/zebratechnologies/zeta-flutter/pull/19) by [@thelukewalton](https://github.com/thelukewalton))_ -## 0.0.1+6 - Spacing - 2023-03-06 +## [0.0.1+6]- Spacing - 2023-03-06 ### :sparkles: New Features @@ -159,7 +1668,7 @@ Introduced license details for third-party libraries used in the project. MIT li - [`1dc0e1b`](https://github.com/zebratechnologies/zeta-flutter/commit/1dc0e1b64cb870685110516c5159b20fb903f2c3) - Update README.md _(commit by [@benken](https://github.com/benken))_ -## 0.0.1+5 - Grid - 2023-02-17 +## [0.0.1+5] - Grid - 2023-02-17 ### :sparkles: New Features @@ -176,8 +1685,28 @@ Introduced license details for third-party libraries used in the project. MIT li - [`4acf3c1`](https://github.com/zebratechnologies/zeta-flutter/commit/4acf3c1134b6c8d17827d8e2c665250d6f6ead1d) - fix(actions) Fix action refs _(PR [#7](https://github.com/zebratechnologies/zeta-flutter/pull/7) by [@thelukewalton](https://github.com/thelukewalton))_ - [`83e073b`](https://github.com/zebratechnologies/zeta-flutter/commit/83e073b16808d89373a74dba35172bb7a978e765) - fix(actions) another attempt at fixing actions checkout _(PR [#8](https://github.com/zebratechnologies/zeta-flutter/pull/8) by [@thelukewalton](https://github.com/thelukewalton))_ -## 0.0.1+1 - Initial setup +## [0.0.1+1] - Initial setup - Initial setup -[0.0.1+12]: https://github.com/zebratechnologies/zeta-flutter/compare/0.0.1+11...0.0.1+12 +[0.1.1+2]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.0+9...0.1.1+2 +[0.1.1+3]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+2...0.1.1+3 +[0.1.1+4]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+3...0.1.1+4 +[0.1.1+5]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+4...0.1.1+5 +[0.1.1+6]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+5...0.1.1+6 +[0.1.1+7]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+6...0.1.1+7 +[0.1.1+8]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+7...0.1.1+8 +[0.1.1+9]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+8...0.1.1+9 +[0.1.1+10]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+9...0.1.1+10 +[0.1.1+11]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+10...0.1.1+11 +[0.1.1+12]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+11...0.1.1+12 +[0.1.1+13]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+12...0.1.1+13 +[0.1.1+14]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+13...0.1.1+14 +[0.1.1+15]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+14...0.1.1+15 +[0.1.1+16]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+15...0.1.1+16 +[0.1.1+18]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+17...0.1.1+18 +[0.1.1+19]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+18...0.1.1+19 +[0.1.1+20]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+19...0.1.1+20 +[0.1.1+21]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+20...0.1.1+21 +[0.1.1+22]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+21...0.1.1+22 +[0.1.1+23]: https://github.com/zebratechnologies/zeta-flutter/compare/0.1.1+22...0.1.1+23 diff --git a/LICENSE b/LICENSE index c091ab8d..d4f367ee 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2023 Zebra Technologies +Copyright 2024 Zebra Technologies Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index eb8c75bd..7437d3b5 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Zeta is the new, formal, standardized Zebra Design System based off the successe To install `zeta_flutter`, follow the instructions [here](https://pub.dev/packages/zeta_flutter/install). ## Usage + Zeta offers flexibility in theming through its `ZetaProvider` widget. Here's a breakdown of its features: ### Setting the Initial Theme Mode @@ -83,6 +84,7 @@ To tie everything together, use the `ZetaProvider` constructor. The `builder` ar ``` With these configurations, Zeta makes it easy to achieve consistent theming throughout your Flutter application. + ## Viewing the components To view examples of all the components in the library, you can run the example app in this repo or go to [Zeta](https://zeta-ds.web.app/) diff --git a/custom_docs/components/Grid/flutter.md b/custom_docs/components/Grid/flutter.md deleted file mode 100644 index 0edc52d2..00000000 --- a/custom_docs/components/Grid/flutter.md +++ /dev/null @@ -1,59 +0,0 @@ -## ZetaGrid Usage - -To import ZetaGrid into a Dart file: - -```dart -import 'package:zeta_flutter/zeta_flutter.dart'; -``` - -### Example - -```dart -import 'package:zeta_flutter/zeta_flutter.dart'; -class ZetaGridExample extends StatelessWidget{ - const ZetaGridExample({Key? key}) : super(key: key); - @override - Widget build(BuildContext context) { - return ZetaGrid(...); - } -} -``` - -### Setup - -To ensure all components work as intended, applications should have ZetaTheme.zeta applied at their top level; typically within MaterialApp or wrapping its equivalent: - -```dart -import 'package:flutter/material.dart'; -import 'package:zeta_flutter/src/theme/theme.dart'; -class MyApp extends StatelessWidget { - const MyApp({super.key}); - @override - Widget build(BuildContext context) { - return MaterialApp( - theme: ZetaTheme.zeta, - builder: (context, child) => ..., - ); - } -} -``` - -or - -```dart -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:zeta_flutter/src/theme/theme.dart'; -class MyApp extends StatelessWidget { - const MyApp({super.key}); - @override - Widget build(BuildContext context) { - return Theme( - data: ZetaTheme.zeta, - child: CupertinoApp( - builder: (context, child) => ..., - ), - ); - } -} -``` diff --git a/custom_docs/components/Spacing/flutter.md b/custom_docs/components/Spacing/flutter.md deleted file mode 100644 index 6eddb8b6..00000000 --- a/custom_docs/components/Spacing/flutter.md +++ /dev/null @@ -1,198 +0,0 @@ -## ZetaSpacing Usage - -To import ZetaSpacing into a Dart file: - -```dart -import 'package:zeta_flutter/zeta_flutter.dart'; -``` - -### Example - -```dart -import 'package:zeta_flutter/zeta_flutter.dart'; - -class ZetaSpacingExample extends StatelessWidget{ - const ZetaSpacingExample({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ZetaSpacing(...); - } -} -``` - -### Setup - -To ensure all components work as intended, applications should have ZetaTheme.zeta applied at their top level; typically within MaterialApp or wrapping its equivalent: - -```dart -import 'package:flutter/material.dart'; -import 'package:zeta_flutter/src/theme/theme.dart'; - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - theme: ZetaTheme.zeta, - builder: (context, child) => ..., - ); - } -} -``` - -or - -```dart - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:zeta_flutter/src/theme/theme.dart'; - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return Theme( - data: ZetaTheme.zeta, - child: CupertinoApp( - builder: (context, child) => ..., - ), - ); - } -} -``` - -### ZetaSpacing - -ZetaSpacing provides four options for spacing - -- `square` - applies inset to all 4 sides equally. -- `squish` - applies inset to top and bottom only. -- `inline` - applies inset to start and end only. -- `inlineStart` - applies inset to start only. For LTR locales this is the left, for RTL locales this is the right. -- `inlineEnd` - applies inset to end only. For LTR locales this is the right, for RTL locales this is the left. -- `stack` - applies inset to bottom only. - -These spacings should be applied with the following doubles: - -- `x0` - 0 -- `x1` - 4 - also `xxs` -- `x2` - 8 - also `xs` -- `x3` - 12 - also `s` -- `x4` - 16 - also `b` -- `x5` - 20 -- `x6` - 24 - also `m` -- `x7` - 28 -- `x8` - 32 - also `l` -- `x9` - 36 -- `x10` - 40 -- `x11` - 44 -- `x12` - 48 -- `x13` - 52 -- `x14` - 58 -- `x16` - 64 - also `xl` -- `x20` - 80 - also `xxl` -- `x24` - 96 - also `xxxl` - -Spacing components can be used in multiple ways: - -### ZetaSpacing() widget with named constructors. - -```dart -const ZetaSpacing.square( - Text('Example'), - size: Dimensions.x1, -), -const ZetaSpacing.squish( - Text('Example'), - size: Dimensions.x1, -), -const ZetaSpacing.stack( - Text('Example'), - size: Dimensions.x1, -) -const ZetaSpacing.inline( - Text('Example'), - size: Dimensions.x1, -) -``` - -This is the preferred way to use ZetaSpacing. Having a const, named constructor makes the intuitive and efficient. - -- Const constructor -- Can wrap any Widget. -- Padding inset around widget. - -
- -### ZetaSpacing() widget. - -```dart -const ZetaSpacing( - Text('Example'), - size: Dimensions.x1, - type: ZetaSpacingType.square, -), -const ZetaSpacing( - Text('Example'), - size: Dimensions.x1, - type: ZetaSpacingType.squish, -), -const ZetaSpacing( - Text('Example'), - size: Dimensions.x1, - type: ZetaSpacingType.stack, -), -const ZetaSpacing( - Text('Example'), - size: Dimensions.x1, - type: ZetaSpacingType.inline, -), -``` - -This method is less intuitive than using the named constructor, although it does still provide a const constructor for efficiency. - -- Having a const constructor makes this a preferred approach to spacing. -- Can wrap any Widget. -- Padding inset around widget. - -
- -### SpacingSize extension on double. - -```dart -Container( - padding: Dimensions.x1.square, - margin: Dimensions.x10.squish, - child: Text('Example'), -), -Container( - padding: Dimensions.x1.stack, - margin: Dimensions.x10.inline, - child: Text('Example'), -), -``` - -- Should be used with defined sizes: x0, x1,... or xxs, xs,... (although can be used on any double). -- Can be used for either padding or margin. -- Can be wrapped around any Widget, or used as a parameter. - -
- -### SpacingWidget extension on Widget. - -```dart -Text('Example').square(Dimensions.x1), -Text('Example').squish(Dimensions.x1), -Text('Example').stack(Dimensions.x1), -Text('Example').inline(Dimensions.x1), -Text('Example').inlineStart(Dimensions.x1), -Text('Example').inlineEnd(Dimensions.x1), -``` - -- Should be used with defined sizes: x0, x1,... or xxs, xs,... (although can be used on any double). -- Padding inset around widget. -- Can be applied to any widget. diff --git a/custom_docs/components/Typography/flutter.md b/custom_docs/components/Typography/flutter.md deleted file mode 100644 index 3bd74554..00000000 --- a/custom_docs/components/Typography/flutter.md +++ /dev/null @@ -1,98 +0,0 @@ -## ZetaText Usage - -To import ZetaText into a Dart file: - -```dart -import 'package:zeta_flutter/zeta_flutter.dart'; -``` - -### Example - -```dart -import 'package:zeta_flutter/zeta_flutter.dart'; - -class ZetaTextExample extends StatelessWidget{ - const ZetaTextExample({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ZetaText(...); - } -} -``` - -### Setup - -To ensure all components work as intended, applications should have ZetaTheme.zeta applied at their top level; typically within MaterialApp or wrapping its equivalent: - -```dart -import 'package:flutter/material.dart'; -import 'package:zeta_flutter/src/theme/theme.dart'; - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - theme: ZetaTheme.zeta, - builder: (context, child) => ..., - ); - } -} -``` - -or - -```dart - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:zeta_flutter/src/theme/theme.dart'; - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return Theme( - data: ZetaTheme.zeta, - child: CupertinoApp( - builder: (context, child) => ..., - ), - ); - } -} -``` - -### ZetaText - -Zeta text styles are within ZetaTheme.zeta, and so will be automatically applied to many widgets that use the built in text theme. When building components, text should not have any spacing by default, and so it is recommended to use the individual text styles with Text, rather than using ZetaText as this will by default apply a x2 squish padding (top and bottom). - -Zeta Typography styles can be used in multiple ways: - -### ZetaText widget with named constructors - -```dart -const ZetaText.bodySmall('Example'), -const ZetaText.displayLarge('Example'), -``` - -This is the preferred way to display text. Having a const, named constructor with helper parameters makes the intuitive and efficient. Constructors exist for all pre-defined text styles and helper functions exist to support all tokens. - -### ZetaText widget - -```dart -const ZetaText('Example', style: ZetaText.zetaBodySmall) -``` - -This method is less intuitive than using the named constructor, although it does still provide a const constructor for efficiency. Having a const, named constructor with helper functions makes the intuitive and efficient. Constructors exist for all pre-defined text styles and helper parameters exist to support all tokens. - -### ZetaText styles - -```dart -Text('Example', style: ZetaText.zetaBodySmall) -Text('Example', style: Theme.of(context).bodySmall) -``` - -Using the base styles gives the greatest flexibility as styles can be extended or added to where needed, at the expense of following the specifications exactly. As ZetaText.textTheme is within the base app style, we can use context to get the styles. This greatly speed up the process of applying Zeta to a pre-existing application. Using this method means helper parameters (such as caps) can not be used and spacing is not applied. This can be useful in components where spacing should be zero. diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index f5c285f1..f6342e74 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -3,21 +3,28 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS DEPENDENCIES: - Flutter (from `Flutter`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) EXTERNAL SOURCES: Flutter: :path: Flutter path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 -COCOAPODS: 1.13.0 +COCOAPODS: 1.14.3 diff --git a/example/lib/home.dart b/example/lib/home.dart index d455a251..aaafbe7b 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -1,10 +1,16 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:zeta_example/pages/color_example.dart'; -import 'package:zeta_example/pages/checkbox_example.dart'; -import 'package:zeta_example/pages/grid_example.dart'; -import 'package:zeta_example/pages/spacing_example.dart'; -import 'package:zeta_example/pages/typography_example.dart'; +import 'package:zeta_example/pages/components/accordion_example.dart'; +import 'package:zeta_example/pages/components/avatar_example.dart'; +import 'package:zeta_example/pages/components/badges_example.dart'; +import 'package:zeta_example/pages/components/banner_example.dart'; +import 'package:zeta_example/pages/components/bottom_sheet_example.dart'; +import 'package:zeta_example/pages/components/button_example.dart'; +import 'package:zeta_example/pages/components/checkbox_example.dart'; +import 'package:zeta_example/pages/components/chip_example.dart'; +import 'package:zeta_example/pages/theme/color_example.dart'; +import 'package:zeta_example/pages/components/password_input_example.dart'; +import 'package:zeta_example/pages/assets/icons_example.dart'; import 'package:zeta_example/widgets.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; @@ -16,12 +22,22 @@ class Component { } final List components = [ - Component(GridExample.name, (context) => const GridExample()), - Component(SpacingExample.name, (context) => const SpacingExample()), - Component(TypographyExample.name, (context) => const TypographyExample()), - Component(ColorExample.name, (context) => const ColorExample()), + Component(AccordionExample.name, (context) => const AccordionExample()), + Component(AvatarExample.name, (context) => const AvatarExample()), + Component(BannerExample.name, (context) => const BannerExample()), + Component(BadgesExample.name, (context) => const BadgesExample()), + Component(BottomSheetExample.name, (context) => const BottomSheetExample()), + Component(ButtonExample.name, (context) => const ButtonExample()), Component(CheckBoxExample.name, (context) => const CheckBoxExample()), + Component(ChipExample.name, (context) => const ChipExample()), + Component(PasswordInputExample.name, (context) => const PasswordInputExample()), +]; +final List theme = [ + Component(ColorExample.name, (context) => const ColorExample()), +]; +final List assets = [ + Component(IconsExample.name, (context) => const IconsExample()), ]; class Home extends StatefulWidget { @@ -35,13 +51,25 @@ final GoRouter router = GoRouter( routes: [ GoRoute( path: '/', + name: 'Home', builder: (_, __) => const Home(), routes: [ - ...components.map( + ...[ + ...components, + ...assets, + ...theme, + ].map( (e) => GoRoute( path: e.name, + name: e.name, builder: (_, __) => e.pageBuilder.call(_), - routes: e.children.map((f) => GoRoute(path: f.name, builder: (_, __) => f.pageBuilder(_))).toList(), + routes: e.children + .map((f) => GoRoute( + path: f.name, + name: f.name, + builder: (_, __) => f.pageBuilder(_), + )) + .toList(), ), ) ], @@ -52,18 +80,37 @@ final GoRouter router = GoRouter( class _HomeState extends State { @override Widget build(BuildContext context) { - final items = components..sort((a, b) => a.name.compareTo(b.name)); + final _components = components..sort((a, b) => a.name.compareTo(b.name)); + final _assets = assets..sort((a, b) => a.name.compareTo(b.name)); + final _theme = theme..sort((a, b) => a.name.compareTo(b.name)); return ExampleScaffold( name: 'Zeta', - child: ListView.builder( - padding: EdgeInsets.all(Dimensions.s), - itemCount: items.length, - itemBuilder: (context, index) { - return ListTile( - title: ZetaText(items[index].name), - onTap: () => context.go('/${items[index].name}'), - ); - }, + child: SingleChildScrollView( + child: Column( + children: [ + ExpansionTile( + title: Text('Widgets'), + backgroundColor: Zeta.of(context).colors.warm.shade30, + children: _components + .map((item) => ListTile(title: Text(item.name), onTap: () => context.go('/${item.name}'))) + .toList(), + ), + ExpansionTile( + title: Text('Theme'), + backgroundColor: Zeta.of(context).colors.warm.shade30, + children: _theme + .map((item) => ListTile(title: Text(item.name), onTap: () => context.go('/${item.name}'))) + .toList(), + ), + ExpansionTile( + title: Text('Assets'), + backgroundColor: Zeta.of(context).colors.warm.shade30, + children: _assets + .map((item) => ListTile(title: Text(item.name), onTap: () => context.go('/${item.name}'))) + .toList(), + ), + ], + ), ), ); } diff --git a/example/lib/main.dart b/example/lib/main.dart index 86099838..596eed2e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -9,14 +9,14 @@ void main() async { final preferences = await SharedPreferences.getInstance(); final themeService = SharedPrefsThemeService(preferences); - final themPreferences = await themeService.loadTheme(); + final themePreferences = await themeService.loadTheme(); runApp( ZetaExample( themeService: themeService, - initialThemeData: themPreferences.$1 ?? ZetaThemeData(), - initialThemeMode: themPreferences.$2 ?? ThemeMode.system, - initialContrast: themPreferences.$3 ?? ZetaContrast.aa, + initialThemeData: themePreferences.$1 ?? ZetaThemeData(), + initialThemeMode: themePreferences.$2 ?? ThemeMode.system, + initialContrast: themePreferences.$3 ?? ZetaContrast.aa, ), ); } @@ -49,14 +49,18 @@ class ZetaExample extends StatelessWidget { routerConfig: router, themeMode: themeMode, theme: ThemeData( + useMaterial3: true, fontFamily: themeData.fontFamily, scaffoldBackgroundColor: light.background, colorScheme: light, + textTheme: zetaTextTheme, ), darkTheme: ThemeData( + useMaterial3: true, fontFamily: themeData.fontFamily, scaffoldBackgroundColor: dark.background, colorScheme: dark, + textTheme: zetaTextTheme, ), ); }, diff --git a/example/lib/pages/assets/icons_example.dart b/example/lib/pages/assets/icons_example.dart new file mode 100644 index 00000000..21d2c56d --- /dev/null +++ b/example/lib/pages/assets/icons_example.dart @@ -0,0 +1,1015 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../widgets.dart'; + +class IconsExample extends StatefulWidget { + static const String name = 'Icons'; + + const IconsExample({super.key}); + + @override + State createState() => _IconsExampleState(); +} + +class _IconsExampleState extends State { + bool showGeneratedColors = false; + + @override + Widget build(BuildContext context) { + return ExampleScaffold( + name: IconsExample.name, + child: SingleChildScrollView( + padding: EdgeInsets.all(ZetaSpacing.s), + child: Column( + children: [ + Text('Round', style: ZetaTextStyles.bodyLarge), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + // round + Icon(ZetaIcons.alarm_round), + Icon(ZetaIcons.ar_round), + Icon(ZetaIcons.bookmark_outline_round), + Icon(ZetaIcons.bookmark_round), + Icon(ZetaIcons.build_round), + Icon(ZetaIcons.cached_round), + Icon(ZetaIcons.calendar_3_day_round), + Icon(ZetaIcons.calendar_alt_1_round), + Icon(ZetaIcons.calendar_alt_2_round), + Icon(ZetaIcons.calendar_available_round), + Icon(ZetaIcons.calendar_cancel_round), + Icon(ZetaIcons.calendar_day_round), + Icon(ZetaIcons.calendar_edit_round), + Icon(ZetaIcons.calendar_monthly_alt_round), + Icon(ZetaIcons.calendar_monthly_round), + Icon(ZetaIcons.calendar_range_round), + Icon(ZetaIcons.calendar_round), + Icon(ZetaIcons.calendar_weekly_alt_round), + Icon(ZetaIcons.calendar_weekly_round), + Icon(ZetaIcons.calendar_yearly_round), + Icon(ZetaIcons.check_circle_outline_alt_round), + Icon(ZetaIcons.check_circle_outline_round), + Icon(ZetaIcons.check_circle_round), + Icon(ZetaIcons.check_mark_round), + Icon(ZetaIcons.clipboard_round), + Icon(ZetaIcons.clock_outline_round), + Icon(ZetaIcons.clock_round), + Icon(ZetaIcons.credit_card_round), + Icon(ZetaIcons.delete_forever_round), + Icon(ZetaIcons.delete_outline_round), + Icon(ZetaIcons.delete_round), + Icon(ZetaIcons.dislike_action_round), + Icon(ZetaIcons.document_round), + Icon(ZetaIcons.download_round), + Icon(ZetaIcons.filter_alt_round), + Icon(ZetaIcons.filter_round), + Icon(ZetaIcons.hand_round), + Icon(ZetaIcons.history_round), + Icon(ZetaIcons.home_round), + Icon(ZetaIcons.info_round), + Icon(ZetaIcons.label_round), + Icon(ZetaIcons.like_action_round), + Icon(ZetaIcons.lock_alt_round), + Icon(ZetaIcons.lock_round), + Icon(ZetaIcons.log_in_round), + Icon(ZetaIcons.log_out_round), + Icon(ZetaIcons.love_outline_round), + Icon(ZetaIcons.love_round), + Icon(ZetaIcons.open_in_full_round), + Icon(ZetaIcons.open_in_new_window_round), + Icon(ZetaIcons.pending_round), + Icon(ZetaIcons.pin_round), + Icon(ZetaIcons.price_round), + Icon(ZetaIcons.renew_round), + Icon(ZetaIcons.search_round), + Icon(ZetaIcons.security_check_round), + Icon(ZetaIcons.settings_round), + Icon(ZetaIcons.sync_action_round), + Icon(ZetaIcons.touch_round), + Icon(ZetaIcons.trending_down_round), + Icon(ZetaIcons.trending_up_round), + Icon(ZetaIcons.unlock_alt_round), + Icon(ZetaIcons.unlock_round), + Icon(ZetaIcons.update_round), + Icon(ZetaIcons.upload_round), + Icon(ZetaIcons.user_circle_round), + Icon(ZetaIcons.user_round), + Icon(ZetaIcons.user_settings_round), + Icon(ZetaIcons.verified_round), + Icon(ZetaIcons.visibility_off_round), + Icon(ZetaIcons.visibility_round), + Icon(ZetaIcons.world_action_round), + Icon(ZetaIcons.zoom_in_round), + Icon(ZetaIcons.zoom_out_round), + Icon(ZetaIcons.add_alert_round), + Icon(ZetaIcons.alert_round), + Icon(ZetaIcons.auto_delete_round), + Icon(ZetaIcons.error_outline_round), + Icon(ZetaIcons.error_round), + Icon(ZetaIcons.important_notification_round), + Icon(ZetaIcons.warning_outline_round), + Icon(ZetaIcons.warning_round), + Icon(ZetaIcons.block_round), + Icon(ZetaIcons.closed_caption_round), + Icon(ZetaIcons.fast_forward_round), + Icon(ZetaIcons.fast_rewind_round), + Icon(ZetaIcons.loop_round), + Icon(ZetaIcons.microphone_off_round), + Icon(ZetaIcons.microphone_outline_round), + Icon(ZetaIcons.microphone_round), + Icon(ZetaIcons.pause_circle_round), + Icon(ZetaIcons.pause_round), + Icon(ZetaIcons.play_circle_round), + Icon(ZetaIcons.play_outline_round), + Icon(ZetaIcons.play_round), + Icon(ZetaIcons.replay_round), + Icon(ZetaIcons.skip_next_round), + Icon(ZetaIcons.skip_previous_round), + Icon(ZetaIcons.stop_circle_round), + Icon(ZetaIcons.stop_round), + Icon(ZetaIcons.video_camera_round), + Icon(ZetaIcons.volume_down_round), + Icon(ZetaIcons.volume_mute_round), + Icon(ZetaIcons.volume_off_round), + Icon(ZetaIcons.volume_up_round), + Icon(ZetaIcons.add_call_round), + Icon(ZetaIcons.chat_bubble_outline_round), + Icon(ZetaIcons.chat_bubble_round), + Icon(ZetaIcons.conversation_round), + Icon(ZetaIcons.dialpad_round), + Icon(ZetaIcons.do_not_disturb_round), + Icon(ZetaIcons.dollar_round), + Icon(ZetaIcons.email_alt_round), + Icon(ZetaIcons.email_outline_round), + Icon(ZetaIcons.email_round), + Icon(ZetaIcons.end_call_round), + Icon(ZetaIcons.message_round), + Icon(ZetaIcons.person_search_round), + Icon(ZetaIcons.phone_bluetooth_speaker_round), + Icon(ZetaIcons.phone_in_talk_round), + Icon(ZetaIcons.phone_round), + Icon(ZetaIcons.priority_round), + Icon(ZetaIcons.qr_code_round), + Icon(ZetaIcons.qr_code_scan_round), + Icon(ZetaIcons.rss_feed_round), + Icon(ZetaIcons.sd_card_round), + Icon(ZetaIcons.sim_card_round), + Icon(ZetaIcons.sms_round), + Icon(ZetaIcons.sync_disabled_round), + Icon(ZetaIcons.sync_round), + Icon(ZetaIcons.add_box_round), + Icon(ZetaIcons.add_circle_outline_round), + Icon(ZetaIcons.add_circle_round), + Icon(ZetaIcons.add_round), + Icon(ZetaIcons.analysis_round), + Icon(ZetaIcons.android_round), + Icon(ZetaIcons.backspace_round), + Icon(ZetaIcons.block_content_round), + Icon(ZetaIcons.clear_round), + Icon(ZetaIcons.content_round), + Icon(ZetaIcons.copy_file_round), + Icon(ZetaIcons.create_round), + Icon(ZetaIcons.filter_list_round), + Icon(ZetaIcons.flag_round), + Icon(ZetaIcons.link_content_round), + Icon(ZetaIcons.mail_round), + Icon(ZetaIcons.push_pin_round), + Icon(ZetaIcons.redo_round), + Icon(ZetaIcons.remove_box_round), + Icon(ZetaIcons.remove_circle_outline_round), + Icon(ZetaIcons.remove_circle_round), + Icon(ZetaIcons.remove_round), + Icon(ZetaIcons.reply_round), + Icon(ZetaIcons.save_alt_round), + Icon(ZetaIcons.save_round), + Icon(ZetaIcons.send_round), + Icon(ZetaIcons.sort_round), + Icon(ZetaIcons.undo_round), + Icon(ZetaIcons.antenna_round), + Icon(ZetaIcons.battery_alert_round), + Icon(ZetaIcons.battery_charging_round), + Icon(ZetaIcons.battery_round), + Icon(ZetaIcons.bluetooth_disabled_round), + Icon(ZetaIcons.bluetooth_round), + Icon(ZetaIcons.bluetooth_searching_round), + Icon(ZetaIcons.brightness_round), + Icon(ZetaIcons.cellular_signal_round), + Icon(ZetaIcons.chain_round), + Icon(ZetaIcons.contrast_round), + Icon(ZetaIcons.dark_mode_round), + Icon(ZetaIcons.devices_round), + Icon(ZetaIcons.ethernet_round), + Icon(ZetaIcons.flight_mode_round), + Icon(ZetaIcons.hdmi_round), + Icon(ZetaIcons.light_mode_round), + Icon(ZetaIcons.location_point_round), + Icon(ZetaIcons.location_round), + Icon(ZetaIcons.mobile_friendly_round), + Icon(ZetaIcons.network_signal_round), + Icon(ZetaIcons.nfc_round), + Icon(ZetaIcons.night_round), + Icon(ZetaIcons.restart_alt_round), + Icon(ZetaIcons.screen_rotation_round), + Icon(ZetaIcons.uhf_rfid_round), + Icon(ZetaIcons.usb_alt_device_round), + Icon(ZetaIcons.usb_alt_round), + Icon(ZetaIcons.usb_round), + Icon(ZetaIcons.volte_round), + Icon(ZetaIcons.appgallery_round), + Icon(ZetaIcons.application_analytics_round), + Icon(ZetaIcons.asset_tracker_lite_round), + Icon(ZetaIcons.blood_bag_plus_round), + Icon(ZetaIcons.bluetooth_management_round), + Icon(ZetaIcons.browser_print_round), + Icon(ZetaIcons.cloud_connect_round), + Icon(ZetaIcons.data_wedge_round), + Icon(ZetaIcons.design_tools_round), + Icon(ZetaIcons.device_tracker_round), + Icon(ZetaIcons.direct_connect_round), + Icon(ZetaIcons.emdk_round), + Icon(ZetaIcons.enterprise_browser_round), + Icon(ZetaIcons.enterprise_connectors_round), + Icon(ZetaIcons.enterprise_keyboard_round), + Icon(ZetaIcons.gms_restricted_mode_round), + Icon(ZetaIcons.intelligent_document_capture_round), + Icon(ZetaIcons.label_plus_round), + Icon(ZetaIcons.life_guard_round), + Icon(ZetaIcons.link_os_round), + Icon(ZetaIcons.mdm_connectors_round), + Icon(ZetaIcons.mdm_toolkit_round), + Icon(ZetaIcons.mobility_dna_round), + Icon(ZetaIcons.mobility_security_round), + Icon(ZetaIcons.multi_code_data_formatting_round), + Icon(ZetaIcons.mx_round), + Icon(ZetaIcons.ocr_round), + Icon(ZetaIcons.oem_config_round), + Icon(ZetaIcons.pairing_solutions_round), + Icon(ZetaIcons.pdf_direct_round), + Icon(ZetaIcons.power_precision_round), + Icon(ZetaIcons.preferred_symbol_round), + Icon(ZetaIcons.print_secure_round), + Icon(ZetaIcons.print_station_round), + Icon(ZetaIcons.printer_profile_manager_round), + Icon(ZetaIcons.przm_round), + Icon(ZetaIcons.remote_control_round), + Icon(ZetaIcons.remote_diagnostics_round), + Icon(ZetaIcons.remote_management_round), + Icon(ZetaIcons.rx_round), + Icon(ZetaIcons.scan_and_pair_round), + Icon(ZetaIcons.scan_speed_analytics_round), + Icon(ZetaIcons.scan_to_connect_round), + Icon(ZetaIcons.scanner_control_application_round), + Icon(ZetaIcons.setting_tool_round), + Icon(ZetaIcons.simulscan_round), + Icon(ZetaIcons.smart_te_lite_round), + Icon(ZetaIcons.smart_te_round), + Icon(ZetaIcons.smart_tek_round), + Icon(ZetaIcons.smartdex_round), + Icon(ZetaIcons.stagenow_round), + Icon(ZetaIcons.swipe_assist_round), + Icon(ZetaIcons.tekspeech_pro_round), + Icon(ZetaIcons.virtual_devices_round), + Icon(ZetaIcons.virtual_tether_round), + Icon(ZetaIcons.visibility_services_round), + Icon(ZetaIcons.voice_wedge_round), + Icon(ZetaIcons.workforce_connect_round), + Icon(ZetaIcons.workstation_connect_round), + Icon(ZetaIcons.zebra_designer_round), + Icon(ZetaIcons.zebra_onecare_round), + Icon(ZetaIcons.zebra_setup_utility_round), + Icon(ZetaIcons.zero_touch_round), + Icon(ZetaIcons.align_center_round), + Icon(ZetaIcons.align_horizontal_center_round), + Icon(ZetaIcons.align_horizontal_left_round), + Icon(ZetaIcons.align_horizontal_right_round), + Icon(ZetaIcons.align_left_round), + Icon(ZetaIcons.align_right_round), + Icon(ZetaIcons.align_vertical_bottom_round), + Icon(ZetaIcons.align_vertical_center_round), + Icon(ZetaIcons.align_vertical_top_round), + Icon(ZetaIcons.attach_round), + Icon(ZetaIcons.bold_round), + Icon(ZetaIcons.bullet_list_round), + Icon(ZetaIcons.checklist_round), + Icon(ZetaIcons.color_fill_round), + Icon(ZetaIcons.distribute_horizontal_round), + Icon(ZetaIcons.distribute_vertical_round), + Icon(ZetaIcons.edit_border_round), + Icon(ZetaIcons.edit_round), + Icon(ZetaIcons.font_size_decrease_round), + Icon(ZetaIcons.font_size_increase_round), + Icon(ZetaIcons.format_shapes_round), + Icon(ZetaIcons.indent_decrease_round), + Icon(ZetaIcons.indent_increase_round), + Icon(ZetaIcons.italic_round), + Icon(ZetaIcons.justify_round), + Icon(ZetaIcons.line_spacing_round), + Icon(ZetaIcons.link_round), + Icon(ZetaIcons.no_color_round), + Icon(ZetaIcons.numbered_list_round), + Icon(ZetaIcons.strikethrough_round), + Icon(ZetaIcons.text_color_round), + Icon(ZetaIcons.type_round), + Icon(ZetaIcons.underline_round), + Icon(ZetaIcons.barcode_bluetooth_round), + Icon(ZetaIcons.barcode_done_round), + Icon(ZetaIcons.barcode_image_scan_round), + Icon(ZetaIcons.barcode_qr_code_round), + Icon(ZetaIcons.barcode_round), + Icon(ZetaIcons.barcode_scanner_down_round), + Icon(ZetaIcons.barcode_settings_round), + Icon(ZetaIcons.calculator_round), + Icon(ZetaIcons.card_orientation_round), + Icon(ZetaIcons.card_with_chip_round), + Icon(ZetaIcons.certificate_round), + Icon(ZetaIcons.choices_round), + Icon(ZetaIcons.contactless_pay_round), + Icon(ZetaIcons.credit_card_swipe_round), + Icon(ZetaIcons.eas_round), + Icon(ZetaIcons.enterprise_diagnostics_round), + Icon(ZetaIcons.environmental_round), + Icon(ZetaIcons.factory_round), + Icon(ZetaIcons.freezer_round), + Icon(ZetaIcons.issue_audit_round), + Icon(ZetaIcons.performance_round), + Icon(ZetaIcons.planogram_round), + Icon(ZetaIcons.productivity_apps_round), + Icon(ZetaIcons.receipt_round), + Icon(ZetaIcons.retail_round), + Icon(ZetaIcons.road_map_round), + Icon(ZetaIcons.rules_round), + Icon(ZetaIcons.scan_rate_round), + Icon(ZetaIcons.shape_recognition_round), + Icon(ZetaIcons.solutions_round), + Icon(ZetaIcons.steps_round), + Icon(ZetaIcons.strategy_round), + Icon(ZetaIcons.tap_to_pay_round), + Icon(ZetaIcons.zero_scale_round), + Icon(ZetaIcons.attachment_round), + Icon(ZetaIcons.cloud_done_round), + Icon(ZetaIcons.cloud_download_round), + Icon(ZetaIcons.cloud_off_round), + Icon(ZetaIcons.cloud_outline_round), + Icon(ZetaIcons.cloud_round), + Icon(ZetaIcons.cloud_upload_round), + Icon(ZetaIcons.create_new_folder_round), + Icon(ZetaIcons.doc_attach_round), + Icon(ZetaIcons.downloading_round), + Icon(ZetaIcons.file_download_round), + Icon(ZetaIcons.file_upload_round), + Icon(ZetaIcons.finished_download_round), + Icon(ZetaIcons.folder_outline_round), + Icon(ZetaIcons.folder_round), + Icon(ZetaIcons.grid_view_round), + Icon(ZetaIcons.jpg_attach_round), + Icon(ZetaIcons.pdf_attach_round), + Icon(ZetaIcons.png_attach_round), + Icon(ZetaIcons.ppt_attach_round), + Icon(ZetaIcons.shared_folder_round), + Icon(ZetaIcons.upload_file_round), + Icon(ZetaIcons.cast_connected_round), + Icon(ZetaIcons.cast_round), + Icon(ZetaIcons.desktop_round), + Icon(ZetaIcons.device_settings_round), + Icon(ZetaIcons.devices_ecosystem_round), + Icon(ZetaIcons.headphones_round), + Icon(ZetaIcons.keyboard_round), + Icon(ZetaIcons.laptop_round), + Icon(ZetaIcons.memory_round), + Icon(ZetaIcons.monitor_round), + Icon(ZetaIcons.phone_android_round), + Icon(ZetaIcons.phone_iphone_round), + Icon(ZetaIcons.phonelink_round), + Icon(ZetaIcons.printer_round), + Icon(ZetaIcons.scanner_round), + Icon(ZetaIcons.security_round), + Icon(ZetaIcons.smart_phone_round), + Icon(ZetaIcons.speaker_round), + Icon(ZetaIcons.tablet_mac_round), + Icon(ZetaIcons.tablet_round), + Icon(ZetaIcons.tv_round), + Icon(ZetaIcons.video_play_round), + Icon(ZetaIcons.watch_round), + Icon(ZetaIcons.adjustments_round), + Icon(ZetaIcons.auto_round), + Icon(ZetaIcons.awb_round), + Icon(ZetaIcons.camera_round), + Icon(ZetaIcons.camera_shutter_round), + Icon(ZetaIcons.contrast_image_round), + Icon(ZetaIcons.crop_round), + Icon(ZetaIcons.eye_dropper_round), + Icon(ZetaIcons.flip_camera_round), + Icon(ZetaIcons.grid_off_round), + Icon(ZetaIcons.grid_on_round), + Icon(ZetaIcons.hdr_round), + Icon(ZetaIcons.image_library_round), + Icon(ZetaIcons.image_round), + Icon(ZetaIcons.iso_round), + Icon(ZetaIcons.portrait_round), + Icon(ZetaIcons.pro_round), + Icon(ZetaIcons.rotate_left_round), + Icon(ZetaIcons.rotate_right_round), + Icon(ZetaIcons.slide_show_round), + Icon(ZetaIcons.straighten_round), + Icon(ZetaIcons.timer_round), + Icon(ZetaIcons.visibility_image_round), + Icon(ZetaIcons.visibility_off_image_round), + Icon(ZetaIcons.wb_image_round), + Icon(ZetaIcons.wb_round), + Icon(ZetaIcons.bus_round), + Icon(ZetaIcons.cafe_round), + Icon(ZetaIcons.car_round), + Icon(ZetaIcons.flight_round), + Icon(ZetaIcons.layers_round), + Icon(ZetaIcons.light_rail_train_round), + Icon(ZetaIcons.local_shipping_round), + Icon(ZetaIcons.map_round), + Icon(ZetaIcons.my_location_round), + Icon(ZetaIcons.navigation_round), + Icon(ZetaIcons.near_me_round), + Icon(ZetaIcons.offer_round), + Icon(ZetaIcons.place_round), + Icon(ZetaIcons.ship_round), + Icon(ZetaIcons.taxi_round), + Icon(ZetaIcons.train_round), + Icon(ZetaIcons.world_round), + Icon(ZetaIcons.zoom_in_map_round), + Icon(ZetaIcons.zoom_out_map_round), + Icon(ZetaIcons.apps_round), + Icon(ZetaIcons.arrow_back_round), + Icon(ZetaIcons.arrow_down_round), + Icon(ZetaIcons.arrow_forward_round), + Icon(ZetaIcons.arrow_up_round), + Icon(ZetaIcons.cancel_outline_round), + Icon(ZetaIcons.cancel_round), + Icon(ZetaIcons.caret_down_round), + Icon(ZetaIcons.caret_left_round), + Icon(ZetaIcons.caret_right_round), + Icon(ZetaIcons.caret_sort_round), + Icon(ZetaIcons.caret_up_round), + Icon(ZetaIcons.check_round), + Icon(ZetaIcons.chevron_left_round), + Icon(ZetaIcons.chevron_right_round), + Icon(ZetaIcons.close_round), + Icon(ZetaIcons.columns_round), + Icon(ZetaIcons.dashboard_round), + Icon(ZetaIcons.drop_down_circle_round), + Icon(ZetaIcons.expand_less_round), + Icon(ZetaIcons.expand_more_round), + Icon(ZetaIcons.first_page_round), + Icon(ZetaIcons.full_screen_exit_round), + Icon(ZetaIcons.full_screen_round), + Icon(ZetaIcons.hamburger_menu_navigation_rail_round), + Icon(ZetaIcons.hamburger_menu_round), + Icon(ZetaIcons.hamburger_menu_thick_round), + Icon(ZetaIcons.help_outline_round), + Icon(ZetaIcons.help_round), + Icon(ZetaIcons.last_page_round), + Icon(ZetaIcons.list_alt_round), + Icon(ZetaIcons.list_round), + Icon(ZetaIcons.more_horizontal_round), + Icon(ZetaIcons.more_vertical_round), + Icon(ZetaIcons.refresh_round), + Icon(ZetaIcons.unfold_less_round), + Icon(ZetaIcons.unfold_more_round), + Icon(ZetaIcons.add_group_round), + Icon(ZetaIcons.add_person_round), + Icon(ZetaIcons.bad_mood_round), + Icon(ZetaIcons.blog_round), + Icon(ZetaIcons.dislike_round), + Icon(ZetaIcons.facebook_round), + Icon(ZetaIcons.group_round), + Icon(ZetaIcons.groups_round), + Icon(ZetaIcons.happy_round), + Icon(ZetaIcons.instagram_round), + Icon(ZetaIcons.ios_share_round), + Icon(ZetaIcons.like_round), + Icon(ZetaIcons.linkedin_round), + Icon(ZetaIcons.medium_round), + Icon(ZetaIcons.notifications_active_round), + Icon(ZetaIcons.notifications_outline_round), + Icon(ZetaIcons.notifications_round), + Icon(ZetaIcons.person_outline_round), + Icon(ZetaIcons.person_round), + Icon(ZetaIcons.remove_group_round), + Icon(ZetaIcons.remove_person_round), + Icon(ZetaIcons.sad_round), + Icon(ZetaIcons.satisfied_round), + Icon(ZetaIcons.share_round), + Icon(ZetaIcons.switch_user_round), + Icon(ZetaIcons.twitter_round), + Icon(ZetaIcons.very_satisfied_round), + Icon(ZetaIcons.world_map_round), + Icon(ZetaIcons.youtube_round), + Icon(ZetaIcons.check_box_empty_round), + Icon(ZetaIcons.check_box_round), + Icon(ZetaIcons.indeterminate_check_box_round), + Icon(ZetaIcons.radio_button_checked_round), + Icon(ZetaIcons.radio_button_unchecked_round), + Icon(ZetaIcons.star_half_round), + Icon(ZetaIcons.star_outline_round), + Icon(ZetaIcons.star_round), + Icon(ZetaIcons.toggle_off_round), + Icon(ZetaIcons.toggle_on_round), + ], + ), + const SizedBox(height: 20), + Text('Sharp', style: ZetaTextStyles.bodyLarge), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + Icon(ZetaIcons.alarm_sharp), + Icon(ZetaIcons.ar_sharp), + Icon(ZetaIcons.bookmark_outline_sharp), + Icon(ZetaIcons.bookmark_sharp), + Icon(ZetaIcons.build_sharp), + Icon(ZetaIcons.cached_sharp), + Icon(ZetaIcons.calendar_3_day_sharp), + Icon(ZetaIcons.calendar_alt_1_sharp), + Icon(ZetaIcons.calendar_alt_2_sharp), + Icon(ZetaIcons.calendar_available_sharp), + Icon(ZetaIcons.calendar_cancel_sharp), + Icon(ZetaIcons.calendar_day_sharp), + Icon(ZetaIcons.calendar_edit_sharp), + Icon(ZetaIcons.calendar_monthly_alt_sharp), + Icon(ZetaIcons.calendar_monthly_sharp), + Icon(ZetaIcons.calendar_range_sharp), + Icon(ZetaIcons.calendar_sharp), + Icon(ZetaIcons.calendar_weekly_alt_sharp), + Icon(ZetaIcons.calendar_weekly_sharp), + Icon(ZetaIcons.calendar_yearly_sharp), + Icon(ZetaIcons.check_circle_outline_alt_sharp), + Icon(ZetaIcons.check_circle_outline_sharp), + Icon(ZetaIcons.check_circle_sharp), + Icon(ZetaIcons.check_mark_sharp), + Icon(ZetaIcons.clipboard_sharp), + Icon(ZetaIcons.clock_outline_sharp), + Icon(ZetaIcons.clock_sharp), + Icon(ZetaIcons.credit_card_sharp), + Icon(ZetaIcons.delete_forever_sharp), + Icon(ZetaIcons.delete_outline_sharp), + Icon(ZetaIcons.delete_sharp), + Icon(ZetaIcons.dislike_action_sharp), + Icon(ZetaIcons.document_sharp), + Icon(ZetaIcons.download_sharp), + Icon(ZetaIcons.filter_alt_sharp), + Icon(ZetaIcons.filter_sharp), + Icon(ZetaIcons.hand_sharp), + Icon(ZetaIcons.history_sharp), + Icon(ZetaIcons.home_sharp), + Icon(ZetaIcons.info_sharp), + Icon(ZetaIcons.label_sharp), + Icon(ZetaIcons.like_action_sharp), + Icon(ZetaIcons.lock_alt_sharp), + Icon(ZetaIcons.lock_sharp), + Icon(ZetaIcons.log_in_sharp), + Icon(ZetaIcons.log_out_sharp), + Icon(ZetaIcons.love_outline_sharp), + Icon(ZetaIcons.love_sharp), + Icon(ZetaIcons.open_in_full_sharp), + Icon(ZetaIcons.open_in_new_window_sharp), + Icon(ZetaIcons.pending_sharp), + Icon(ZetaIcons.pin_sharp), + Icon(ZetaIcons.price_sharp), + Icon(ZetaIcons.renew_sharp), + Icon(ZetaIcons.search_sharp), + Icon(ZetaIcons.security_check_sharp), + Icon(ZetaIcons.settings_sharp), + Icon(ZetaIcons.sync_action_sharp), + Icon(ZetaIcons.touch_sharp), + Icon(ZetaIcons.trending_down_sharp), + Icon(ZetaIcons.trending_up_sharp), + Icon(ZetaIcons.unlock_alt_sharp), + Icon(ZetaIcons.unlock_sharp), + Icon(ZetaIcons.update_sharp), + Icon(ZetaIcons.upload_sharp), + Icon(ZetaIcons.user_circle_sharp), + Icon(ZetaIcons.user_sharp), + Icon(ZetaIcons.user_settings_sharp), + Icon(ZetaIcons.verified_sharp), + Icon(ZetaIcons.visibility_off_sharp), + Icon(ZetaIcons.visibility_sharp), + Icon(ZetaIcons.world_action_sharp), + Icon(ZetaIcons.zoom_in_sharp), + Icon(ZetaIcons.zoom_out_sharp), + Icon(ZetaIcons.add_alert_sharp), + Icon(ZetaIcons.alert_sharp), + Icon(ZetaIcons.auto_delete_sharp), + Icon(ZetaIcons.error_outline_sharp), + Icon(ZetaIcons.error_sharp), + Icon(ZetaIcons.important_notification_sharp), + Icon(ZetaIcons.warning_outline_sharp), + Icon(ZetaIcons.warning_sharp), + Icon(ZetaIcons.block_sharp), + Icon(ZetaIcons.closed_caption_sharp), + Icon(ZetaIcons.fast_forward_sharp), + Icon(ZetaIcons.fast_rewind_sharp), + Icon(ZetaIcons.loop_sharp), + Icon(ZetaIcons.microphone_off_sharp), + Icon(ZetaIcons.microphone_outline_sharp), + Icon(ZetaIcons.microphone_sharp), + Icon(ZetaIcons.pause_circle_sharp), + Icon(ZetaIcons.pause_sharp), + Icon(ZetaIcons.play_circle_sharp), + Icon(ZetaIcons.play_outline_sharp), + Icon(ZetaIcons.play_sharp), + Icon(ZetaIcons.replay_sharp), + Icon(ZetaIcons.skip_next_sharp), + Icon(ZetaIcons.skip_previous_sharp), + Icon(ZetaIcons.stop_circle_sharp), + Icon(ZetaIcons.stop_sharp), + Icon(ZetaIcons.video_camera_sharp), + Icon(ZetaIcons.volume_down_sharp), + Icon(ZetaIcons.volume_mute_sharp), + Icon(ZetaIcons.volume_off_sharp), + Icon(ZetaIcons.volume_up_sharp), + Icon(ZetaIcons.add_call_sharp), + Icon(ZetaIcons.chat_bubble_outline_sharp), + Icon(ZetaIcons.chat_bubble_sharp), + Icon(ZetaIcons.conversation_sharp), + Icon(ZetaIcons.dialpad_sharp), + Icon(ZetaIcons.do_not_disturb_sharp), + Icon(ZetaIcons.dollar_sharp), + Icon(ZetaIcons.email_alt_sharp), + Icon(ZetaIcons.email_outline_sharp), + Icon(ZetaIcons.email_sharp), + Icon(ZetaIcons.end_call_sharp), + Icon(ZetaIcons.message_sharp), + Icon(ZetaIcons.person_search_sharp), + Icon(ZetaIcons.phone_bluetooth_speaker_sharp), + Icon(ZetaIcons.phone_in_talk_sharp), + Icon(ZetaIcons.phone_sharp), + Icon(ZetaIcons.priority_sharp), + Icon(ZetaIcons.qr_code_sharp), + Icon(ZetaIcons.qr_code_scan_sharp), + Icon(ZetaIcons.rss_feed_sharp), + Icon(ZetaIcons.sd_card_sharp), + Icon(ZetaIcons.sim_card_sharp), + Icon(ZetaIcons.sms_sharp), + Icon(ZetaIcons.sync_disabled_sharp), + Icon(ZetaIcons.sync_sharp), + Icon(ZetaIcons.add_box_sharp), + Icon(ZetaIcons.add_circle_outline_sharp), + Icon(ZetaIcons.add_circle_sharp), + Icon(ZetaIcons.add_sharp), + Icon(ZetaIcons.analysis_sharp), + Icon(ZetaIcons.android_sharp), + Icon(ZetaIcons.backspace_sharp), + Icon(ZetaIcons.block_content_sharp), + Icon(ZetaIcons.clear_sharp), + Icon(ZetaIcons.content_sharp), + Icon(ZetaIcons.copy_file_sharp), + Icon(ZetaIcons.create_sharp), + Icon(ZetaIcons.filter_list_sharp), + Icon(ZetaIcons.flag_sharp), + Icon(ZetaIcons.link_content_sharp), + Icon(ZetaIcons.mail_sharp), + Icon(ZetaIcons.push_pin_sharp), + Icon(ZetaIcons.redo_sharp), + Icon(ZetaIcons.remove_box_sharp), + Icon(ZetaIcons.remove_circle_outline_sharp), + Icon(ZetaIcons.remove_circle_sharp), + Icon(ZetaIcons.remove_sharp), + Icon(ZetaIcons.reply_sharp), + Icon(ZetaIcons.save_alt_sharp), + Icon(ZetaIcons.save_sharp), + Icon(ZetaIcons.send_sharp), + Icon(ZetaIcons.sort_sharp), + Icon(ZetaIcons.undo_sharp), + Icon(ZetaIcons.antenna_sharp), + Icon(ZetaIcons.battery_alert_sharp), + Icon(ZetaIcons.battery_charging_sharp), + Icon(ZetaIcons.battery_sharp), + Icon(ZetaIcons.bluetooth_disabled_sharp), + Icon(ZetaIcons.bluetooth_sharp), + Icon(ZetaIcons.bluetooth_searching_sharp), + Icon(ZetaIcons.brightness_sharp), + Icon(ZetaIcons.cellular_signal_sharp), + Icon(ZetaIcons.chain_sharp), + Icon(ZetaIcons.contrast_sharp), + Icon(ZetaIcons.dark_mode_sharp), + Icon(ZetaIcons.devices_sharp), + Icon(ZetaIcons.ethernet_sharp), + Icon(ZetaIcons.flight_mode_sharp), + Icon(ZetaIcons.hdmi_sharp), + Icon(ZetaIcons.light_mode_sharp), + Icon(ZetaIcons.location_point_sharp), + Icon(ZetaIcons.location_sharp), + Icon(ZetaIcons.mobile_friendly_sharp), + Icon(ZetaIcons.network_signal_sharp), + Icon(ZetaIcons.nfc_sharp), + Icon(ZetaIcons.night_sharp), + Icon(ZetaIcons.restart_alt_sharp), + Icon(ZetaIcons.screen_rotation_sharp), + Icon(ZetaIcons.uhf_rfid_sharp), + Icon(ZetaIcons.usb_alt_device_sharp), + Icon(ZetaIcons.usb_alt_sharp), + Icon(ZetaIcons.usb_sharp), + Icon(ZetaIcons.volte_sharp), + Icon(ZetaIcons.appgallery_sharp), + Icon(ZetaIcons.application_analytics_sharp), + Icon(ZetaIcons.asset_tracker_lite_sharp), + Icon(ZetaIcons.blood_bag_plus_sharp), + Icon(ZetaIcons.bluetooth_management_sharp), + Icon(ZetaIcons.browser_print_sharp), + Icon(ZetaIcons.cloud_connect_sharp), + Icon(ZetaIcons.data_wedge_sharp), + Icon(ZetaIcons.design_tools_sharp), + Icon(ZetaIcons.device_tracker_sharp), + Icon(ZetaIcons.direct_connect_sharp), + Icon(ZetaIcons.emdk_sharp), + Icon(ZetaIcons.enterprise_browser_sharp), + Icon(ZetaIcons.enterprise_connectors_sharp), + Icon(ZetaIcons.enterprise_keyboard_sharp), + Icon(ZetaIcons.gms_restricted_mode_sharp), + Icon(ZetaIcons.intelligent_document_capture_sharp), + Icon(ZetaIcons.label_plus_sharp), + Icon(ZetaIcons.life_guard_sharp), + Icon(ZetaIcons.link_os_sharp), + Icon(ZetaIcons.mdm_connectors_sharp), + Icon(ZetaIcons.mdm_toolkit_sharp), + Icon(ZetaIcons.mobility_dna_sharp), + Icon(ZetaIcons.mobility_security_sharp), + Icon(ZetaIcons.multi_code_data_formatting_sharp), + Icon(ZetaIcons.mx_sharp), + Icon(ZetaIcons.ocr_sharp), + Icon(ZetaIcons.oem_config_sharp), + Icon(ZetaIcons.pairing_solutions_sharp), + Icon(ZetaIcons.pdf_direct_sharp), + Icon(ZetaIcons.power_precision_sharp), + Icon(ZetaIcons.preferred_symbol_sharp), + Icon(ZetaIcons.print_secure_sharp), + Icon(ZetaIcons.print_station_sharp), + Icon(ZetaIcons.printer_profile_manager_sharp), + Icon(ZetaIcons.przm_sharp), + Icon(ZetaIcons.remote_control_sharp), + Icon(ZetaIcons.remote_diagnostics_sharp), + Icon(ZetaIcons.remote_management_sharp), + Icon(ZetaIcons.rx_sharp), + Icon(ZetaIcons.scan_and_pair_sharp), + Icon(ZetaIcons.scan_speed_analytics_sharp), + Icon(ZetaIcons.scan_to_connect_sharp), + Icon(ZetaIcons.scanner_control_application_sharp), + Icon(ZetaIcons.setting_tool_sharp), + Icon(ZetaIcons.simulscan_sharp), + Icon(ZetaIcons.smart_te_lite_sharp), + Icon(ZetaIcons.smart_te_sharp), + Icon(ZetaIcons.smart_tek_sharp), + Icon(ZetaIcons.smartdex_sharp), + Icon(ZetaIcons.stagenow_sharp), + Icon(ZetaIcons.swipe_assist_sharp), + Icon(ZetaIcons.tekspeech_pro_sharp), + Icon(ZetaIcons.virtual_devices_sharp), + Icon(ZetaIcons.virtual_tether_sharp), + Icon(ZetaIcons.visibility_services_sharp), + Icon(ZetaIcons.voice_wedge_sharp), + Icon(ZetaIcons.workforce_connect_sharp), + Icon(ZetaIcons.workstation_connect_sharp), + Icon(ZetaIcons.zebra_designer_sharp), + Icon(ZetaIcons.zebra_onecare_sharp), + Icon(ZetaIcons.zebra_setup_utility_sharp), + Icon(ZetaIcons.zero_touch_sharp), + Icon(ZetaIcons.align_center_sharp), + Icon(ZetaIcons.align_horizontal_center_sharp), + Icon(ZetaIcons.align_horizontal_left_sharp), + Icon(ZetaIcons.align_horizontal_right_sharp), + Icon(ZetaIcons.align_left_sharp), + Icon(ZetaIcons.align_right_sharp), + Icon(ZetaIcons.align_vertical_bottom_sharp), + Icon(ZetaIcons.align_vertical_center_sharp), + Icon(ZetaIcons.align_vertical_top_sharp), + Icon(ZetaIcons.attach_sharp), + Icon(ZetaIcons.bold_sharp), + Icon(ZetaIcons.bullet_list_sharp), + Icon(ZetaIcons.checklist_sharp), + Icon(ZetaIcons.color_fill_sharp), + Icon(ZetaIcons.distribute_horizontal_sharp), + Icon(ZetaIcons.distribute_vertical_sharp), + Icon(ZetaIcons.edit_border_sharp), + Icon(ZetaIcons.edit_sharp), + Icon(ZetaIcons.font_size_decrease_sharp), + Icon(ZetaIcons.font_size_increase_sharp), + Icon(ZetaIcons.format_shapes_sharp), + Icon(ZetaIcons.indent_decrease_sharp), + Icon(ZetaIcons.indent_increase_sharp), + Icon(ZetaIcons.italic_sharp), + Icon(ZetaIcons.justify_sharp), + Icon(ZetaIcons.line_spacing_sharp), + Icon(ZetaIcons.link_sharp), + Icon(ZetaIcons.no_color_sharp), + Icon(ZetaIcons.numbered_list_sharp), + Icon(ZetaIcons.strikethrough_sharp), + Icon(ZetaIcons.text_color_sharp), + Icon(ZetaIcons.type_sharp), + Icon(ZetaIcons.underline_sharp), + Icon(ZetaIcons.barcode_bluetooth_sharp), + Icon(ZetaIcons.barcode_done_sharp), + Icon(ZetaIcons.barcode_image_scan_sharp), + Icon(ZetaIcons.barcode_qr_code_sharp), + Icon(ZetaIcons.barcode_sharp), + Icon(ZetaIcons.barcode_scanner_down_sharp), + Icon(ZetaIcons.barcode_settings_sharp), + Icon(ZetaIcons.calculator_sharp), + Icon(ZetaIcons.card_orientation_sharp), + Icon(ZetaIcons.card_with_chip_sharp), + Icon(ZetaIcons.certificate_sharp), + Icon(ZetaIcons.choices_sharp), + Icon(ZetaIcons.contactless_pay_sharp), + Icon(ZetaIcons.credit_card_swipe_sharp), + Icon(ZetaIcons.eas_sharp), + Icon(ZetaIcons.enterprise_diagnostics_sharp), + Icon(ZetaIcons.environmental_sharp), + Icon(ZetaIcons.factory_sharp), + Icon(ZetaIcons.freezer_sharp), + Icon(ZetaIcons.issue_audit_sharp), + Icon(ZetaIcons.performance_sharp), + Icon(ZetaIcons.planogram_sharp), + Icon(ZetaIcons.productivity_apps_sharp), + Icon(ZetaIcons.receipt_sharp), + Icon(ZetaIcons.retail_sharp), + Icon(ZetaIcons.road_map_sharp), + Icon(ZetaIcons.rules_sharp), + Icon(ZetaIcons.scan_rate_sharp), + Icon(ZetaIcons.shape_recognition_sharp), + Icon(ZetaIcons.solutions_sharp), + Icon(ZetaIcons.steps_sharp), + Icon(ZetaIcons.strategy_sharp), + Icon(ZetaIcons.tap_to_pay_sharp), + Icon(ZetaIcons.zero_scale_sharp), + Icon(ZetaIcons.attachment_sharp), + Icon(ZetaIcons.cloud_done_sharp), + Icon(ZetaIcons.cloud_download_sharp), + Icon(ZetaIcons.cloud_off_sharp), + Icon(ZetaIcons.cloud_outline_sharp), + Icon(ZetaIcons.cloud_sharp), + Icon(ZetaIcons.cloud_upload_sharp), + Icon(ZetaIcons.create_new_folder_sharp), + Icon(ZetaIcons.doc_attach_sharp), + Icon(ZetaIcons.downloading_sharp), + Icon(ZetaIcons.file_download_sharp), + Icon(ZetaIcons.file_upload_sharp), + Icon(ZetaIcons.finished_download_sharp), + Icon(ZetaIcons.folder_outline_sharp), + Icon(ZetaIcons.folder_sharp), + Icon(ZetaIcons.grid_view_sharp), + Icon(ZetaIcons.jpg_attach_sharp), + Icon(ZetaIcons.pdf_attach_sharp), + Icon(ZetaIcons.png_attach_sharp), + Icon(ZetaIcons.ppt_attach_sharp), + Icon(ZetaIcons.shared_folder_sharp), + Icon(ZetaIcons.upload_file_sharp), + Icon(ZetaIcons.cast_connected_sharp), + Icon(ZetaIcons.cast_sharp), + Icon(ZetaIcons.desktop_sharp), + Icon(ZetaIcons.device_settings_sharp), + Icon(ZetaIcons.devices_ecosystem_sharp), + Icon(ZetaIcons.headphones_sharp), + Icon(ZetaIcons.keyboard_sharp), + Icon(ZetaIcons.laptop_sharp), + Icon(ZetaIcons.memory_sharp), + Icon(ZetaIcons.monitor_sharp), + Icon(ZetaIcons.phone_android_sharp), + Icon(ZetaIcons.phone_iphone_sharp), + Icon(ZetaIcons.phonelink_sharp), + Icon(ZetaIcons.printer_sharp), + Icon(ZetaIcons.scanner_sharp), + Icon(ZetaIcons.security_sharp), + Icon(ZetaIcons.smart_phone_sharp), + Icon(ZetaIcons.speaker_sharp), + Icon(ZetaIcons.tablet_mac_sharp), + Icon(ZetaIcons.tablet_sharp), + Icon(ZetaIcons.tv_sharp), + Icon(ZetaIcons.video_play_sharp), + Icon(ZetaIcons.watch_sharp), + Icon(ZetaIcons.adjustments_sharp), + Icon(ZetaIcons.auto_sharp), + Icon(ZetaIcons.awb_sharp), + Icon(ZetaIcons.camera_sharp), + Icon(ZetaIcons.camera_shutter_sharp), + Icon(ZetaIcons.contrast_image_sharp), + Icon(ZetaIcons.crop_sharp), + Icon(ZetaIcons.eye_dropper_sharp), + Icon(ZetaIcons.flip_camera_sharp), + Icon(ZetaIcons.grid_off_sharp), + Icon(ZetaIcons.grid_on_sharp), + Icon(ZetaIcons.hdr_sharp), + Icon(ZetaIcons.image_library_sharp), + Icon(ZetaIcons.image_sharp), + Icon(ZetaIcons.iso_sharp), + Icon(ZetaIcons.portrait_sharp), + Icon(ZetaIcons.pro_sharp), + Icon(ZetaIcons.rotate_left_sharp), + Icon(ZetaIcons.rotate_right_sharp), + Icon(ZetaIcons.slide_show_sharp), + Icon(ZetaIcons.straighten_sharp), + Icon(ZetaIcons.timer_sharp), + Icon(ZetaIcons.visibility_image_sharp), + Icon(ZetaIcons.visibility_off_image_sharp), + Icon(ZetaIcons.wb_image_sharp), + Icon(ZetaIcons.wb_sharp), + Icon(ZetaIcons.bus_sharp), + Icon(ZetaIcons.cafe_sharp), + Icon(ZetaIcons.car_sharp), + Icon(ZetaIcons.flight_sharp), + Icon(ZetaIcons.layers_sharp), + Icon(ZetaIcons.light_rail_train_sharp), + Icon(ZetaIcons.local_shipping_sharp), + Icon(ZetaIcons.map_sharp), + Icon(ZetaIcons.my_location_sharp), + Icon(ZetaIcons.navigation_sharp), + Icon(ZetaIcons.near_me_sharp), + Icon(ZetaIcons.offer_sharp), + Icon(ZetaIcons.place_sharp), + Icon(ZetaIcons.ship_sharp), + Icon(ZetaIcons.taxi_sharp), + Icon(ZetaIcons.train_sharp), + Icon(ZetaIcons.world_sharp), + Icon(ZetaIcons.zoom_in_map_sharp), + Icon(ZetaIcons.zoom_out_map_sharp), + Icon(ZetaIcons.apps_sharp), + Icon(ZetaIcons.arrow_back_sharp), + Icon(ZetaIcons.arrow_down_sharp), + Icon(ZetaIcons.arrow_forward_sharp), + Icon(ZetaIcons.arrow_up_sharp), + Icon(ZetaIcons.cancel_outline_sharp), + Icon(ZetaIcons.cancel_sharp), + Icon(ZetaIcons.caret_down_sharp), + Icon(ZetaIcons.caret_left_sharp), + Icon(ZetaIcons.caret_right_sharp), + Icon(ZetaIcons.caret_sort_sharp), + Icon(ZetaIcons.caret_up_sharp), + Icon(ZetaIcons.check_sharp), + Icon(ZetaIcons.chevron_left_sharp), + Icon(ZetaIcons.chevron_right_sharp), + Icon(ZetaIcons.close_sharp), + Icon(ZetaIcons.columns_sharp), + Icon(ZetaIcons.dashboard_sharp), + Icon(ZetaIcons.drop_down_circle_sharp), + Icon(ZetaIcons.expand_less_sharp), + Icon(ZetaIcons.expand_more_sharp), + Icon(ZetaIcons.first_page_sharp), + Icon(ZetaIcons.full_screen_exit_sharp), + Icon(ZetaIcons.full_screen_sharp), + Icon(ZetaIcons.hamburger_menu_navigation_rail_sharp), + Icon(ZetaIcons.hamburger_menu_sharp), + Icon(ZetaIcons.hamburger_menu_thick_sharp), + Icon(ZetaIcons.help_outline_sharp), + Icon(ZetaIcons.help_sharp), + Icon(ZetaIcons.last_page_sharp), + Icon(ZetaIcons.list_alt_sharp), + Icon(ZetaIcons.list_sharp), + Icon(ZetaIcons.more_horizontal_sharp), + Icon(ZetaIcons.more_vertical_sharp), + Icon(ZetaIcons.refresh_sharp), + Icon(ZetaIcons.unfold_less_sharp), + Icon(ZetaIcons.unfold_more_sharp), + Icon(ZetaIcons.add_group_sharp), + Icon(ZetaIcons.add_person_sharp), + Icon(ZetaIcons.bad_mood_sharp), + Icon(ZetaIcons.blog_sharp), + Icon(ZetaIcons.dislike_sharp), + Icon(ZetaIcons.facebook_sharp), + Icon(ZetaIcons.group_sharp), + Icon(ZetaIcons.groups_sharp), + Icon(ZetaIcons.happy_sharp), + Icon(ZetaIcons.instagram_sharp), + Icon(ZetaIcons.ios_share_sharp), + Icon(ZetaIcons.like_sharp), + Icon(ZetaIcons.linkedin_sharp), + Icon(ZetaIcons.medium_sharp), + Icon(ZetaIcons.notifications_active_sharp), + Icon(ZetaIcons.notifications_outline_sharp), + Icon(ZetaIcons.notifications_sharp), + Icon(ZetaIcons.person_outline_sharp), + Icon(ZetaIcons.person_sharp), + Icon(ZetaIcons.remove_group_sharp), + Icon(ZetaIcons.remove_person_sharp), + Icon(ZetaIcons.sad_sharp), + Icon(ZetaIcons.satisfied_sharp), + Icon(ZetaIcons.share_sharp), + Icon(ZetaIcons.switch_user_sharp), + Icon(ZetaIcons.twitter_sharp), + Icon(ZetaIcons.very_satisfied_sharp), + Icon(ZetaIcons.world_map_sharp), + Icon(ZetaIcons.youtube_sharp), + Icon(ZetaIcons.check_box_empty_sharp), + Icon(ZetaIcons.check_box_sharp), + Icon(ZetaIcons.indeterminate_check_box_sharp), + Icon(ZetaIcons.radio_button_checked_sharp), + Icon(ZetaIcons.radio_button_unchecked_sharp), + Icon(ZetaIcons.star_half_sharp), + Icon(ZetaIcons.star_outline_sharp), + Icon(ZetaIcons.star_sharp), + Icon(ZetaIcons.toggle_off_sharp), + Icon(ZetaIcons.toggle_on_sharp), + ], + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/pages/components/accordion_example.dart b/example/lib/pages/components/accordion_example.dart new file mode 100644 index 00000000..b23587c8 --- /dev/null +++ b/example/lib/pages/components/accordion_example.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class AccordionExample extends StatelessWidget { + static const String name = 'Accordion'; + + const AccordionExample({super.key}); + + @override + Widget build(BuildContext context) { + return ExampleScaffold( + name: AccordionExample.name, + child: SingleChildScrollView( + padding: EdgeInsets.all(ZetaSpacing.s), + child: Column( + children: [ + Text('Rounded Divider'), + const SizedBox(height: 20), + ZetaAccordion( + title: 'title', + child: Column( + children: [ + ListTile(title: Text('List Item')), + ListTile(title: Text('List Item')), + ListTile(title: Text('List Item')), + ], + ), + ), + ZetaAccordion(title: 'title'), + const SizedBox(height: 40), + Text('Rounded Contained'), + const SizedBox(height: 20), + ZetaAccordion( + contained: true, + title: 'title', + child: Column( + children: [ + ListTile(title: Text('List Item')), + ListTile(title: Text('List Item')), + ListTile(title: Text('List Item')), + ], + ), + ), + ZetaAccordion( + contained: true, + title: 'title', + ), + const SizedBox(height: 40), + Text('Sharp Divider'), + const SizedBox(height: 20), + ZetaAccordion( + contained: false, + title: 'title', + rounded: false, + child: Column( + children: [ + ListTile(title: Text('List Item')), + ListTile(title: Text('List Item')), + ListTile(title: Text('List Item')), + ], + ), + ), + ZetaAccordion( + contained: false, + title: 'title', + rounded: false, + ), + Text('Sharp Contained'), + const SizedBox(height: 20), + ZetaAccordion( + contained: true, + title: 'title', + rounded: false, + child: Column( + children: [ + ListTile(title: Text('List Item')), + ListTile(title: Text('List Item')), + ListTile(title: Text('List Item')), + ], + ), + ), + ZetaAccordion( + contained: true, + title: 'title', + rounded: false, + ), + ].divide(const SizedBox.square(dimension: 10)).toList(), + ), + ), + ); + } +} diff --git a/example/lib/pages/components/avatar_example.dart b/example/lib/pages/components/avatar_example.dart new file mode 100644 index 00000000..39f42a8f --- /dev/null +++ b/example/lib/pages/components/avatar_example.dart @@ -0,0 +1,595 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class AvatarExample extends StatelessWidget { + static const String name = 'Avatar'; + + const AvatarExample({super.key}); + + @override + Widget build(BuildContext context) { + final Widget image = CachedNetworkImage( + imageUrl: "https://i.ytimg.com/vi/KItsWUzFUOs/maxresdefault.jpg", + placeholder: (context, url) => Icon(ZetaIcons.user_round), + errorWidget: (context, url, error) => Icon(Icons.error), + fit: BoxFit.cover, + ); + + return ExampleScaffold( + name: AvatarExample.name, + child: SingleChildScrollView( + padding: EdgeInsets.all(ZetaSpacing.s), + child: Column( + children: [ + Column( + children: [ + Text( + 'ZetaAvatar.image', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), + ), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 15), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image(size: size), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 15), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + borderColor: Zeta.of(context).colors.green, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 15), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + image: image, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 15), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + borderColor: Zeta.of(context).colors.green, + image: image, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + Column( + children: [ + Text( + 'ZetaAvatar.initials', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), + ), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 15), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 15), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + borderColor: Zeta.of(context).colors.green, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + Column( + children: [ + Text( + 'ZetaAvatar.image with badge', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), + ), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + upperBadge: ZetaIndicator.notification(value: 3), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + borderColor: Zeta.of(context).colors.green, + upperBadge: ZetaIndicator.notification(value: 3), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + upperBadge: ZetaIndicator.notification(value: 3), + image: image, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + borderColor: Zeta.of(context).colors.green, + upperBadge: ZetaIndicator.notification(value: 3), + image: image, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + Column( + children: [ + Text( + 'ZetaAvatar.initials with badge', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), + ), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + upperBadge: ZetaIndicator.notification(value: 3), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + borderColor: Zeta.of(context).colors.green, + upperBadge: ZetaIndicator.notification(value: 3), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + Column( + children: [ + Text( + 'ZetaAvatar.image with special status', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), + ), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + lowerBadge: ZetaIndicator.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + borderColor: Zeta.of(context).colors.green, + lowerBadge: ZetaIndicator.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + lowerBadge: ZetaIndicator.icon(), + image: image, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + borderColor: Zeta.of(context).colors.green, + lowerBadge: ZetaIndicator.icon(), + image: image, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + Column( + children: [ + Text( + 'ZetaAvatar.initials with special status', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), + ), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + lowerBadge: ZetaIndicator.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + borderColor: Zeta.of(context).colors.green, + lowerBadge: ZetaIndicator.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + Column( + children: [ + Text( + 'ZetaAvatar with notification badge and status badge', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), + ), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + image: image, + upperBadge: ZetaIndicator.notification(value: 3), + lowerBadge: ZetaIndicator.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + image: image, + borderColor: Zeta.of(context).colors.green, + upperBadge: ZetaIndicator.notification(value: 3), + lowerBadge: ZetaIndicator.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + upperBadge: ZetaIndicator.notification(value: 3), + lowerBadge: ZetaIndicator.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + borderColor: Zeta.of(context).colors.green, + upperBadge: ZetaIndicator.notification(value: 3), + lowerBadge: ZetaIndicator.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + ].divide(const SizedBox(height: ZetaSpacing.x6)).toList(), + ), + ), + ); + } +} + +extension on ZetaAvatarSize { + double get pixelSize { + switch (this) { + case ZetaAvatarSize.xl: + return ZetaSpacing.x16; + case ZetaAvatarSize.l: + return ZetaSpacing.x12; + case ZetaAvatarSize.m: + return ZetaSpacing.x10; + case ZetaAvatarSize.s: + return ZetaSpacing.x8; + case ZetaAvatarSize.xs: + return ZetaSpacing.x6; + } + } +} diff --git a/example/lib/pages/components/badges_example.dart b/example/lib/pages/components/badges_example.dart new file mode 100644 index 00000000..76d28534 --- /dev/null +++ b/example/lib/pages/components/badges_example.dart @@ -0,0 +1,265 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class BadgesExample extends StatelessWidget { + static const String name = 'Badges'; + + const BadgesExample({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ExampleScaffold( + name: BadgesExample.name, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _DividingText('Status Label'), + _StatusLabel(), + _DividingText('Priority Pill'), + _PriorityPill(), + _DividingText('Badge'), + _Badge(), + _DividingText('Indicators'), + _Indicators(), + _DividingText('Tags'), + _Tags(), + _DividingText('WorkCloud indicators'), + _WorkcloudIndicators(), + ], + ), + ), + ); + } +} + +class _DividingText extends StatelessWidget { + final String text; + const _DividingText(this.text); + + @override + Widget build(BuildContext context) { + return Text( + text, + style: ZetaTextStyles.displayMedium, + ).paddingVertical(ZetaSpacing.l); + } +} + +class _StatusLabel extends StatelessWidget { + const _StatusLabel(); + + Widget statusLabelExampleRow(ZetaWidgetStatus type) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ZetaStatusLabel(label: 'Label', status: type, rounded: false), + ZetaStatusLabel(label: 'Label', status: type, rounded: true), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + statusLabelExampleRow(ZetaWidgetStatus.neutral), + statusLabelExampleRow(ZetaWidgetStatus.info), + statusLabelExampleRow(ZetaWidgetStatus.positive), + statusLabelExampleRow(ZetaWidgetStatus.warning), + statusLabelExampleRow(ZetaWidgetStatus.negative), + ].divide(const SizedBox.square(dimension: ZetaSpacing.m)).toList(), + ); + } +} + +class _PriorityPill extends StatelessWidget { + const _PriorityPill(); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + ZetaPriorityPill(index: 1000, priority: 'Rounded', rounded: true), + ZetaPriorityPill(index: 2, priority: 'Sharp', rounded: false), + ].divide(const SizedBox.square(dimension: ZetaSpacing.m)).toList(), + ); + } +} + +class _Badge extends StatelessWidget { + const _Badge(); + + Widget badgeExampleRow(ZetaWidgetStatus type) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ZetaBadge(label: 'Label', status: type, rounded: false), + ZetaBadge(label: 'Label', status: type), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + badgeExampleRow(ZetaWidgetStatus.info), + badgeExampleRow(ZetaWidgetStatus.positive), + badgeExampleRow(ZetaWidgetStatus.warning), + badgeExampleRow(ZetaWidgetStatus.negative), + badgeExampleRow(ZetaWidgetStatus.neutral), + ].divide(const SizedBox(height: ZetaSpacing.m)).toList(), + ); + } +} + +class _Indicators extends StatelessWidget { + const _Indicators(); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + children: [ + Text( + 'ZetaIndicator.icon', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + ZetaIndicator.icon(), + ZetaIndicator.icon(size: ZetaWidgetSize.medium), + ZetaIndicator.icon(size: ZetaWidgetSize.small), + ].divide(const SizedBox.square(dimension: ZetaSpacing.m)).toList(), + ), + Row( + children: [ + ZetaIndicator.icon(inverse: true), + ZetaIndicator.icon(size: ZetaWidgetSize.medium, inverse: true), + ZetaIndicator.icon(size: ZetaWidgetSize.small, inverse: true), + ].divide(const SizedBox.square(dimension: ZetaSpacing.m)).toList(), + ), + ].divide(const SizedBox.square(dimension: ZetaSpacing.xs)).toList(), + ), + const SizedBox(width: 50), + ], + ), + ], + ), + const SizedBox.square(dimension: ZetaSpacing.xl), + Column( + children: [ + Text('ZetaIndicator.notification', style: TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 15), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + ZetaIndicator.notification(value: 3), + ZetaIndicator.notification(size: ZetaWidgetSize.medium, value: 3), + ZetaIndicator.notification(size: ZetaWidgetSize.small), + ].divide(const SizedBox.square(dimension: ZetaSpacing.m)).toList(), + ), + Row( + children: [ + ZetaIndicator.notification(value: 3, inverse: true), + ZetaIndicator.notification(size: ZetaWidgetSize.medium, value: 3, inverse: true), + ZetaIndicator.notification(size: ZetaWidgetSize.small, inverse: true), + ].divide(const SizedBox.square(dimension: ZetaSpacing.m)).toList(), + ), + ].divide(const SizedBox.square(dimension: ZetaSpacing.s)).toList(), + ), + ], + ), + ], + ), + ], + ); + } +} + +class _Tags extends StatelessWidget { + const _Tags(); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + ZetaTag.left(label: 'Sharp', rounded: false), + ZetaTag.right(label: 'Sharp', rounded: false), + ZetaTag.left(label: 'Rounded'), + ZetaTag.right(label: 'Rounded'), + ].divide(SizedBox.square(dimension: ZetaSpacing.m)).toList(), + ), + ); + } +} + +class _WorkcloudIndicators extends StatelessWidget { + const _WorkcloudIndicators(); + + Widget workcloudIndicatorExampleRow(ZetaWorkcloudIndicatorType type) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ZetaWorkcloudIndicator( + priorityType: type, + prioritySize: ZetaWidgetSize.large, + icon: ZetaIcons.star_half_round, + label: 'Label', + ), + ZetaWorkcloudIndicator( + prioritySize: ZetaWidgetSize.medium, + index: '14', + priorityType: type, + label: 'Label!', + ), + ZetaWorkcloudIndicator( + priorityType: type, + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SizedBox(height: 30), + Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text('Medium'), Text('Small'), Text('X-Small')], + ), + ...List.generate(10, (index) { + return workcloudIndicatorExampleRow(ZetaWorkcloudIndicatorType.values[index]); + }), + ].divide(const SizedBox.square(dimension: ZetaSpacing.s)).toList(), + ).paddingAll(ZetaSpacing.m) + ], + ); + } +} diff --git a/example/lib/pages/components/banner_example.dart b/example/lib/pages/components/banner_example.dart new file mode 100644 index 00000000..b6513599 --- /dev/null +++ b/example/lib/pages/components/banner_example.dart @@ -0,0 +1,198 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class BannerExample extends StatelessWidget { + static const String name = 'Banner'; + + const BannerExample({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ExampleScaffold( + name: BannerExample.name, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(20), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('System Banner', style: ZetaTextStyles.displayMedium), + _getTitle('Style variants'), + ZetaSystemBanner( + type: ZetaSystemBannerStatus.primary, + title: 'Centered', + context: context, + titleStart: true, + leadingIcon: ZetaIcons.info_round, + ), + ZetaSystemBanner( + type: ZetaSystemBannerStatus.primary, + context: context, + title: 'Title Left', + ), + ZetaSystemBanner( + type: ZetaSystemBannerStatus.primary, + context: context, + title: 'Title left with arrow', + titleStart: true, + trailing: Icon(ZetaIcons.chevron_right_round), + ), + ZetaSystemBanner( + type: ZetaSystemBannerStatus.primary, + title: 'Title left + Icon', + titleStart: true, + context: context, + leadingIcon: ZetaIcons.info_round, + ), + ZetaSystemBanner( + type: ZetaSystemBannerStatus.primary, + context: context, + title: 'Title left + Icon with Arrow', + titleStart: true, + leadingIcon: ZetaIcons.info_round, + trailing: IconButton( + icon: Icon(ZetaIcons.chevron_right_round), + onPressed: () { + ScaffoldMessenger.of(context).showMaterialBanner(ZetaSystemBanner( + title: 'Title', + context: context, + type: ZetaSystemBannerStatus.primary, + trailing: IconButton( + icon: Icon(ZetaIcons.close_round), + onPressed: () => ScaffoldMessenger.of(context).clearMaterialBanners(), + ), + )); + }, + ), + ), + _getTitle('Color variants'), + ZetaSystemBanner( + type: ZetaSystemBannerStatus.positive, + context: context, + title: 'Centered', + titleStart: true, + leadingIcon: ZetaIcons.info_round, + trailing: IconButton( + icon: Icon(ZetaIcons.chevron_right_round), + onPressed: () { + ScaffoldMessenger.of(context).showMaterialBanner(ZetaSystemBanner( + title: 'Title', + context: context, + type: ZetaSystemBannerStatus.positive, + trailing: IconButton( + icon: Icon(ZetaIcons.close_round), + onPressed: () => ScaffoldMessenger.of(context).clearMaterialBanners(), + ), + )); + }, + ), + ), + ZetaSystemBanner( + type: ZetaSystemBannerStatus.warning, + title: 'Centered', + context: context, + titleStart: true, + leadingIcon: ZetaIcons.info_round, + trailing: IconButton( + icon: Icon(ZetaIcons.chevron_right_round), + onPressed: () { + ScaffoldMessenger.of(context).showMaterialBanner(ZetaSystemBanner( + title: 'Title', + context: context, + type: ZetaSystemBannerStatus.warning, + trailing: IconButton( + icon: Icon(ZetaIcons.close_round), + onPressed: () => ScaffoldMessenger.of(context).clearMaterialBanners(), + ), + )); + }, + ), + ), + ZetaSystemBanner( + type: ZetaSystemBannerStatus.negative, + title: 'Centered', + context: context, + titleStart: true, + leadingIcon: ZetaIcons.info_round, + trailing: IconButton( + icon: Icon(ZetaIcons.chevron_right_round), + onPressed: () { + ScaffoldMessenger.of(context).showMaterialBanner(ZetaSystemBanner( + title: 'Title', + context: context, + type: ZetaSystemBannerStatus.negative, + trailing: IconButton( + icon: Icon(ZetaIcons.close_round), + onPressed: () => ScaffoldMessenger.of(context).clearMaterialBanners(), + ), + )); + }, + ), + ), + const Divider(), + Text('In-Page Banner', style: ZetaTextStyles.displayMedium), + ZetaInPageBanner( + content: Text(_content), + status: ZetaWidgetStatus.info, + ), + ZetaInPageBanner( + content: Text(_content), + onClose: () {}, + status: ZetaWidgetStatus.positive, + title: 'Banner Title', + rounded: false, + ), + ZetaInPageBanner( + content: Text(_content), + onClose: () {}, + status: ZetaWidgetStatus.warning, + title: 'Banner Title', + actions: [ZetaButton(label: 'Button', onPressed: () {})], + ), + ZetaInPageBanner( + content: Text(_content), + onClose: () {}, + status: ZetaWidgetStatus.negative, + title: 'Banner Title Banner Title Banner Title Banner Title', + rounded: false, + ), + ZetaInPageBanner( + content: Text(_content), + onClose: () {}, + status: ZetaWidgetStatus.neutral, + title: 'Banner Title', + ) + ].divide(const SizedBox(height: 10)).toList(), + ), + ), + ), + ); + } + + final _content = 'Lorem ipsum dolor sit amet, conse ctetur cididunt ut' + 'labore et do lore magna aliqua.'; + + Widget _getTitle(String title) => Container(height: 50, child: Center(child: Text(title))); + + Column buildExampleBannerColumn( + ZetaWidgetStatus status, { + bool rounded = true, + IconData? customIcon, + }) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisSize: MainAxisSize.min, + children: [ + ZetaInPageBanner( + content: Text(_content), + onClose: () {}, + rounded: rounded, + status: status, + title: 'Banner Title', + customIcon: customIcon, + ), + ], + ); + } +} diff --git a/example/lib/pages/components/bottom_sheet_example.dart b/example/lib/pages/components/bottom_sheet_example.dart new file mode 100644 index 00000000..1cc6fa0f --- /dev/null +++ b/example/lib/pages/components/bottom_sheet_example.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class BottomSheetExample extends StatefulWidget { + static const String name = 'BottomSheet'; + + const BottomSheetExample({super.key}); + + @override + State createState() => _BottomSheetExampleState(); +} + +class _BottomSheetExampleState extends State { + bool centerTitle = true; + + @override + Widget build(BuildContext context) { + return ExampleScaffold( + name: BottomSheetExample.name, + child: SingleChildScrollView( + padding: EdgeInsets.all(ZetaSpacing.s), + child: Column( + children: [ + ZetaMenuItem.horizontal( + label: Text('Grid'), + onTap: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return ZetaBottomSheet( + title: 'Bottom Sheet', + centerTitle: centerTitle, + body: Wrap( + spacing: 12, + runSpacing: 12, + children: List.generate( + 6, + (index) => ZetaMenuItem.vertical( + label: Text('Menu Item'), + icon: Icon(ZetaIcons.star_round), + onTap: () {}, + ), + ), + ), + ); + }, + ); + }, + ), + ZetaMenuItem.horizontal( + label: Text('Horizontal'), + onTap: () { + showZetaBottomSheet( + context: context, + title: 'Bottom Sheet', + centerTitle: centerTitle, + body: Wrap( + spacing: 12, + runSpacing: 12, + children: List.generate( + 6, + (index) => ZetaMenuItem.horizontal( + label: Text('Menu Item'), + onTap: () {}, + leading: Icon(ZetaIcons.star_round), + ), + ), + ), + ); + }, + ), + ZetaCheckbox( + label: 'Center title', + value: centerTitle, + onChanged: (value) => setState(() => centerTitle = !centerTitle), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/pages/components/button_example.dart b/example/lib/pages/components/button_example.dart new file mode 100644 index 00000000..c99d80e6 --- /dev/null +++ b/example/lib/pages/components/button_example.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class ButtonExample extends StatefulWidget { + static const String name = 'Button'; + + const ButtonExample({super.key}); + + @override + State createState() => _ButtonExampleState(); +} + +class _ButtonExampleState extends State { + Widget? fab; + late ScrollController _scrollController; + + @override + void initState() { + super.initState(); + _scrollController = ScrollController(); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void setFab(int index) => setState(() => fab = fabs[index]); + + List fabs = []; + @override + Widget build(BuildContext context) { + if (fabs.isEmpty) { + fabs = [ + ZetaFAB( + scrollController: _scrollController, + label: 'Small Circle Primary', + size: ZetaFabSize.small, + shape: ZetaWidgetBorder.full, + type: ZetaFabType.primary, + onPressed: () => setFab(0), + ), + ZetaFAB( + scrollController: _scrollController, + label: 'Small Rounded Primary', + size: ZetaFabSize.small, + shape: ZetaWidgetBorder.rounded, + type: ZetaFabType.secondary, + onPressed: () => setFab(1), + ), + ZetaFAB( + scrollController: _scrollController, + label: 'Small Sharp Primary', + size: ZetaFabSize.small, + shape: ZetaWidgetBorder.sharp, + type: ZetaFabType.inverse, + onPressed: () => setFab(2), + ), + ZetaFAB( + scrollController: _scrollController, + label: 'Large Circle Primary', + size: ZetaFabSize.large, + shape: ZetaWidgetBorder.full, + type: ZetaFabType.secondary, + onPressed: () => setFab(3), + ), + ZetaFAB( + scrollController: _scrollController, + label: 'Large Rounded Secondary', + size: ZetaFabSize.large, + shape: ZetaWidgetBorder.rounded, + type: ZetaFabType.inverse, + onPressed: () => setFab(4), + ), + ZetaFAB( + scrollController: _scrollController, + label: 'Large Sharp Secondary', + size: ZetaFabSize.large, + shape: ZetaWidgetBorder.sharp, + type: ZetaFabType.primary, + onPressed: () => setFab(5), + ), + ]; + } + + return ExampleScaffold( + name: 'Button', + floatingActionButton: fab ?? fabs.first, + child: SingleChildScrollView( + controller: _scrollController, + child: Row( + children: [ + Expanded( + flex: 8, + child: Column( + children: [ + Text('Rounded Buttons', style: ZetaTextStyles.displayMedium), + Column(children: buttons(ZetaWidgetBorder.rounded)), + Text('Sharp Buttons', style: ZetaTextStyles.displayMedium), + Column(children: buttons(ZetaWidgetBorder.sharp)), + Text('Full Buttons', style: ZetaTextStyles.displayMedium), + Column(children: buttons(ZetaWidgetBorder.full)), + Text('Floating Action Buttons', style: ZetaTextStyles.displayMedium), + Text('Tap buttons to change current FAB: ', style: ZetaTextStyles.bodyMedium), + Wrap(children: fabs.divide(SizedBox.square(dimension: 10)).toList()), + ].divide(const SizedBox.square(dimension: ZetaSpacing.m)).toList(), + ), + ), + Expanded(child: const SizedBox()), + ], + ), + ), + ); + } + + List buttons(ZetaWidgetBorder borderType) { + return List.generate( + ZetaWidgetSize.values.length + 1, + (index) => SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: List.generate( + ZetaButtonType.values.length, + (index2) => ZetaButton( + label: 'Button', + onPressed: index == 0 ? null : () {}, + type: ZetaButtonType.values[index2], + size: ZetaWidgetSize.values[index == 0 ? 0 : index - 1], + borderType: borderType, + ), + ).divide(const SizedBox.square(dimension: ZetaSpacing.m)).toList(), + ), + ), + ).reversed.divide(const SizedBox.square(dimension: ZetaSpacing.m)).toList(); + } +} diff --git a/example/lib/pages/checkbox_example.dart b/example/lib/pages/components/checkbox_example.dart similarity index 54% rename from example/lib/pages/checkbox_example.dart rename to example/lib/pages/components/checkbox_example.dart index 37ab826d..be48113d 100644 --- a/example/lib/pages/checkbox_example.dart +++ b/example/lib/pages/components/checkbox_example.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; -import '../widgets.dart'; - class CheckBoxExample extends StatefulWidget { static const String name = 'Checkbox'; @@ -15,6 +14,7 @@ class CheckBoxExample extends StatefulWidget { class _CheckBoxExampleState extends State { bool? isChecked = true; bool isEnabled = true; + bool useIndeterminate = false; @override Widget build(BuildContext context) { @@ -29,22 +29,27 @@ class _CheckBoxExampleState extends State { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ZetaCheckbox( - value: isChecked, - isEnabled: isEnabled, - onChanged: (value) => setState(() => isChecked = value)), - ElevatedButton( - child: const Text('Disable'), + value: isChecked, + onChanged: isEnabled ? (value) => setState(() => isChecked = value) : null, + useIndeterminate: useIndeterminate, + ), + ZetaButton( + label: isEnabled ? 'Disable' : 'Enable', onPressed: () => setState(() => isEnabled = !isEnabled), - ) + ), + ZetaButton( + label: !useIndeterminate ? 'Use Indeterminate' : 'Don\'t use indeterminate', + onPressed: () => setState(() => useIndeterminate = !useIndeterminate), + ), ], ), - Row(children: [const Text('Sharp Checkbox Enabled')]), + Row(children: [Text('Sharp Checkbox Enabled')]), getCheckBoxRow(isEnabled: true), - Row(children: [const Text('Sharp Checkbox Disabled')]), + Row(children: [Text('Sharp Checkbox Disabled')]), getCheckBoxRow(isEnabled: false), - Row(children: [const Text('Rounded Checkbox Enabled')]), + Row(children: [Text('Rounded Checkbox Enabled')]), getCheckBoxRow(isEnabled: true, isSharp: false), - Row(children: [const Text('Rounded Checkbox Disabled')]), + Row(children: [Text('Rounded Checkbox Disabled')]), getCheckBoxRow(isEnabled: false, isSharp: false), ], ), @@ -59,21 +64,21 @@ Row getCheckBoxRow({required bool isEnabled, bool isSharp = true}) { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ZetaCheckbox( - value: true, - isEnabled: isEnabled, - label: 'Selected', - borderType: isSharp ? BorderType.sharp : BorderType.rounded, - onChanged: (value) => {}), + value: true, + label: 'Label', + rounded: !isSharp, + onChanged: isEnabled ? (value) => {} : null, + ), ZetaCheckbox( - value: false, - isEnabled: isEnabled, - label: 'Indeterminate', - borderType: isSharp ? BorderType.sharp : BorderType.rounded, - onChanged: (value) => {}), + value: false, + label: 'Label', + rounded: !isSharp, + onChanged: isEnabled ? (value) => {} : null, + ), ZetaCheckbox( - value: null, - borderType: isSharp ? BorderType.sharp : BorderType.rounded, - isEnabled: isEnabled, - onChanged: (value) => {}), + value: null, + rounded: !isSharp, + onChanged: isEnabled ? (value) => {} : null, + ) ]); } diff --git a/example/lib/pages/components/chip_example.dart b/example/lib/pages/components/chip_example.dart new file mode 100644 index 00000000..cbbc09f2 --- /dev/null +++ b/example/lib/pages/components/chip_example.dart @@ -0,0 +1,228 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class ChipExample extends StatelessWidget { + static const String name = 'Chip'; + + const ChipExample({super.key}); + + @override + Widget build(BuildContext context) { + final List inputChipExample = [ + Text( + 'Input Chip', + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + children: [ + Text( + 'Rounded', + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 30), + Text('Label Only', textAlign: TextAlign.center), + const SizedBox(height: 10), + ZetaInputChip(label: 'Label'), + const SizedBox(height: 30), + Text('Label + Icon', textAlign: TextAlign.center), + const SizedBox(height: 10), + ZetaInputChip( + label: 'Label', + ), + const SizedBox(height: 30), + Text('Label + Avatar', textAlign: TextAlign.center), + const SizedBox(height: 10), + ZetaInputChip( + label: 'Label', + leading: const Icon(ZetaIcons.user_round), + ), + const SizedBox(height: 30), + Text('Label, Avatar + Icon', textAlign: TextAlign.center), + const SizedBox(height: 10), + ZetaInputChip( + label: 'Label', + leading: const Icon(ZetaIcons.user_round), + trailing: Icon(ZetaIcons.close_round), + ), + ], + ), + Column( + children: [ + Text( + 'Sharp', + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 30), + Text('Label Only', textAlign: TextAlign.center), + const SizedBox(height: 10), + ZetaInputChip( + label: 'Label', + rounded: false, + ), + const SizedBox(height: 30), + Text('Label + Icon', textAlign: TextAlign.center), + const SizedBox(height: 10), + ZetaInputChip( + label: 'Label', + rounded: false, + ), + const SizedBox(height: 30), + Text('Label + Avatar', textAlign: TextAlign.center), + const SizedBox(height: 10), + ZetaInputChip( + label: 'Label', + rounded: false, + leading: const Icon(ZetaIcons.user_round), + ), + const SizedBox(height: 30), + Text('Label, Avatar + Icon', textAlign: TextAlign.center), + const SizedBox(height: 10), + ZetaInputChip( + label: 'Label', + rounded: false, + leading: const Icon(ZetaIcons.user_round), + trailing: Icon(ZetaIcons.close_sharp), + ), + ], + ), + ], + ), + ]; + + final List filterChipExample = [ + Text( + 'Filter Chip', + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + children: [ + Text( + 'Rounded', + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + ZetaFilterChip(label: 'Label'), + const SizedBox(height: 10), + ZetaFilterChip( + label: 'Label', + selected: true, + ), + ], + ), + Column( + children: [ + Text( + 'Sharp', + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + ZetaFilterChip( + label: 'Label', + rounded: false, + ), + const SizedBox(height: 10), + ZetaFilterChip( + label: 'Label', + rounded: false, + selected: true, + ), + ], + ), + ], + ), + ]; + + final List assistChipExample = [ + Text( + 'Assist Chip', + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + children: [ + Text( + 'Rounded', + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 30), + Text('Label Only', textAlign: TextAlign.center), + const SizedBox(height: 10), + ZetaAssistChip(label: 'Label'), + const SizedBox(height: 30), + Text('Label + Icon', textAlign: TextAlign.center), + const SizedBox(height: 10), + ZetaAssistChip( + label: 'Label', + leading: Icon(ZetaIcons.star_round), + ), + ], + ), + Column( + children: [ + Text( + 'Sharp', + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 30), + Text('Label Only', textAlign: TextAlign.center), + const SizedBox(height: 10), + ZetaAssistChip( + label: 'Label', + rounded: false, + ), + const SizedBox(height: 30), + Text('Label + Icon', textAlign: TextAlign.center), + const SizedBox(height: 10), + ZetaAssistChip( + label: 'Label', + rounded: false, + leading: Icon(ZetaIcons.star_round), + ), + ], + ), + ], + ), + ]; + + return ExampleScaffold( + name: ChipExample.name, + child: SingleChildScrollView( + padding: EdgeInsets.all(ZetaSpacing.s), + child: Column( + children: [ + ...inputChipExample, + const SizedBox(height: 30), + ...assistChipExample, + const SizedBox(height: 30), + ...filterChipExample, + const SizedBox(height: 30), + ], + ), + ), + ); + } +} diff --git a/example/lib/pages/components/password_input_example.dart b/example/lib/pages/components/password_input_example.dart new file mode 100644 index 00000000..d7a46e20 --- /dev/null +++ b/example/lib/pages/components/password_input_example.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../widgets.dart'; + +class PasswordInputExample extends StatefulWidget { + static const String name = 'PasswordInput'; + + const PasswordInputExample({Key? key}) : super(key: key); + + @override + State createState() => _PasswordInputExampleState(); +} + +class _PasswordInputExampleState extends State { + final _passwordController = TextEditingController(); + final _formKey = GlobalKey(); + + @override + void dispose() { + _passwordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Form( + key: _formKey, + child: ExampleScaffold( + name: PasswordInputExample.name, + child: Padding( + padding: EdgeInsets.all(10), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding(padding: EdgeInsets.only(top: 20)), + ZetaPasswordInput( + size: ZetaWidgetSize.medium, + rounded: false, + footerText: 'Error state is triggered if the password contains digits', + footerIcon: ZetaIcons.info_round, + hintText: 'Password', + controller: _passwordController, + onChanged: (value) => _formKey.currentState?.validate(), + validator: (value) { + if (value != null) { + final regExp = RegExp(r'\d'); + if (regExp.hasMatch(value)) return 'Password is incorrect'; + } + return null; + }, + ), + SizedBox(height: ZetaSpacing.x10), + ...passwordInputExampleRow(ZetaWidgetSize.large), + Divider(height: ZetaSpacing.x20), + ...passwordInputExampleRow(ZetaWidgetSize.medium), + Divider(height: ZetaSpacing.x20), + ...passwordInputExampleRow(ZetaWidgetSize.small), + ], + ), + ), + ), + )); + } +} + +List passwordInputExampleRow(ZetaWidgetSize size, {bool rounded = true}) { + return [ + ZetaPasswordInput(size: size, hintText: 'Password', rounded: rounded), + SizedBox(height: 20), + ZetaPasswordInput(rounded: rounded, size: size, hintText: 'Password', enabled: false), + SizedBox(height: 20), + ZetaPasswordInput( + size: size, + label: 'Label', + hintText: 'Password', + footerText: 'Default hint text', + rounded: rounded, + footerIcon: ZetaIcons.info_round, + ), + ]; +} diff --git a/example/lib/pages/grid_example.dart b/example/lib/pages/grid_example.dart deleted file mode 100644 index fd10ed3d..00000000 --- a/example/lib/pages/grid_example.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:zeta_flutter/zeta_flutter.dart'; - -import '../widgets.dart'; - -class GridExample extends StatelessWidget { - static const String name = 'Grid'; - - static const List symmetrical = [null, 2, 4, 8, 16]; - static const List noGaps = [false, true]; - static const List asymmetrical = [11, 10, 9, 8, 7, 5, 4, 3, 2, 1]; - - const GridExample({super.key}); - - @override - Widget build(BuildContext context) { - final List gridItems = List.generate(20, (index) => GridItem(label: (index + 1).toString())); - - final List examples = [ - ...symmetrical - .map( - (col) => noGaps.map( - (noGaps) => ExampleModel( - example: ZetaGrid( - col: col ?? 12, - noGaps: noGaps, - children: gridItems.getRange(0, (col ?? 12).toInt()).toList(), - ), - token: - r'$grid.zeta' + (col != null && col != 0 ? '.${col.toInt()}col' : '') + (noGaps ? '.nogaps' : ''), - code: - 'ZetaGrid(${col != null ? 'col:${col.toInt()}' : ''}${col != null && noGaps ? ', ' : ''}${noGaps ? 'noGaps: true' : ''}${col != null || noGaps ? ', ' : ''}children:[])', - ), - ), - ) - .expand((element) => element), - ...asymmetrical - .map( - (col) => noGaps.map( - (noGaps) => ExampleModel( - example: ZetaGrid( - asymmetricWeight: col.toInt(), - noGaps: noGaps, - children: gridItems.getRange(0, 12).toList(), - ), - token: r'$grid.zeta' + ('.${col.toInt()}to${(12 - col).toInt()}') + (noGaps ? '.nogaps' : ''), - code: 'ZetaGrid(${'asymmetricWeight:${col.toInt()}'}${noGaps ? ', noGaps: true' : ''}, children:[])', - ), - ), - ) - .expand((element) => element), - ...noGaps.map( - (noGaps) => ExampleModel( - example: Column( - children: [ - ZetaGrid( - noGaps: noGaps, - col: 8, - hybrid: true, - children: const [ - GridItem(width: 120), - Flexible(fit: FlexFit.tight, child: GridItem()), - GridItem(width: 80), - Flexible(fit: FlexFit.tight, flex: 2, child: GridItem()), - GridItem(width: 76), - Flexible(fit: FlexFit.tight, child: GridItem()), - Flexible(fit: FlexFit.tight, flex: 3, child: GridItem()), - GridItem(width: 40), - ], - ), - ], - ), - token: r'$grid.zeta.120px.1fr.80px.2fr.76px.1fr.3fr.40px' + (noGaps ? '.nogaps' : ''), - code: - 'ZetaGrid(\n col: 8,\n hybrid: true,\n ${noGaps ? 'noGaps: true,\n ' : ''}children:[\n GridItem(width: 120),\n Flexible(fit: FlexFit.tight, child: GridItem()),\n GridItem(width: 80),\n Flexible(fit: FlexFit.tight, flex: 2, child: GridItem()),\n GridItem(width: 76),\n Flexible(fit: FlexFit.tight, child: GridItem()),\n Flexible(fit: FlexFit.tight, flex: 3, child: GridItem()),\n GridItem(width: 40),\n ],\n)', - ), - ) - ]; - - return ExampleScaffold( - name: name, - child: SingleChildScrollView( - padding: EdgeInsets.all(Dimensions.s), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [...examples.map(ExampleBuilder.new)], - ), - ), - ); - } -} - -class GridItem extends StatelessWidget { - final String label; - final double? width; - - const GridItem({this.label = '', this.width, super.key}); - - @override - Widget build(BuildContext context) { - final colors = Zeta.of(context).colors; - return Container( - height: 80, - width: width, - decoration: BoxDecoration(border: Border.all(color: colors.blue.border), color: colors.blue.surface), - child: Padding( - padding: const EdgeInsets.only(left: 8), - child: ZetaText.bodyLarge(label, textColor: colors.blue.text), - ), - ); - } -} diff --git a/example/lib/pages/spacing_example.dart b/example/lib/pages/spacing_example.dart deleted file mode 100644 index ca7a82ce..00000000 --- a/example/lib/pages/spacing_example.dart +++ /dev/null @@ -1,132 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:zeta_flutter/zeta_flutter.dart'; - -import '../widgets.dart'; - -class _SizeEx { - final String name; - final double? value; - - const _SizeEx({required this.name, required this.value}); -} - -class _TypeEx { - final String name; - final ZetaSpacingType? value; - final String description; - - const _TypeEx({required this.name, required this.value, required this.description}); -} - -class SpacingExample extends StatelessWidget { - static const String name = 'Spacing'; - - const SpacingExample({super.key}); - - static const List<_SizeEx> _sizes = [ - _SizeEx(name: '', value: null), - _SizeEx(name: 'x1', value: Dimensions.x1), - _SizeEx(name: 'xxs', value: Dimensions.xxs), - _SizeEx(name: 'x2', value: Dimensions.x2), - _SizeEx(name: 'xs', value: Dimensions.xs), - _SizeEx(name: 'x3', value: Dimensions.x3), - _SizeEx(name: 's', value: Dimensions.s), - _SizeEx(name: 'x4', value: Dimensions.x4), - _SizeEx(name: 'b', value: Dimensions.b), - _SizeEx(name: 'x5', value: Dimensions.x5), - _SizeEx(name: 'x6', value: Dimensions.x6), - _SizeEx(name: 'm', value: Dimensions.m), - _SizeEx(name: 'x7', value: Dimensions.x7), - _SizeEx(name: 'x8', value: Dimensions.x8), - _SizeEx(name: 'l', value: Dimensions.l), - _SizeEx(name: 'x9', value: Dimensions.x9), - _SizeEx(name: 'x10', value: Dimensions.x10), - _SizeEx(name: 'x11', value: Dimensions.x11), - _SizeEx(name: 'x12', value: Dimensions.x12), - _SizeEx(name: 'x16', value: Dimensions.x16), - _SizeEx(name: 'xl', value: Dimensions.xl), - _SizeEx(name: 'x20', value: Dimensions.x20), - _SizeEx(name: 'xxl', value: Dimensions.xxl), - _SizeEx(name: 'x24', value: Dimensions.x24), - _SizeEx(name: 'xxxl', value: Dimensions.xxxl), - ]; - - static const List<_TypeEx> _types = [ - _TypeEx(name: '', value: null, description: 'Adds {0}rem either or both margin and padding.'), - _TypeEx(name: 'square', value: ZetaSpacingType.square, description: 'Adds {0}rem padding.'), - _TypeEx(name: 'squish', value: ZetaSpacingType.squish, description: 'Adds {0}rem top and bottom padding.'), - _TypeEx(name: 'inline', value: ZetaSpacingType.inline, description: 'Adds {0}rem left and right padding.'), - _TypeEx(name: 'inline.start', value: ZetaSpacingType.inlineStart, description: 'Adds {0}rem start padding.'), - _TypeEx(name: 'inline.end', value: ZetaSpacingType.inlineEnd, description: 'Adds {0}rem end padding.'), - _TypeEx(name: 'stack', value: ZetaSpacingType.stack, description: 'Adds {0}rem bottom padding.'), - ]; - - static final List _x = _sizes - .map((size) { - final x = _types.map((type) { - return ExampleModel( - token: r'$spacing.zeta' + - (type.value != null ? '.${type.name}' : '') + - (size.value != null ? '.${size.name}' : ''), - example: _SpaceExample(size: size.value ?? 0, type: type.value ?? ZetaSpacingType.square), - description: type.description.replaceAll('{0}', size.value == null ? '0' : (size.value! ~/ 4).toString()), - code: - 'ZetaSpacing${type.value != null ? '.${type.name}' : ''}(child${size.value != null ? ', size: ZetaSpacing.${size.name}' : ''})', - ); - }); - return [...x, null]; - }) - .expand((element) => element) - .toList(); - - @override - Widget build(BuildContext context) { - return ExampleScaffold( - name: name, - child: ListView.builder( - padding: EdgeInsets.all(Dimensions.s), - itemCount: _x.length, - itemBuilder: (context, index) { - final e = _x[index]; - return e == null ? const Divider() : ExampleBuilder(e); - }, - ), - ); - } -} - -class _SpaceExample extends StatelessWidget { - final double size; - final ZetaSpacingType type; - - const _SpaceExample({required this.size, required this.type}); - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Expanded( - child: ColoredBox( - color: Zeta.of(context).colors.blue.shade20, - child: ZetaSpacing(const SpacingItem(), size: size, type: type), - ), - ), - ], - ); - } -} - -class SpacingItem extends StatelessWidget { - const SpacingItem({super.key}); - - @override - Widget build(BuildContext context) { - return DecoratedBox( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - border: Border.all(color: const Color(0xFFdddddd)), - ), - child: const Text('Text with some spacing'), - ); - } -} diff --git a/example/lib/pages/color_example.dart b/example/lib/pages/theme/color_example.dart similarity index 83% rename from example/lib/pages/color_example.dart rename to example/lib/pages/theme/color_example.dart index 1093e8bf..da441b7c 100644 --- a/example/lib/pages/color_example.dart +++ b/example/lib/pages/theme/color_example.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; -import '../widgets.dart'; - class ColorExample extends StatefulWidget { static const String name = 'Color'; @@ -124,21 +123,21 @@ class _ColorExampleState extends State { }; final Map primaries = { - 'primaryColor': colors.primary, - 'secondaryColor': colors.secondary, + 'primaryColor': colors.primary.text, + 'secondaryColor': colors.secondary.text, }; final Map alerts = { - 'positive': colors.positive, - 'negative': colors.negative, - 'warning': colors.warning, - 'info': colors.info, + 'positive': colors.positive.text, + 'negative': colors.negative.text, + 'warning': colors.warning.text, + 'info': colors.info.text, }; return ExampleScaffold( name: ColorExample.name, child: SingleChildScrollView( - padding: EdgeInsets.all(Dimensions.s), + padding: EdgeInsets.all(ZetaSpacing.s), child: Column( children: [ MyRow(children: textIcon, title: 'Text and icon styles'), @@ -147,7 +146,8 @@ class _ColorExampleState extends State { MyRow(children: backdrop, title: 'Backdrop colors'), MyRow(children: primaries, title: 'Primary colors'), MyRow(children: alerts, title: 'Alert colors'), - Row(children: [ZetaText.displayMedium('Full color swatches')]).squish(Dimensions.x8), + Row(children: [Text('Full color swatches', style: ZetaTextStyles.displayMedium)]) + .paddingVertical(ZetaSpacing.x8), ...swatches.entries.map( (value) => Row( children: List.generate(10, (index) => 100 - (10 * index)) @@ -158,21 +158,19 @@ class _ColorExampleState extends State { color: value.value[e], child: FittedBox( fit: BoxFit.scaleDown, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - DefaultTextStyle( - style: ZetaText.zetaBodyMedium - .copyWith(color: calculateTextColor(value.value[e] ?? Colors.white)), - child: Column( - children: [ - Text('${value.key.toLowerCase().replaceAll(' ', '')}-$e'), - Text(value.value[e].toString().replaceAll('Color(0xff', '#').substring(0, 7)), - ], - ), + child: Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + DefaultTextStyle( + style: ZetaTextStyles.bodyMedium + .copyWith(color: calculateTextColor(value.value[e] ?? Colors.white)), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text('${value.key.toLowerCase().replaceAll(' ', '')}-$e'), + Text(value.value[e].toString().replaceAll('Color(0xff', '#').substring(0, 7)), + ], ), - ], - ), + ), + ]), ), ), ), @@ -182,10 +180,11 @@ class _ColorExampleState extends State { ), ElevatedButton( onPressed: () => setState(() => showGeneratedColors = !showGeneratedColors), - child: const Text('Toggle generated colors').square(Dimensions.s), - ).square(Dimensions.s), + child: const Text('Toggle generated colors').paddingAll(ZetaSpacing.s), + ).paddingAll(ZetaSpacing.s), if (showGeneratedColors) - Row(children: [ZetaText.displayMedium('Generated color swatches')]).squish(Dimensions.x8), + Row(children: [Text('Generated color swatches', style: ZetaTextStyles.displayMedium)]) + .paddingVertical(ZetaSpacing.x8), if (showGeneratedColors) ...generatedSwatches.entries.map( (value) => Row( @@ -203,7 +202,7 @@ class _ColorExampleState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ DefaultTextStyle( - style: ZetaText.zetaBodyMedium + style: ZetaTextStyles.bodyMedium .copyWith(color: calculateTextColor(value.value[e] ?? Colors.white)), child: Column( children: [ @@ -251,7 +250,7 @@ class MyRow extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - ZetaText.labelLarge(title, textColor: Zeta.of(context).colors.textDefault), + Text(title, style: ZetaTextStyles.labelLarge), Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -268,7 +267,7 @@ class MyRow extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ DefaultTextStyle( - style: ZetaText.zetaBodyMedium.copyWith(color: calculateTextColor(e.value)), + style: ZetaTextStyles.bodyMedium.copyWith(color: calculateTextColor(e.value)), child: Column( children: [ Text(e.key), @@ -292,12 +291,6 @@ class MyRow extends StatelessWidget { } } -extension StringExtension on Color { - String get toHexString { - return toString().substring(10, 16).toUpperCase(); - } -} - Color calculateTextColor(Color background) { return ThemeData.estimateBrightnessForColor(background) == Brightness.light ? Colors.black : Colors.white; } diff --git a/example/lib/pages/theme/typography_example.dart b/example/lib/pages/theme/typography_example.dart new file mode 100644 index 00000000..80e9542b --- /dev/null +++ b/example/lib/pages/theme/typography_example.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +const String exampleText = 'The quick brown fox jumps over the lazy dog.'; + +class TypographyExample extends StatelessWidget { + static const String name = 'Typography'; + + const TypographyExample({super.key}); + + @override + Widget build(BuildContext context) { + final dedicatedSizes = [ + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.displayLarge), + code: "Text('...', style: ZetaTextStyles.displayLarge)", + title: 'Display Large', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.displayMedium), + code: "Text('...', style: ZetaTextStyles.displayMedium)", + title: 'Display Medium', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.displaySmall), + code: "Text('...', style: ZetaTextStyles.displaySmall)", + title: 'Display Small', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.heading1), + code: "Text('...', style: ZetaTextStyles.heading1)", + title: 'Heading 1', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.heading2), + code: "Text('...', style: ZetaTextStyles.heading2)", + title: 'Heading 2', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.heading3), + code: "Text('...', style: ZetaTextStyles.heading3)", + title: 'Heading 3', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.titleLarge), + code: "Text('...', style: ZetaTextStyles.titleLarge)", + title: 'Title Large', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.titleMedium), + code: "Text('...', style: ZetaTextStyles.titleMedium)", + title: 'Title Medium', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.titleSmall), + code: "Text('...', style: ZetaTextStyles.titleSmall)", + title: 'Title Small', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.bodyLarge), + code: "Text('...', style: ZetaTextStyles.titleLarge)", + title: 'Body Large', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.bodyMedium), + code: "Text('...', style: ZetaTextStyles.titleMedium)", + title: 'Body Medium', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.bodySmall), + code: "Text('...', style: ZetaTextStyles.titleSmall)", + title: 'Body Small', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.labelLarge), + code: "Text('...', style: ZetaTextStyles.labelLarge)", + title: 'Label Large', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.labelMedium), + code: "Text('...', style: ZetaTextStyles.labelMedium)", + title: 'Label Medium', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.labelSmall), + code: "Text('...', style: ZetaTextStyles.labelSmall)", + title: 'Label Small', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.labelIndicator), + code: "Text('...', style: ZetaTextStyles.labelIndicator)", + title: 'Label Indicator', + ), + ExampleModel( + example: Text(exampleText, style: ZetaTextStyles.labelTiny), + code: "Text('...', style: ZetaTextStyles.labelTiny)", + title: 'Label Tiny', + ), + ]; + + return ExampleScaffold( + name: name, + child: SingleChildScrollView( + padding: EdgeInsets.all(ZetaSpacing.s), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...dedicatedSizes.map(ExampleBuilder.new), + ], + ), + ), + ); + } +} diff --git a/example/lib/pages/typography_example.dart b/example/lib/pages/typography_example.dart deleted file mode 100644 index 0ff46d3b..00000000 --- a/example/lib/pages/typography_example.dart +++ /dev/null @@ -1,188 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:zeta_flutter/zeta_flutter.dart'; -import '../widgets.dart'; - -const String exampleText = 'Lorem ipsum dolor sit amet.'; -const String exampleParagraph = - 'Paragraph: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.'; - -class TypographyExample extends StatelessWidget { - static const String name = 'Typography'; - - const TypographyExample({super.key}); - - static const Map sizes = { - 'x3': Dimensions.x3, - 'x3_5': Dimensions.x3_5, - 'x4': Dimensions.x4, - 'x5': Dimensions.x5, - 'x6': Dimensions.x6, - 'x7': Dimensions.x7, - 'x8': Dimensions.x8, - 'x9': Dimensions.x9, - 'x10': Dimensions.x10, - 'x11': Dimensions.x11, - 'x12': Dimensions.x12, - 'x13': Dimensions.x13, - }; - static final List universalSizes = sizes.entries.map( - (size) { - return ExampleModel( - example: ZetaText(exampleText, fontSize: size.value), - token: size.value == Dimensions.x3_5 - ? r'$text.zeta.x3_5.x4' - : r'$text.zeta.x' + '${size.value ~/ 4}.x${(size.value + 4) ~/ 4}', - code: "ZetaText('', size: ZetaSpacing.${size.key})", - ); - }, - ).toList(); - - static final dedicatedSizes = [ - ExampleModel( - example: ZetaText.bodySmall(exampleText), - wDescription: ZetaText.bodySmall(exampleParagraph, maxWidth: 66), - code: "ZetaText.bodySmall('')", - token: r'$text.zeta.bodysmall', - ), - ExampleModel( - example: ZetaText.bodyMedium(exampleText), - wDescription: ZetaText.bodyMedium(exampleParagraph, maxWidth: 66), - code: "ZetaText.bodyMedium('')", - token: r'$text.zeta.bodymedium', - ), - ExampleModel( - example: ZetaText.bodyLarge(exampleText), - wDescription: ZetaText.bodyLarge(exampleParagraph, maxWidth: 66), - code: "ZetaText.bodyLarge('')", - token: r'$text.zeta.bodylarge', - ), - ExampleModel( - example: ZetaText.titleSmall(exampleText), - code: "ZetaText.titleSmall('')", - token: r'$text.zeta.titlesmall', - ), - ExampleModel( - example: ZetaText.titleMedium(exampleText), - code: "ZetaText.titleMedium('')", - token: r'$text.zeta.titlemedium', - ), - ExampleModel( - example: ZetaText.titleLarge(exampleText), - code: "ZetaText.titleLarge('')", - token: r'$text.zeta.titlelarge', - ), - ExampleModel( - example: ZetaText.headingSmall(exampleText), - code: "ZetaText.headingSmall('')", - token: r'$text.zeta.headingsmall', - ), - ExampleModel( - example: ZetaText.headingMedium(exampleText), - code: "ZetaText.headingMedium('')", - token: r'$text.zeta.headingmedium', - ), - ExampleModel( - example: ZetaText.headingLarge(exampleText), - code: "ZetaText.headingLarge('')", - token: r'$text.zeta.headinglarge', - ), - ExampleModel( - example: ZetaText.displaySmall(exampleText), - code: "ZetaText.displaySmall('')", - token: r'$text.zeta.displaysmall', - ), - ExampleModel( - example: ZetaText.displayMedium(exampleText), - code: "ZetaText.displayMedium('')", - token: r'$text.zeta.displaymedium', - ), - ExampleModel( - example: ZetaText.displayLarge(exampleText), - code: "ZetaText.displayLarge('')", - token: r'$text.zeta.displaylarge', - ), - ]; - - @override - Widget build(BuildContext context) { - final colors = Zeta.of(context).colors; - - final tokens = [ - const ExampleModel( - example: ZetaText(exampleText), - code: "ZetaText('')", - token: r'$text.zeta', - ), - ExampleModel( - example: ZetaText(exampleText, textColor: colors.textSubtle), - code: "ZetaText('', textColor: ZetaColors.textColorSubtle)", - token: r'$text.zeta.subtle', - ), - const ExampleModel( - example: ZetaText(exampleText, fontWeight: FontWeight.w300), - code: "ZetaText('', fontWeight: FontWeight.w300)", - token: r'$text.zeta.300', - ), - const ExampleModel( - example: ZetaText(exampleText, fontWeight: FontWeight.w500), - code: "ZetaText('', fontWeight: FontWeight.w500)", - token: r'$text.zeta.500', - ), - const ExampleModel( - example: ZetaText(exampleText, fontStyle: FontStyle.italic), - code: "ZetaText('', fontStyle: FontStyle.italic)", - token: r'$text.zeta.italics', - ), - const ExampleModel( - code: "ZetaText('', uppercase: true)", - token: r'$text.zeta.caps', - example: ZetaText(exampleText, upperCase: true), - ), - const ExampleModel( - example: ZetaText(exampleText, decoration: TextDecoration.underline), - code: "ZetaText('', decoration: TextDecoration.underline)", - token: r'$text.zeta.underline', - ), - const ExampleModel( - code: "ZetaText('', textDirection: TextDirection.rtl)", - token: r'$text.zeta.direction', - example: ZetaText(exampleText, textDirection: TextDirection.rtl), - ), - const ExampleModel( - code: "ZetaText('', first: true)", - token: r'$text.zeta.first', - example: ZetaText(exampleText, first: true), - ), - const ExampleModel( - code: "ZetaText('', last: true)", - token: r'$text.zeta.last', - example: ZetaText(exampleText, last: true), - ), - const ExampleModel( - code: "ZetaText('', resetHeight: true)", - token: r'$text.zeta.reset', - example: ZetaText(exampleText, resetHeight: true), - ), - ]; - - return ExampleScaffold( - name: name, - child: SingleChildScrollView( - padding: EdgeInsets.all(Dimensions.s), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ZetaText.headingLarge('Text').inline(Dimensions.x10), - ...tokens.map(ExampleBuilder.new), - const Divider().squish(Dimensions.x4), - ZetaText.headingLarge('Universal sizes').inline(Dimensions.x10), - ...universalSizes.map(ExampleBuilder.new), - const Divider().squish(Dimensions.x4), - ZetaText.headingLarge('Dedicated sizes').inline(Dimensions.x10), - ...dedicatedSizes.map(ExampleBuilder.new), - ], - ), - ), - ); - } -} diff --git a/example/lib/theme_service.dart b/example/lib/theme_service.dart index b25c0a45..3c80a1b9 100644 --- a/example/lib/theme_service.dart +++ b/example/lib/theme_service.dart @@ -1,6 +1,6 @@ import 'package:flutter/src/material/app.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:zeta_example/pages/theme_color_switch.dart'; +import 'package:zeta_example/utils/theme_color_switch.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; class SharedPrefsThemeService extends ZetaThemeService { diff --git a/example/lib/pages/theme_color_switch.dart b/example/lib/utils/theme_color_switch.dart similarity index 92% rename from example/lib/pages/theme_color_switch.dart rename to example/lib/utils/theme_color_switch.dart index 98ebbeec..cb4a29d8 100644 --- a/example/lib/pages/theme_color_switch.dart +++ b/example/lib/utils/theme_color_switch.dart @@ -26,7 +26,7 @@ class ZetaThemeColorSwitch extends StatelessWidget { @override Widget build(BuildContext context) { - var zeta = Zeta.of(context); + final zeta = Zeta.of(context); ZetaColors primary(ZetaThemeData data) { if (zeta.brightness == Brightness.light) { @@ -45,8 +45,8 @@ class ZetaThemeColorSwitch extends StatelessWidget { icon: SizedBox(width: 8), dropdownColor: zeta.colors.borderDisabled, items: appThemes.entries.map((e) { - var zetaColors = primary(appThemes[e.key]!); - var color = zetaColors.primary; + final zetaColors = primary(appThemes[e.key]!); + final color = zetaColors.primary; return DropdownMenuItem( value: e.value.identifier, alignment: Alignment.center, diff --git a/example/lib/pages/theme_constrast_switch.dart b/example/lib/utils/theme_constrast_switch.dart similarity index 89% rename from example/lib/pages/theme_constrast_switch.dart rename to example/lib/utils/theme_constrast_switch.dart index 9d36c28f..780acd01 100644 --- a/example/lib/pages/theme_constrast_switch.dart +++ b/example/lib/utils/theme_constrast_switch.dart @@ -11,7 +11,7 @@ class ZetaThemeContrastSwitch extends StatelessWidget { @override Widget build(BuildContext context) { - var zeta = Zeta.of(context); + final zeta = Zeta.of(context); ZetaColors zetaColors(ZetaContrast contrast) { if (zeta.brightness == Brightness.light) { @@ -37,10 +37,9 @@ class ZetaThemeContrastSwitch extends StatelessWidget { child: CircleAvatar( backgroundColor: colors.primary.surface, foregroundColor: colors.primary, - child: ZetaText.bodyMedium( + child: Text( e == ZetaContrast.aa ? 'AA' : 'AAA', - textColor: colors.primary, - fontWeight: FontWeight.w700, + style: ZetaTextStyles.bodyMedium.copyWith(color: colors.primary, fontWeight: FontWeight.w700), ), ), ); diff --git a/example/lib/pages/theme_mode_switch.dart b/example/lib/utils/theme_mode_switch.dart similarity index 98% rename from example/lib/pages/theme_mode_switch.dart rename to example/lib/utils/theme_mode_switch.dart index 392d7e12..fb9a22e1 100644 --- a/example/lib/pages/theme_mode_switch.dart +++ b/example/lib/utils/theme_mode_switch.dart @@ -12,7 +12,7 @@ class ZetaThemeModeSwitch extends StatelessWidget { @override Widget build(BuildContext context) { - var zeta = Zeta.of(context); + final zeta = Zeta.of(context); ZetaColors zetaColors(ThemeMode mode) { if ((mode == ThemeMode.system && MediaQuery.of(context).platformBrightness == Brightness.light) || diff --git a/example/lib/widgets.dart b/example/lib/widgets.dart index 99c8da5d..ae57919f 100644 --- a/example/lib/widgets.dart +++ b/example/lib/widgets.dart @@ -1,24 +1,25 @@ import 'package:flutter/material.dart'; - import 'package:google_fonts/google_fonts.dart'; -import 'package:zeta_example/pages/theme_color_switch.dart'; -import 'package:zeta_example/pages/theme_constrast_switch.dart'; -import 'package:zeta_example/pages/theme_mode_switch.dart'; +import 'package:zeta_example/utils/theme_color_switch.dart'; +import 'package:zeta_example/utils/theme_constrast_switch.dart'; +import 'package:zeta_example/utils/theme_mode_switch.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; class ExampleModel { final Widget example; final String code; - final String token; + final String? token; final String? description; final Widget? wDescription; + final String? title; const ExampleModel({ required this.example, - required this.token, required this.code, + this.token, this.description, this.wDescription, + this.title, }); } @@ -32,17 +33,21 @@ class ExampleBuilder extends StatelessWidget { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - height: 7, - width: 7, - decoration: BoxDecoration(color: Theme.of(context).colorScheme.onSurface, shape: BoxShape.circle), - ).squish(Dimensions.x9).inline(Dimensions.x4), + if (model.token != null) + Container( + height: 7, + width: 7, + decoration: BoxDecoration(color: Theme.of(context).colorScheme.onSurface, shape: BoxShape.circle), + ).paddingVertical(ZetaSpacing.x9).paddingHorizontal(ZetaSpacing.x4), + if (model.title != null && MediaQuery.of(context).size.width > 767) Expanded(child: Text(model.title!)), Expanded( + flex: 5, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - CodeExample(code: model.token), - if (model.description != null) ZetaText(model.description), + if (model.title != null && MediaQuery.of(context).size.width <= 767) Text(model.title!), + if (model.token != null) CodeExample(code: model.token!), + if (model.description != null) Text(model.description!), if (model.wDescription != null) model.wDescription!, model.example, Container(color: const Color(0xFFF5F5F5)), @@ -50,7 +55,7 @@ class ExampleBuilder extends StatelessWidget { ], ), ), - const SizedBox(height: 7, width: 7).squish(Dimensions.x9).inline(Dimensions.x4), + const SizedBox(height: 7, width: 7).paddingVertical(ZetaSpacing.x9).paddingHorizontal(ZetaSpacing.x4), ], ); } @@ -60,23 +65,29 @@ class ExampleScaffold extends StatelessWidget { final String name; final Widget child; final List actions; + final Widget? floatingActionButton; const ExampleScaffold({ required this.name, required this.child, this.actions = const [], + this.floatingActionButton, super.key, }); @override Widget build(BuildContext context) { - var theme = Theme.of(context); + final theme = Theme.of(context); final colors = theme.colorScheme; return Scaffold( + floatingActionButton: floatingActionButton, appBar: AppBar( centerTitle: false, - title: Text(name), + title: Text( + name, + style: ZetaTextStyles.titleMedium, + ), backgroundColor: colors.primary, foregroundColor: colors.onPrimary, actions: [ @@ -86,6 +97,7 @@ class ExampleScaffold extends StatelessWidget { ZetaThemeColorSwitch(), ], ), + backgroundColor: colors.surface, body: SelectionArea( child: child, ), @@ -101,10 +113,10 @@ class CodeExample extends StatelessWidget { @override Widget build(BuildContext context) { - var colors = Zeta.of(context).colors; + final colors = Zeta.of(context).colors; final widget = Container( color: colors.surfaceDisabled, - padding: Dimensions.x4.square, + padding: EdgeInsets.all(ZetaSpacing.x4), child: Text(code, style: GoogleFonts.ibmPlexMono(color: colors.textDefault)), ); @@ -115,7 +127,6 @@ class CodeExample extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const FlutterWordMark(), Row(children: [Expanded(child: widget)]), ], ), @@ -123,26 +134,6 @@ class CodeExample extends StatelessWidget { ], ) : widget) - .squish(Dimensions.x4); - } -} - -class FlutterWordMark extends StatelessWidget { - final String text; - final EdgeInsets padding; - - const FlutterWordMark({ - this.text = 'Flutter', - this.padding = const EdgeInsets.symmetric(horizontal: Dimensions.x5, vertical: Dimensions.x2), - super.key, - }); - - @override - Widget build(BuildContext context) { - return Container( - color: Zeta.of(context).colors.borderSubtle, - padding: padding, - child: ZetaText(text), - ); + .paddingVertical(ZetaSpacing.x4); } } diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..eefcc6d7 --- /dev/null +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import path_provider_foundation +import shared_preferences_foundation +import sqflite + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) +} diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 0782b5c6..4556329c 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -3,20 +3,34 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite (0.0.3): + - Flutter + - FlutterMacOS DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`) EXTERNAL SOURCES: FlutterMacOS: :path: Flutter/ephemeral path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + sqflite: + :path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 + sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index d644e73f..253d9342 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -159,7 +159,6 @@ 775BFA427E635DE4CCD15022 /* Pods-Runner.release.xcconfig */, 89C2799490DE736CE6C6B909 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 74d5c998..6b891784 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -7,6 +7,7 @@ environment: sdk: ">=3.0.1 <4.0.0" dependencies: + cached_network_image: ^3.3.1 flutter: sdk: flutter go_router: ^11.1.2 diff --git a/example/test/badge_test.dart b/example/test/badge_test.dart new file mode 100644 index 00000000..0028007d --- /dev/null +++ b/example/test/badge_test.dart @@ -0,0 +1,24 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import 'test_components.dart'; + +void main() { + testWidgets('Initializes with correct parameters', (WidgetTester tester) async { + await tester.pumpWidget( + TestWidget( + widget: ZetaBadge( + label: 'Test Label', + status: ZetaWidgetStatus.warning, + ), + ), + ); + + final zetaBadgeFinder = find.byType(ZetaBadge); + final ZetaBadge badge = tester.firstWidget(zetaBadgeFinder); + + expect(badge.rounded, true); + expect(badge.label, 'Test Label'); + expect(badge.status, ZetaWidgetStatus.warning); + }); +} diff --git a/example/test/button_test.dart b/example/test/button_test.dart new file mode 100644 index 00000000..cbd6ed03 --- /dev/null +++ b/example/test/button_test.dart @@ -0,0 +1,36 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import 'test_components.dart'; + +void main() { + group('ZetaButton Tests', () { + testWidgets('Initializes with correct Label', (WidgetTester tester) async { + await tester.pumpWidget( + TestWidget( + widget: ZetaButton( + onPressed: () {}, + label: 'Test Button', + ), + ), + ); + + expect(find.text('Test Button'), findsOneWidget); + }); + }); + + testWidgets('Triggers callback on tap', (WidgetTester tester) async { + bool callbackTriggered = false; + await tester.pumpWidget( + TestWidget( + widget: ZetaButton( + onPressed: () => callbackTriggered = true, + label: 'Test Button', + )), + ); + await tester.tap(find.byType(ZetaButton)); + await tester.pump(); + + expect(callbackTriggered, isTrue); + }); +} diff --git a/example/test/checkbox_test.dart b/example/test/checkbox_test.dart index abc531a6..78ba7381 100644 --- a/example/test/checkbox_test.dart +++ b/example/test/checkbox_test.dart @@ -1,42 +1,43 @@ +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; -import 'package:flutter/material.dart'; + +import 'test_components.dart'; void main() { group('ZetaCheckbox Tests', () { - testWidgets('Initializes with correct parameters', - (WidgetTester tester) async { + testWidgets('Initializes with correct parameters', (WidgetTester tester) async { await tester.pumpWidget( - TestWidgetCB( - widget: ZetaCheckbox( - value: true, - onChanged: (value) {}, - borderType: BorderType.rounded, - label: 'Test Checkbox', - checkboxSize: Size(30, 30), - )), + TestWidget( + widget: ZetaCheckbox( + value: true, + onChanged: (value) {}, + label: 'Test Checkbox', + ), + ), ); final checkboxFinder = find.byType(ZetaCheckbox); final ZetaCheckbox checkbox = tester.firstWidget(checkboxFinder); expect(checkbox.value, true); - expect(checkbox.borderType, BorderType.rounded); + expect(checkbox.rounded, true); expect(checkbox.label, 'Test Checkbox'); - expect(checkbox.checkboxSize, Size(30, 30)); }); testWidgets('ZetaCheckbox changes state on tap', (WidgetTester tester) async { bool? checkboxValue = true; await tester.pumpWidget( - TestWidgetCB( - widget: ZetaCheckbox( - value: checkboxValue, - onChanged: (value) { - checkboxValue = value; - }, - )), + TestWidget( + removeBody: true, + widget: ZetaCheckbox( + value: checkboxValue, + onChanged: (value) { + checkboxValue = value; + }, + ), + ), ); await tester.tap(find.byType(ZetaCheckbox)); @@ -60,7 +61,7 @@ class TestWidgetCB extends StatelessWidget { return MaterialApp( theme: ThemeData( fontFamily: theme.fontFamily, - textTheme: ZetaText.textTheme, + textTheme: zetaTextTheme, ), home: Scaffold( body: widget, diff --git a/example/test/fab_test.dart b/example/test/fab_test.dart new file mode 100644 index 00000000..63bebbc0 --- /dev/null +++ b/example/test/fab_test.dart @@ -0,0 +1,46 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import 'test_components.dart'; + +void main() { + group('ZetaFAB Tests', () { + testWidgets('Initializes with correct', (WidgetTester tester) async { + final scrollController = ScrollController(); + await tester.pumpWidget(TestWidget( + widget: ZetaFAB( + scrollController: scrollController, + label: 'Label', + ))); + + expect(find.byType(ZetaFAB), findsOneWidget); + }); + + testWidgets('OnPressed callback', (WidgetTester tester) async { + bool isPressed = false; + final scrollController = ScrollController(); + + await tester.pumpWidget(TestWidget( + widget: ZetaFAB( + scrollController: scrollController, + label: 'Label', + onPressed: () => isPressed = true, + ))); + + await tester.tap(find.byType(ZetaFAB)); + await tester.pumpAndSettle(); + expect(isPressed, isTrue); + }); + }); + + testWidgets('Icon Test', (WidgetTester tester) async { + final scrollController = ScrollController(); + await tester.pumpWidget(TestWidget( + widget: ZetaFAB( + scrollController: scrollController, + label: 'Label', + ))); + expect(find.byIcon(ZetaIcons.add_round), findsOneWidget); + }); +} diff --git a/example/test/grid_test.dart b/example/test/grid_test.dart deleted file mode 100644 index c766503e..00000000 --- a/example/test/grid_test.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:zeta_example/pages/grid_example.dart'; -import 'package:zeta_flutter/zeta_flutter.dart'; -import 'test_components.dart'; - -void main() { - testWidgets('Grid component default desktop', (tester) async { - await tester.pumpWidget( - TestWidget(widget: ZetaGrid(children: List.generate(20, (index) => GridItem(key: Key(index.toString()))))), - ); - - final item0 = tester.getCenter(find.byKey(const Key('0'))); - final item11 = tester.getCenter(find.byKey(const Key('11'))); - final item12 = tester.getCenter(find.byKey(const Key('12'))); - - // First 12 items don't wrap - all grid items on same row - expect(item0.dy, item11.dy); - - // Items wrap after 12th item - expect(item0.dy != item12.dy, true); - }); - - testWidgets('Grid component default mobile', (tester) async { - await tester.pumpWidget( - TestWidget( - widget: ZetaGrid(children: List.generate(20, (index) => GridItem(key: Key(index.toString())))), - screenSize: const Size(400, 600), - ), - ); - - final item0 = tester.getCenter(find.byKey(const Key('0'))); - final item1 = tester.getCenter(find.byKey(const Key('1'))); - final item2 = tester.getCenter(find.byKey(const Key('2'))); - - // First 2 items are on same row - expect(item0.dy, item1.dy); - - // Grid wraps at 2 columns - expect(item1.dy == item2.dy, false); - }); -} diff --git a/example/test/in_page_banner_test.dart b/example/test/in_page_banner_test.dart new file mode 100644 index 00000000..bf28f160 --- /dev/null +++ b/example/test/in_page_banner_test.dart @@ -0,0 +1,88 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; +import 'package:flutter/material.dart'; + +import 'test_components.dart'; + +void main() { + group('ZetaInPageBanner Tests', () { + testWidgets('ZetaInPageBanner creation', (WidgetTester tester) async { + await tester.pumpWidget( + TestWidget( + widget: ZetaInPageBanner( + content: Text('Test'), + )), + ); + + expect(find.byType(ZetaInPageBanner), findsOneWidget); + }); + }); + + testWidgets('ZetaInPageBanner displays content text', (WidgetTester tester) async { + await tester.pumpWidget( + TestWidget( + widget: ZetaInPageBanner( + content: Text('Test'), + )), + ); + + expect(find.text('Test'), findsOneWidget); + }); + + testWidgets('ZetaInPageBanner shows/hides \'close icon\' correctly', (WidgetTester tester) async { + await tester.pumpWidget( + TestWidget( + widget: ZetaInPageBanner( + content: Text('Test'), + onClose: () {}, + )), + ); + expect(find.byIcon(ZetaIcons.close_round), findsOneWidget); + + await tester.pumpWidget( + TestWidget( + widget: ZetaInPageBanner( + content: Text('Test'), + )), + ); + expect(find.byIcon(ZetaIcons.close_sharp), findsNothing); + }); + + testWidgets('ZetaInPageBanner button callbacks work', (WidgetTester tester) async { + bool onPressed = false; + final key = GlobalKey(); + await tester.pumpWidget( + TestWidget( + widget: ZetaInPageBanner( + content: Text('Test'), + actions: [ + ZetaButton( + label: 'Test button', + onPressed: () => onPressed = true, + key: key, + ), + ], + ), + ), + ); + + await tester.tap(find.byKey(key)); + await tester.pumpAndSettle(); + expect(onPressed, isTrue); + }); + + testWidgets('ZetaInPageBanner \'close\' icon tap test', (WidgetTester tester) async { + bool closeIconIsTapped = false; + await tester.pumpWidget( + TestWidget( + widget: ZetaInPageBanner( + onClose: () => closeIconIsTapped = true, + content: Text('Test'), + )), + ); + final closeIcon = find.byIcon(ZetaIcons.close_round); + await tester.tap(closeIcon); + await tester.pump(); + expect(closeIconIsTapped, isTrue); + }); +} diff --git a/example/test/password_input_test.dart b/example/test/password_input_test.dart new file mode 100644 index 00000000..765ede1a --- /dev/null +++ b/example/test/password_input_test.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import 'test_components.dart'; + +void main() { + testWidgets('ZetaPasswordInput initializes correctly', (WidgetTester tester) async { + await tester.pumpWidget( + TestWidget( + widget: ZetaPasswordInput(), + ), + ); + expect(find.byType(ZetaPasswordInput), findsOneWidget); + }); + + testWidgets('Test password visibility', (WidgetTester tester) async { + await tester.pumpWidget( + TestWidget( + widget: ZetaPasswordInput(), + ), + ); + final obscureIconOff = find.byIcon(ZetaIcons.visibility_off_sharp); + expect(obscureIconOff, findsOneWidget); + await tester.tap(obscureIconOff); + await tester.pump(); + + final obscureIconOn = find.byIcon(ZetaIcons.visibility_sharp); + expect(obscureIconOn, findsOneWidget); + }); + + testWidgets('Test error message visibility', (WidgetTester tester) async { + String? testValidator(String? value) { + final regExp = RegExp(r'\d'); + if (value != null && regExp.hasMatch(value)) return 'Error'; + return null; + } + + await tester.pumpWidget( + TestWidget( + widget: ZetaPasswordInput( + controller: TextEditingController(), + validator: testValidator, + ), + ), + ); + + final passwordField = find.byType(TextFormField); + await tester.enterText(passwordField, 'password12'); + await tester.pump(); + + expect(find.text('Error'), findsOneWidget); + }); +} diff --git a/example/test/priority_pill_test.dart b/example/test/priority_pill_test.dart new file mode 100644 index 00000000..b2bd6557 --- /dev/null +++ b/example/test/priority_pill_test.dart @@ -0,0 +1,19 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import 'test_components.dart'; + +void main() { + testWidgets('Initializes with correct label and index', (WidgetTester tester) async { + await tester.pumpWidget( + TestWidget( + widget: ZetaPriorityPill( + priority: 'High', + index: 2, + ), + ), + ); + expect(find.text('High'), findsOneWidget); + expect(find.text('2'), findsOneWidget); + }); +} diff --git a/example/test/spacing_test.dart b/example/test/spacing_test.dart deleted file mode 100644 index cc6cf27a..00000000 --- a/example/test/spacing_test.dart +++ /dev/null @@ -1,188 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:zeta_flutter/zeta_flutter.dart'; -import 'test_components.dart'; - -void main() { - const Widget testChild = Text('Item'); - const Key control = Key('control'); - - testWidgets('Square', (tester) async { - await tester.pumpWidget( - TestWidget( - widget: Column( - children: [ - const SizedBox(key: Key('1'), child: ZetaSpacing(testChild, size: Dimensions.x1)), - const SizedBox( - key: Key('2'), - child: ZetaSpacing.square(testChild, size: Dimensions.x1), - ), - SizedBox(key: const Key('3'), child: testChild.square(Dimensions.x1)), - SizedBox(key: const Key('4'), child: Padding(padding: Dimensions.x1.square, child: testChild)), - const SizedBox( - key: control, - child: Padding(padding: EdgeInsets.all(4), child: testChild), - ), - ], - ), - ), - ); - - final item1 = tester.getSize(find.byKey(const Key('1'))); - final item2 = tester.getSize(find.byKey(const Key('2'))); - final item3 = tester.getSize(find.byKey(const Key('3'))); - final item4 = tester.getSize(find.byKey(const Key('4'))); - final controlItem = tester.getSize(find.byKey(control)); - - // Test all spacing methods return same size widget - expect(item1, item2); - expect(item2, item3); - expect(item3, item4); - - // Test spacing component against known correct control item - expect(item1, controlItem); - }); - testWidgets('Squish', (tester) async { - await tester.pumpWidget( - TestWidget( - widget: Column( - children: [ - const SizedBox(key: Key('1'), child: ZetaSpacing.squish(testChild, size: Dimensions.x1)), - SizedBox(key: const Key('2'), child: testChild.squish(Dimensions.x1)), - SizedBox(key: const Key('3'), child: Padding(padding: Dimensions.x1.squish, child: testChild)), - const SizedBox( - key: control, - child: Padding(padding: EdgeInsets.symmetric(vertical: 4), child: testChild), - ), - ], - ), - ), - ); - - final item1 = tester.getSize(find.byKey(const Key('1'))); - final item2 = tester.getSize(find.byKey(const Key('2'))); - final item3 = tester.getSize(find.byKey(const Key('3'))); - final controlItem = tester.getSize(find.byKey(control)); - - // Test all spacing methods return same size widget - expect(item1, item2); - expect(item2, item3); - - // Test spacing component against known correct control item - expect(item1, controlItem); - }); - - testWidgets('Stack', (tester) async { - await tester.pumpWidget( - TestWidget( - widget: Column( - children: [ - const SizedBox(key: Key('1'), child: ZetaSpacing.stack(testChild, size: Dimensions.x1)), - SizedBox(key: const Key('2'), child: testChild.stack(Dimensions.x1)), - SizedBox(key: const Key('3'), child: Padding(padding: Dimensions.x1.stack, child: testChild)), - const SizedBox(key: control, child: Padding(padding: EdgeInsets.only(bottom: 4), child: testChild)), - ], - ), - ), - ); - - final item1 = tester.getSize(find.byKey(const Key('1'))); - final item2 = tester.getSize(find.byKey(const Key('2'))); - final item3 = tester.getSize(find.byKey(const Key('3'))); - final controlItem = tester.getSize(find.byKey(control)); - - // Test all spacing methods return same size widget - expect(item1, item2); - expect(item2, item3); - - // Test spacing component against known correct control item - expect(item1, controlItem); - }); - testWidgets('Inline', (tester) async { - await tester.pumpWidget( - TestWidget( - widget: Column( - children: [ - const SizedBox(key: Key('1'), child: ZetaSpacing.inline(testChild, size: Dimensions.x1)), - SizedBox(key: const Key('2'), child: testChild.inline(Dimensions.x1)), - SizedBox(key: const Key('3'), child: Padding(padding: Dimensions.x1.inline, child: testChild)), - const SizedBox( - key: control, - child: Padding(padding: EdgeInsets.symmetric(horizontal: 4), child: testChild), - ), - ], - ), - ), - ); - - final item1 = tester.getSize(find.byKey(const Key('1'))); - final item2 = tester.getSize(find.byKey(const Key('2'))); - final item3 = tester.getSize(find.byKey(const Key('3'))); - final controlItem = tester.getSize(find.byKey(control)); - - // Test all spacing methods return same size widget - expect(item1, item2); - expect(item2, item3); - - // Test spacing component against known correct control item - expect(item1, controlItem); - }); - testWidgets('Inline Start', (tester) async { - await tester.pumpWidget( - TestWidget( - widget: Column( - children: [ - const SizedBox(key: Key('1'), child: ZetaSpacing.inlineStart(testChild, size: Dimensions.x1)), - SizedBox(key: const Key('2'), child: testChild.inlineStart(Dimensions.x1)), - SizedBox(key: const Key('3'), child: Padding(padding: Dimensions.x1.inlineStart, child: testChild)), - const SizedBox( - key: control, - child: Padding(padding: EdgeInsetsDirectional.only(start: 4), child: testChild), - ), - ], - ), - ), - ); - - final item1 = tester.getSize(find.byKey(const Key('1'))); - final item2 = tester.getSize(find.byKey(const Key('2'))); - final item3 = tester.getSize(find.byKey(const Key('3'))); - final controlItem = tester.getSize(find.byKey(control)); - - // Test all spacing methods return same size widget - expect(item1, item2); - expect(item2, item3); - - // Test spacing component against known correct control item - expect(item1, controlItem); - }); - testWidgets('Inline end', (tester) async { - await tester.pumpWidget( - TestWidget( - widget: Column( - children: [ - const SizedBox(key: Key('1'), child: ZetaSpacing.inlineEnd(testChild, size: Dimensions.x1)), - SizedBox(key: const Key('2'), child: testChild.inlineEnd(Dimensions.x1)), - SizedBox(key: const Key('3'), child: Padding(padding: Dimensions.x1.inlineEnd, child: testChild)), - const SizedBox( - key: control, - child: Padding(padding: EdgeInsetsDirectional.only(end: 4), child: testChild), - ), - ], - ), - ), - ); - - final item1 = tester.getSize(find.byKey(const Key('1'))); - final item2 = tester.getSize(find.byKey(const Key('2'))); - final item3 = tester.getSize(find.byKey(const Key('3'))); - final controlItem = tester.getSize(find.byKey(control)); - - // Test all spacing methods return same size widget - expect(item1, item2); - expect(item2, item3); - - // Test spacing component against known correct control item - expect(item1, controlItem); - }); -} diff --git a/example/test/status_label_test.dart b/example/test/status_label_test.dart new file mode 100644 index 00000000..b1908792 --- /dev/null +++ b/example/test/status_label_test.dart @@ -0,0 +1,54 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; +import 'package:flutter/material.dart'; + +void main() { + group('ZetaStatusLabel Tests', () { + testWidgets('Initializes with correct properties', (WidgetTester tester) async { + await tester.pumpWidget( + TestWidgetStatusLabel( + widget: ZetaStatusLabel(label: 'Test Label'), + ), + ); + expect(find.text('Test Label'), findsOneWidget); + }); + }); + + testWidgets('Initializes with correct label and custom icon', (WidgetTester tester) async { + await tester.pumpWidget( + TestWidgetStatusLabel( + widget: ZetaStatusLabel( + label: 'Custom Icon', + customIcon: Icons.person, + ), + ), + ); + expect(find.text('Custom Icon'), findsOneWidget); + expect(find.byIcon(Icons.person), findsOneWidget); + }); +} + +class TestWidgetStatusLabel extends StatelessWidget { + final Widget widget; + + const TestWidgetStatusLabel({Key? key, required this.widget}); + + @override + Widget build(BuildContext context) { + return ZetaProvider( + builder: (context, theme, __) { + return Builder(builder: (context) { + return MaterialApp( + theme: ThemeData( + fontFamily: theme.fontFamily, + textTheme: zetaTextTheme, + ), + home: Scaffold( + body: widget, + ), + ); + }); + }, + ); + } +} diff --git a/example/test/tag_test.dart b/example/test/tag_test.dart new file mode 100644 index 00000000..6925b94c --- /dev/null +++ b/example/test/tag_test.dart @@ -0,0 +1,40 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import 'test_components.dart'; + +void main() { + group('ZetaCheckbox Tests', () { + testWidgets('Initializes with correct parameters', (WidgetTester tester) async { + await tester.pumpWidget( + TestWidget( + widget: ZetaTag.right( + label: 'Tag', + ), + ), + ); + + expect(find.text('Tag'), findsOneWidget); + }); + + testWidgets('ZetaTag handles direction correctly', (WidgetTester tester) async { + const widgetLeft = TestWidget( + widget: ZetaTag( + label: 'Tag', + direction: ZetaTagDirection.left, + )); + + const widgetRight = TestWidget( + widget: ZetaTag( + label: 'Tag', + direction: ZetaTagDirection.right, + )); + + await tester.pumpWidget(widgetLeft); + expect(find.byType(ZetaTag), findsOneWidget); + + await tester.pumpWidget(widgetRight); + expect(find.byType(ZetaTag), findsOneWidget); + }); + }); +} diff --git a/example/test/test_components.dart b/example/test/test_components.dart index 117e5717..38c78b57 100644 --- a/example/test/test_components.dart +++ b/example/test/test_components.dart @@ -4,37 +4,49 @@ import 'package:zeta_flutter/zeta_flutter.dart'; class TestWidget extends StatelessWidget { final Size? screenSize; final Widget widget; + final ThemeMode? themeMode; + final bool removeBody; - const TestWidget({required this.widget, this.screenSize, super.key}); + const TestWidget({ + required this.widget, + this.screenSize, + super.key, + this.themeMode, + this.removeBody = false, + }); @override Widget build(BuildContext context) { final size = screenSize ?? const Size(1280, 720); return ZetaProvider( + initialThemeMode: themeMode ?? ThemeMode.system, builder: (context, theme, __) { return Builder( builder: (context) { return MaterialApp( + debugShowCheckedModeBanner: false, theme: ThemeData( fontFamily: theme.fontFamily, colorScheme: theme.colorsLight.toScheme(), - textTheme: ZetaText.textTheme, + textTheme: zetaTextTheme, ), darkTheme: ThemeData( fontFamily: theme.fontFamily, colorScheme: theme.colorsDark.toScheme(), - textTheme: ZetaText.textTheme, + textTheme: zetaTextTheme, ), home: Scaffold( - body: SizedBox( - width: size.width, - height: size.height, - child: MediaQuery( - data: MediaQueryData(size: Size(size.width, size.height)), - child: SingleChildScrollView(child: widget), - ), - ), + body: removeBody + ? widget + : SizedBox( + width: size.width, + height: size.height, + child: MediaQuery( + data: MediaQueryData(size: Size(size.width, size.height)), + child: SingleChildScrollView(child: widget), + ), + ), ), ); }, diff --git a/example/test/typography_test.dart b/example/test/typography_test.dart index b20074f0..6eec19ef 100644 --- a/example/test/typography_test.dart +++ b/example/test/typography_test.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:zeta_example/pages/typography_example.dart'; +import 'package:zeta_example/pages/theme/typography_example.dart'; + import 'package:zeta_flutter/zeta_flutter.dart'; import 'test_components.dart'; @@ -18,23 +19,14 @@ void main() { builder: (context) { return Column( children: [ - const ZetaText(exampleText, key: key1), - ZetaText( + const Text(exampleText, key: key1), + Text(exampleText, style: ZetaTextStyles.bodyMedium, key: key2), + Text(exampleText, style: ZetaTextStyles.displayLarge, key: key3), + Text( exampleText, - maxWidth: 66, - decoration: TextDecoration.underline, - key: key2, - upperCase: true, - fontSize: 100, - fontStyle: FontStyle.italic, - fontWeight: FontWeight.w500, - textColor: Zeta.of(context).colors.textSubtle, - textDirection: TextDirection.rtl, - first: true, - last: true, + style: TextStyle(fontSize: 52, fontWeight: FontWeight.w300, height: 64 / 52), + key: key4, ), - ZetaText(exampleText, style: ZetaText.zetaHeadingLarge, key: key3), - ZetaText.headingLarge(exampleText, key: key4), ], ); }, @@ -56,46 +48,19 @@ void main() { final InlineSpan text4 = (find.descendant(of: zetaText4, matching: find.byType(RichText)).evaluate().first.widget as RichText).text; - final Padding padding1 = - find.descendant(of: zetaText1, matching: find.byType(Padding)).evaluate().first.widget as Padding; - final Padding padding2 = - find.descendant(of: zetaText2, matching: find.byType(Padding)).evaluate().first.widget as Padding; + /// Test default in [Text] widget is [ZetaTextStyles.bodyMedium]. + expect(text1.style, text2.style); - final item1Size = tester.getSize(zetaText1); - final item2Size = tester.getSize(zetaText2); + /// Test that [ZetaTextStyles.displayLarge] has not changed. + expect(text3.style, text4.style); - expect(item1Size == item2Size, false); - expect(item2Size.width.floor(), 800); - expect( - TextStyle( - fontSize: text1.style?.fontSize, - height: text1.style?.height, - fontWeight: text1.style?.fontWeight, - ), - ZetaText.zetaBodyMedium, - ); - expect( - TextStyle( - fontSize: text2.style?.fontSize, - height: text2.style?.height, - fontWeight: text2.style?.fontWeight, - ) == - ZetaText.zetaBodyMedium, - false, - ); - expect(text1.style?.fontFamily, 'packages/zeta_flutter/IBMPlexSans'); - expect(text1.style?.color, const Color(0xFF1D1E23)); + /// Test font size of [ZetaTextStyles.bodyMedium] is correct + expect(text1.style!.fontSize, 14); + + /// Test line height of [ZetaTextStyles.bodyMedium] is correct + expect(text1.style!.height, 20 / 14); - expect(text1.style?.decoration, TextDecoration.none); - expect(text1.toPlainText(), exampleText); - expect(text2.toPlainText(), exampleText.toUpperCase()); - expect(text2.style?.decoration, TextDecoration.underline); - expect(text2.style?.fontSize, 100); - expect(text2.style?.fontStyle, FontStyle.italic); - expect(text2.style?.fontWeight, FontWeight.w500); - expect(text2.style?.color, const Color(0xFF545963)); - expect(padding1.padding, Dimensions.x2.squish); - expect(padding2.padding, EdgeInsets.zero); - expect(text3, text4); + /// Test font weight of [ZetaTextStyles.bodyMedium] is correct + expect(text1.style!.fontWeight, FontWeight.w400); }); } diff --git a/example/test/workcloud_indicator_test.dart b/example/test/workcloud_indicator_test.dart new file mode 100644 index 00000000..5d7ee833 --- /dev/null +++ b/example/test/workcloud_indicator_test.dart @@ -0,0 +1,15 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import 'test_components.dart'; + +void main() { + testWidgets('ZetaWorkcloud creates priority pill', (WidgetTester tester) async { + await tester.pumpWidget( + TestWidget( + widget: ZetaWorkcloudIndicator(index: '1'), + ), + ); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/example/widgetbook/components/accordion_widgetbook.dart b/example/widgetbook/components/accordion_widgetbook.dart new file mode 100644 index 00000000..2cd130c5 --- /dev/null +++ b/example/widgetbook/components/accordion_widgetbook.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +WidgetbookComponent accordionWidgetBook() { + return WidgetbookComponent( + isInitiallyExpanded: false, + name: 'Accordion', + useCases: [ + WidgetbookUseCase( + name: 'Accordion', + builder: (context) => TestWidget( + themeMode: ThemeMode.dark, + widget: Padding( + padding: const EdgeInsets.all(20), + child: ZetaAccordion( + child: context.knobs.boolean(label: 'Disabled') + ? null + : Column( + children: [ + ListTile(title: Text('Item One')), + ListTile(title: Text('Item two')), + ListTile(title: Text('Item three')), + ListTile(title: Text('Item four')), + ], + ), + title: context.knobs.string(label: 'Accordion Title', initialValue: 'Title'), + contained: context.knobs.boolean(label: 'Contained', initialValue: false), + isOpen: context.knobs.boolean(label: 'Open', initialValue: false), + rounded: context.knobs.boolean(label: 'Rounded', initialValue: false), + ), + ), + ), + ), + ], + ); +} diff --git a/example/widgetbook/components/avatar_widgetbook.dart b/example/widgetbook/components/avatar_widgetbook.dart new file mode 100644 index 00000000..ff73ed42 --- /dev/null +++ b/example/widgetbook/components/avatar_widgetbook.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +WidgetbookComponent avatarWidgetBook() { + return WidgetbookComponent( + isInitiallyExpanded: false, + name: 'Avatar', + useCases: [ + WidgetbookUseCase( + name: 'Image Avatar', + builder: (context) { + final Widget image = Image.network('https://i.ytimg.com/vi/KItsWUzFUOs/maxresdefault.jpg', fit: BoxFit.cover); + + return TestWidget( + themeMode: ThemeMode.dark, + widget: Column( + children: [ + Padding( + padding: const EdgeInsets.all(20), + child: ZetaAvatar.image( + image: context.knobs.boolean(label: 'Image') ? image : null, + size: context.knobs.list(label: 'Size', options: ZetaAvatarSize.values), + lowerBadge: + context.knobs.boolean(label: 'Status Badge', initialValue: false) ? ZetaIndicator.icon() : null, + borderColor: context.knobs.colorOrNull(label: 'Outline', initialValue: null), + upperBadge: context.knobs.boolean(label: 'Notification Badge', initialValue: false) + ? ZetaIndicator.notification() + : null, + ), + ), + ], + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Initials Avatar', + builder: (context) { + return TestWidget( + themeMode: ThemeMode.dark, + widget: Column( + children: [ + Padding( + padding: const EdgeInsets.all(20), + child: ZetaAvatar.initials( + backgroundColor: context.knobs.colorOrNull(label: 'Background color', initialValue: null), + initials: context.knobs.stringOrNull(label: 'Initials', initialValue: 'AB'), + size: context.knobs.list(label: 'Size', options: ZetaAvatarSize.values), + lowerBadge: context.knobs.boolean(label: 'Status badge', initialValue: false) + ? ZetaIndicator.notification() + : null, + borderColor: context.knobs.colorOrNull(label: 'Outline', initialValue: null), + upperBadge: context.knobs.boolean(label: 'Notification badge', initialValue: false) + ? ZetaIndicator.icon() + : null, + ), + ), + ], + ), + ); + }, + ), + ], + ); +} diff --git a/example/widgetbook/components/badges_widgetbook.dart b/example/widgetbook/components/badges_widgetbook.dart new file mode 100644 index 00000000..6147d1bb --- /dev/null +++ b/example/widgetbook/components/badges_widgetbook.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +WidgetbookComponent badgeWidgetBook() { + return WidgetbookComponent( + isInitiallyExpanded: false, + name: 'Badges', + useCases: [ + WidgetbookUseCase( + name: 'Status Label', + builder: (context) { + return TestWidget( + themeMode: ThemeMode.dark, + widget: Column( + children: [ + Padding( + padding: EdgeInsets.all(20), + child: ZetaStatusLabel( + label: context.knobs.string(label: 'Label', initialValue: 'Label'), + rounded: context.knobs.boolean(label: 'Rounded'), + status: context.knobs.list(label: 'Status', options: ZetaWidgetStatus.values), + customIcon: context.knobs.list( + label: 'Icon', + options: [ + ZetaIcons.star_half_round, + ZetaIcons.add_alert_round, + ZetaIcons.add_box_round, + ZetaIcons.barcode_round, + ], + labelBuilder: (value) { + if (value == ZetaIcons.star_half_round) return 'ZetaIcons.star_half_round'; + if (value == ZetaIcons.add_alert_round) return 'ZetaIcons.add_alert_round'; + if (value == ZetaIcons.add_box_round) return 'ZetaIcons.add_box_round'; + if (value == ZetaIcons.barcode_round) return 'ZetaIcons.barcode_round'; + return ''; + }, + ), + ), + ), + ], + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Priority Pill', + builder: (context) => TestWidget( + themeMode: ThemeMode.dark, + widget: Column( + children: [ + Padding( + padding: EdgeInsets.all(20), + child: ZetaPriorityPill( + index: context.knobs.int.slider(label: 'Index'), + priority: context.knobs.string(label: 'Priority', initialValue: 'Priority'), + rounded: context.knobs.boolean(label: 'Rounded'), + ), + ), + ], + ), + ), + ), + WidgetbookUseCase( + name: 'Badge', + builder: (context) => TestWidget( + themeMode: ThemeMode.dark, + widget: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.all(20), + child: ZetaBadge( + label: context.knobs.string(label: 'Label', initialValue: 'Label'), + rounded: context.knobs.boolean(label: 'Rounded'), + status: context.knobs.list(label: 'Status', options: ZetaWidgetStatus.values), + ), + ), + ], + ), + ), + ), + WidgetbookUseCase( + name: 'Indicators', + builder: (context) => TestWidget( + themeMode: ThemeMode.dark, + widget: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.all(20), + child: ZetaIndicator( + type: context.knobs.list(label: 'Type', options: ZetaIndicatorType.values), + icon: context.knobs.list( + label: 'Icon', + options: [ + Icon(ZetaIcons.star_half_round), + Icon(ZetaIcons.add_alert_round), + Icon(ZetaIcons.add_box_round), + Icon(ZetaIcons.barcode_round), + ], + labelBuilder: (value) { + if (value?.icon == ZetaIcons.star_half_round) return 'ZetaIcons.star_half_round'; + if (value?.icon == ZetaIcons.add_alert_round) return 'ZetaIcons.add_alert_round'; + if (value?.icon == ZetaIcons.add_box_round) return 'ZetaIcons.add_box_round'; + if (value?.icon == ZetaIcons.barcode_round) return 'ZetaIcons.barcode_round'; + return ''; + }, + ), + inverse: context.knobs.boolean(label: 'Inverse Border'), + size: context.knobs.list(label: 'Size', options: ZetaWidgetSize.values), + value: context.knobs.int.slider(label: 'Value'), + ), + ), + ], + ), + ), + ), + WidgetbookUseCase( + name: 'Tags', + builder: (context) => TestWidget( + themeMode: ThemeMode.dark, + widget: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.all(20), + child: ZetaTag( + label: context.knobs.string(label: 'Label', initialValue: 'Tag'), + rounded: context.knobs.boolean(label: 'Rounded'), + direction: context.knobs.list(label: 'Direction', options: ZetaTagDirection.values), + ), + ) + ], + ), + ), + ), + WidgetbookUseCase( + name: 'Workcloud Indicators', + builder: (context) => TestWidget( + themeMode: ThemeMode.dark, + widget: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.all(20), + child: ZetaWorkcloudIndicator( + index: context.knobs.string(label: 'Index', initialValue: '1'), + label: context.knobs.string(label: 'Label', initialValue: 'Label'), + prioritySize: context.knobs.list(label: 'Size', options: ZetaWidgetSize.values), + priorityType: context.knobs.list(label: 'Type', options: ZetaWorkcloudIndicatorType.values), + icon: context.knobs.listOrNull( + label: 'Icon', + options: [ + ZetaIcons.star_half_round, + ZetaIcons.add_alert_round, + ZetaIcons.add_box_round, + ZetaIcons.barcode_round, + ], + initialOption: null, + labelBuilder: (value) { + if (value == ZetaIcons.star_half_round) return 'ZetaIcons.star_half_round'; + if (value == ZetaIcons.add_alert_round) return 'ZetaIcons.add_alert_round'; + if (value == ZetaIcons.add_box_round) return 'ZetaIcons.add_box_round'; + if (value == ZetaIcons.barcode_round) return 'ZetaIcons.barcode_round'; + return ''; + }, + )), + ) + ], + ), + ], + ), + ), + ), + ], + ); +} diff --git a/example/widgetbook/components/banner_widgetbook.dart b/example/widgetbook/components/banner_widgetbook.dart new file mode 100644 index 00000000..b6dceb62 --- /dev/null +++ b/example/widgetbook/components/banner_widgetbook.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +WidgetbookComponent BannerWidgetBook() { + return WidgetbookComponent( + isInitiallyExpanded: false, + name: 'Banners', + useCases: [ + WidgetbookUseCase( + name: 'System Banner', + builder: (context) => TestWidget( + widget: Padding( + padding: EdgeInsets.all(20), + child: Column( + children: [ + ZetaSystemBanner( + context: context, + title: context.knobs.string(label: 'Title', initialValue: 'Banner Title'), + type: context.knobs.list(label: 'Type', options: ZetaSystemBannerStatus.values), + leadingIcon: context.knobs.list( + label: 'Icon', + options: [ + ZetaIcons.star_half_round, + ZetaIcons.add_alert_round, + ZetaIcons.add_box_round, + ZetaIcons.barcode_round, + ], + labelBuilder: (value) { + if (value == ZetaIcons.star_half_round) return 'ZetaIcons.star_half_round'; + if (value == ZetaIcons.add_alert_round) return 'ZetaIcons.add_alert_round'; + if (value == ZetaIcons.add_box_round) return 'ZetaIcons.add_box_round'; + if (value == ZetaIcons.barcode_round) return 'ZetaIcons.barcode_round'; + return ''; + }, + ), + titleStart: context.knobs.boolean(label: 'Center title'), + trailing: context.knobs.boolean(label: 'Trailing Icon') + ? IconButton(icon: Icon(ZetaIcons.chevron_right_round), onPressed: () {}) + : null, + ), + ], + ), + ), + ), + ), + WidgetbookUseCase( + name: 'In Page Banner', + builder: (context) => TestWidget( + themeMode: ThemeMode.dark, + widget: Padding( + padding: EdgeInsets.all(20), + child: Column( + children: [ + ZetaInPageBanner( + content: Text( + context.knobs.string( + label: 'content', + initialValue: + 'Lorem ipsum dolor sit amet, conse ctetur cididunt ut labore et do lore magna aliqua.', + ), + ), + status: context.knobs.list(label: 'Severity', options: ZetaWidgetStatus.values), + onClose: context.knobs.boolean(label: 'Show Close icon') ? () {} : null, + title: context.knobs.string(label: 'Title', initialValue: 'Title'), + rounded: context.knobs.boolean(label: 'Rounded'), + actions: () { + final x = context.knobs.list(label: 'Show Buttons', options: [0, 1, 2]); + + if (x == 1) { + return [ + ZetaButton(label: 'Button 1', onPressed: () {}), + ]; + } + if (x == 2) { + return [ + ZetaButton(label: 'Button 1', onPressed: () {}), + ZetaButton(label: 'Button 2', onPressed: () {}) + ]; + } + return []; + }(), + customIcon: context.knobs.list( + label: 'Icon', + options: [ + ZetaIcons.star_half_round, + ZetaIcons.add_alert_round, + ZetaIcons.add_box_round, + ZetaIcons.barcode_round, + ], + labelBuilder: (value) { + if (value == ZetaIcons.star_half_round) return 'ZetaIcons.star_half_round'; + if (value == ZetaIcons.add_alert_round) return 'ZetaIcons.add_alert_round'; + if (value == ZetaIcons.add_box_round) return 'ZetaIcons.add_box_round'; + if (value == ZetaIcons.barcode_round) return 'ZetaIcons.barcode_round'; + return ''; + }, + ), + ) + ], + ), + ), + ), + ), + ], + ); +} diff --git a/example/widgetbook/components/bottom_sheet_widgetbook.dart b/example/widgetbook/components/bottom_sheet_widgetbook.dart new file mode 100644 index 00000000..2bb95e37 --- /dev/null +++ b/example/widgetbook/components/bottom_sheet_widgetbook.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +WidgetbookComponent bottomSheetWidgetBook() { + return WidgetbookComponent( + isInitiallyExpanded: false, + name: 'Bottom Sheet', + useCases: [ + WidgetbookUseCase( + name: 'Content', + builder: (context) => TestWidget( + themeMode: ThemeMode.dark, + widget: Padding( + padding: const EdgeInsets.all(20), + child: _bottomSheet(context), + ), + ), + ), + WidgetbookUseCase( + name: 'Live', + builder: (context) { + final sheet = _bottomSheet(context); + return TestWidget( + themeMode: ThemeMode.dark, + widget: Padding( + padding: const EdgeInsets.all(20), + child: ElevatedButton( + child: Text('Open'), + onPressed: () { + showModalBottomSheet(context: context, builder: (_) => sheet); + }, + ), + ), + ); + }, + ), + ], + ); +} + +ZetaBottomSheet _bottomSheet(BuildContext context) { + return ZetaBottomSheet( + centerTitle: context.knobs.boolean(label: 'Center title', initialValue: true), + title: context.knobs.string(label: 'Title', initialValue: 'Title'), + body: Wrap( + spacing: 12, + runSpacing: 12, + children: List.generate( + 6, + (index) => ZetaMenuItem( + type: context.knobs.boolean(label: 'Grid') ? ZetaMenuItemType.vertical : ZetaMenuItemType.horizontal, + leading: context.knobs.boolean(label: 'Leading Icon') ? Icon(ZetaIcons.star_round) : null, + trailing: context.knobs.boolean(label: 'Trailing Icon') ? Icon(ZetaIcons.chevron_right_round) : null, + label: Text('Menu Item'), + onTap: context.knobs.boolean(label: 'Disabled') ? null : () {}, + ), + ), + ), + ); +} diff --git a/example/widgetbook/components/button_widgetbook.dart b/example/widgetbook/components/button_widgetbook.dart new file mode 100644 index 00000000..2f3f3655 --- /dev/null +++ b/example/widgetbook/components/button_widgetbook.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +WidgetbookComponent buttonWidgetBook() { + return WidgetbookComponent( + name: 'Button', + isInitiallyExpanded: false, + useCases: [ + WidgetbookUseCase( + name: 'Button', + builder: (context) { + return TestWidget( + widget: Padding( + padding: EdgeInsets.all(20), + child: ZetaButton( + label: context.knobs.string(label: 'Text', initialValue: 'Button'), + onPressed: context.knobs.boolean(label: 'Disabled') ? null : () {}, + borderType: context.knobs.boolean(label: 'Rounded') ? ZetaWidgetBorder.rounded : ZetaWidgetBorder.sharp, + size: context.knobs.list(label: 'Size', options: ZetaWidgetSize.values), + type: context.knobs.list(label: 'Type', options: ZetaButtonType.values), + ), + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Floating Action Button', + builder: (context) => TestWidget( + widget: Padding(padding: EdgeInsets.all(20), child: FabWidget(context)), + ), + ) + ], + ); +} + +class FabWidget extends StatefulWidget { + const FabWidget(this.c); + final BuildContext c; + + @override + State createState() => _FabWidgetState(); +} + +class _FabWidgetState extends State { + late ScrollController _scrollController; + + @override + void initState() { + super.initState(); + _scrollController = ScrollController(); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext _) { + return SizedBox( + height: MediaQuery.of(context).size.height * 0.9, + child: Scaffold( + body: ListView.builder( + itemCount: MediaQuery.of(context).size.height.toInt(), + controller: _scrollController, + itemBuilder: (context, index) { + return Text("$index"); + }, + ), + floatingActionButton: ZetaFAB( + scrollController: _scrollController, + label: widget.c.knobs.string(label: 'Label', initialValue: 'Floating Action Button'), + onPressed: widget.c.knobs.boolean(label: 'Disabled') ? null : () {}, + icon: widget.c.knobs.list( + label: 'Icon', + options: [ + ZetaIcons.star_half_round, + ZetaIcons.add_alert_round, + ZetaIcons.add_box_round, + ZetaIcons.barcode_round, + ], + labelBuilder: (value) { + if (value == ZetaIcons.star_half_round) return 'ZetaIcons.star_half_round'; + if (value == ZetaIcons.add_alert_round) return 'ZetaIcons.add_alert_round'; + if (value == ZetaIcons.add_box_round) return 'ZetaIcons.add_box_round'; + if (value == ZetaIcons.barcode_round) return 'ZetaIcons.barcode_round'; + return ''; + }, + ), + shape: widget.c.knobs.list(label: 'Shape', options: ZetaWidgetBorder.values), + size: widget.c.knobs.list(label: 'Shape', options: ZetaFabSize.values), + type: widget.c.knobs.list(label: 'Shape', options: ZetaFabType.values), + ), + ), + ); + } +} diff --git a/example/widgetbook/components/checkbox_widgetbook.dart b/example/widgetbook/components/checkbox_widgetbook.dart index 6909ee4e..5883b6d0 100644 --- a/example/widgetbook/components/checkbox_widgetbook.dart +++ b/example/widgetbook/components/checkbox_widgetbook.dart @@ -1,35 +1,29 @@ import 'package:flutter/material.dart'; import 'package:widgetbook/widgetbook.dart'; -import 'package:zeta_example/pages/checkbox_example.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; WidgetbookComponent checkboxWidgetBook() { return WidgetbookComponent( + isInitiallyExpanded: false, name: 'Checkbox', useCases: [ WidgetbookUseCase( - name: 'Checkbox (sharp)', - builder: (context) => SingleChildScrollView( - child: Padding( - padding: EdgeInsets.only(top: 10), - child: getCheckBoxRow(isEnabled: true), - ), - ), - ), - WidgetbookUseCase( - name: 'Checkbox (rounded)', - builder: (context) => SingleChildScrollView( - child: Padding( - padding: EdgeInsets.only(top: 10), - child: getCheckBoxRow(isEnabled: true, isSharp: false), - ), - ), - ), - WidgetbookUseCase( - name: 'Checkbox disabled (rounded)', - builder: (context) => SingleChildScrollView( - child: Padding( - padding: EdgeInsets.only(top: 10), - child: getCheckBoxRow(isEnabled: false, isSharp: false), + name: 'Checkbox', + builder: (context) => TestWidget( + widget: Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 10), + child: ZetaCheckbox( + value: context.knobs.booleanOrNull(label: 'Checked'), + onChanged: context.knobs.boolean(label: 'Enabled', initialValue: true) ? (_) {} : null, + rounded: context.knobs.boolean(label: 'Rounded'), + label: context.knobs.string(label: 'Label', initialValue: 'Checkbox'), + ), + ), + ], ), ), ), diff --git a/example/widgetbook/components/chip_widgetbook.dart b/example/widgetbook/components/chip_widgetbook.dart new file mode 100644 index 00000000..072617dc --- /dev/null +++ b/example/widgetbook/components/chip_widgetbook.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +WidgetbookComponent chipWidgetBook() { + return WidgetbookComponent( + isInitiallyExpanded: false, + name: 'Chip', + useCases: [ + WidgetbookUseCase( + name: 'Input Chip', + builder: (context) { + final trailing = context.knobs.listOrNull( + label: 'Icon', + options: [ + ZetaIcons.star_half_round, + ZetaIcons.add_alert_round, + ZetaIcons.add_box_round, + ZetaIcons.barcode_round, + ], + initialOption: null, + labelBuilder: (value) { + if (value == ZetaIcons.star_half_round) return 'ZetaIcons.star_half_round'; + if (value == ZetaIcons.add_alert_round) return 'ZetaIcons.add_alert_round'; + if (value == ZetaIcons.add_box_round) return 'ZetaIcons.add_box_round'; + if (value == ZetaIcons.barcode_round) return 'ZetaIcons.barcode_round'; + return ''; + }, + ); + return TestWidget( + widget: ZetaInputChip( + label: context.knobs.string(label: 'Label', initialValue: 'Label'), + leading: context.knobs.boolean(label: 'Avatar') + ? ZetaAvatar( + initials: 'AZ', + size: ZetaAvatarSize.xs, + ) + : null, + rounded: context.knobs.boolean(label: 'Rounded'), + trailing: trailing != null ? Icon(trailing) : null, + ), + ); + }, + ), + WidgetbookUseCase( + name: 'Filter Chip', + builder: (context) => TestWidget( + widget: Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + ZetaFilterChip( + label: context.knobs.string(label: 'Label', initialValue: 'Label'), + rounded: context.knobs.boolean(label: 'Rounded'), + selected: context.knobs.boolean(label: 'Selected'), + ) + ], + ), + ), + ), + ), + WidgetbookUseCase( + name: 'Assist Chip', + builder: (context) => TestWidget( + widget: Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + ZetaAssistChip( + label: context.knobs.string(label: 'Label', initialValue: 'Label'), + rounded: context.knobs.boolean(label: 'Rounded'), + leading: context.knobs.boolean(label: 'Icon') ? Icon(ZetaIcons.star_round) : null, + ) + ], + ), + ), + ), + ), + ], + ); +} diff --git a/example/widgetbook/components/color_widgetbook.dart b/example/widgetbook/components/color_widgetbook.dart deleted file mode 100644 index 3c4ef757..00000000 --- a/example/widgetbook/components/color_widgetbook.dart +++ /dev/null @@ -1,129 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:widgetbook/widgetbook.dart'; -import 'package:zeta_example/pages/color_example.dart'; -import 'package:zeta_flutter/zeta_flutter.dart'; - -WidgetbookComponent colorWidgetBook() { - return WidgetbookComponent( - name: 'Colors', - useCases: [ - WidgetbookUseCase( - name: 'Colors', - builder: (BuildContext context) { - final colors = Zeta.of(context).colors; - - return LayoutBuilder( - builder: (context, constraints) { - final Map swatches = { - 'Blue': colors.blue, - 'Green': colors.green, - 'Red': colors.red, - 'Orange': colors.orange, - 'Purple': colors.purple, - 'Yellow': colors.yellow, - 'Teal': colors.teal, - 'Pink': colors.pink, - 'Grey Warm': colors.warm, - 'Grey Cool': colors.cool, - }; - final Map textIcon = { - 'textDefault': colors.textDefault, - 'textSubtle': colors.textSubtle, - 'textDisabled': colors.textDisabled, - 'textInverse': colors.textInverse, - }; - final Map border = { - 'borderDefault': colors.borderDefault, - 'borderSubtle': colors.borderSubtle, - 'borderDisabled': colors.borderDisabled, - 'borderSelected': colors.borderSelected, - }; - final Map links = { - 'linkDefault': colors.link, - 'linkVisited': colors.linkVisited, - }; - final Map backdrop = { - 'surfacePrimary': colors.surfacePrimary, - 'surfaceDisabled': colors.surfaceDisabled, - 'surfaceHovered': colors.surfaceHovered, - 'surfaceSecondary': colors.surfaceSecondary, - 'surfaceTertiary': colors.surfaceTertiary, - 'surfaceSelectedHovered': colors.surfaceSelectedHovered, - 'surfaceSelected': colors.surfaceSelected, - }; - - final Map alerts = { - 'positive': colors.positive, - 'negative': colors.negative, - 'warning': colors.warning, - 'info': colors.info, - }; - - return ZetaProvider( - builder: (context3, _, __) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: Dimensions.l), - child: SingleChildScrollView( - child: Column( - children: [ - const SizedBox(height: Dimensions.l), - MyRow(children: textIcon, title: 'Text and icon styles'), - MyRow(children: border, title: 'Border styles'), - MyRow(children: links, title: 'Links'), - MyRow(children: backdrop, title: 'Backdrop colors'), - MyRow(children: alerts, title: 'Alert colors'), - Row(children: [ZetaText.displayMedium('Full color swatches')]).squish(Dimensions.x8), - ...swatches.entries.map( - (value) { - return Row( - children: List.generate(10, (index) => 100 - (10 * index)).map( - (e) { - return Expanded( - child: Container( - height: constraints.maxWidth / 10, - color: value.value[e], - child: FittedBox( - fit: BoxFit.scaleDown, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - DefaultTextStyle( - style: ZetaText.zetaBodyMedium.copyWith( - color: calculateTextColor(value.value[e] ?? Colors.white), - ), - child: Column( - children: [ - Text('${value.key.toLowerCase().replaceAll(' ', '')}-$e'), - Text( - value.value[e] - .toString() - .replaceAll('Color(0xff', '#') - .substring(0, 7), - ), - ], - ), - ), - ], - ), - ), - ), - ); - }, - ).toList(), - ); - }, - ), - const SizedBox(height: Dimensions.l), - ], - ), - ), - ); - }, - ); - }, - ); - }, - ) - ], - ); -} diff --git a/example/widgetbook/components/grid_widgetbook.dart b/example/widgetbook/components/grid_widgetbook.dart deleted file mode 100644 index 72409360..00000000 --- a/example/widgetbook/components/grid_widgetbook.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:widgetbook/widgetbook.dart' hide DeviceType; -import 'package:zeta_example/pages/grid_example.dart'; -import 'package:zeta_flutter/zeta_flutter.dart'; - -WidgetbookComponent gridWidgetBook() { - return WidgetbookComponent( - name: 'Grid', - useCases: [ - WidgetbookUseCase( - name: 'Basic Grid', - builder: (context) => SingleChildScrollView( - child: ZetaGrid( - col: context.knobs.double.slider(label: 'col', min: 2, max: 16, divisions: 7, initialValue: 12), - noGaps: context.knobs.boolean(label: 'No Gaps'), - children: List.generate(16, (index) => const GridItem()), - ), - ), - ), - WidgetbookUseCase( - name: 'Asymmetrical Grid', - builder: (context) => SingleChildScrollView( - child: ZetaGrid( - asymmetricWeight: context.knobs.double.slider(label: 'Asymmetric', min: 1, max: 11, divisions: 10).toInt(), - noGaps: context.knobs.boolean(label: 'No Gaps'), - children: List.generate(16, (index) => const GridItem()), - ), - ), - ), - WidgetbookUseCase( - name: 'Hybrid Grid', - builder: (context) => LayoutBuilder( - builder: (context, constraints) { - final double initialSize = constraints.maxWidth * 0.01; - final double maxSize = (constraints.maxWidth - (context.knobs.boolean(label: 'No Gaps') ? 0 : 40)) * 0.2; - - return ZetaGrid( - noGaps: context.knobs.boolean(label: 'No Gaps'), - col: 7, - hybrid: true, - children: [ - GridItem( - width: context.knobs.double - .slider(label: 'Fixed width 1', min: 1, max: maxSize, initialValue: initialSize), - label: 'Fixed 1', - ), - Flexible( - fit: FlexFit.tight, - flex: context.knobs.double.slider(label: 'Flex width 1', min: 0, max: 5, initialValue: 1).toInt(), - child: const GridItem(label: 'Flex 1'), - ), - GridItem( - width: context.knobs.double - .slider(label: 'Fixed width 2', min: 1, max: maxSize, initialValue: initialSize), - label: 'Fixed 2', - ), - Flexible( - fit: FlexFit.tight, - flex: context.knobs.double.slider(label: 'Flex width 2', min: 0, max: 5, initialValue: 1).toInt(), - child: const GridItem(label: 'Flex 2'), - ), - GridItem( - width: context.knobs.double - .slider(label: 'Fixed width 3', min: 1, max: maxSize, initialValue: initialSize), - label: 'Fixed 3', - ), - Flexible( - fit: FlexFit.tight, - flex: context.knobs.double.slider(label: 'Flex width 3', min: 0, max: 5, initialValue: 1).toInt(), - child: const GridItem(label: 'Flex 3'), - ), - GridItem( - width: context.knobs.double - .slider(label: 'Fixed width 4', min: 1, max: maxSize, initialValue: initialSize), - label: 'Fixed 4', - ), - ], - ); - }, - ), - ) - ], - ); -} diff --git a/example/widgetbook/components/password_input_widgetbook.dart b/example/widgetbook/components/password_input_widgetbook.dart new file mode 100644 index 00000000..fdfe3c5b --- /dev/null +++ b/example/widgetbook/components/password_input_widgetbook.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +WidgetbookComponent passwordInputWidgetBook() { + return WidgetbookComponent( + name: 'Password Input', + isInitiallyExpanded: false, + useCases: [ + WidgetbookUseCase( + name: 'Password Input', + builder: (context) { + return _Password(); + }, + ), + ], + ); +} + +class _Password extends StatefulWidget { + const _Password(); + + @override + State<_Password> createState() => _PasswordState(); +} + +class _PasswordState extends State<_Password> { + final _passwordController = TextEditingController(); + final _formKey = GlobalKey(); + + @override + Widget build(BuildContext _) { + final enableValidation = context.knobs.boolean(label: 'Enable validation', initialValue: false); + final validationString = context.knobs.string(label: 'Error label', initialValue: 'Incorrect'); + return Form( + key: _formKey, + child: TestWidget( + widget: Padding( + padding: EdgeInsets.all(ZetaSpacing.x5), + child: Column( + children: [ + ConstrainedBox( + constraints: BoxConstraints(maxWidth: 328), + child: ZetaPasswordInput( + rounded: context.knobs.boolean(label: 'Rounded'), + enabled: context.knobs.boolean(label: 'Enabled', initialValue: true), + obscureText: context.knobs.boolean(label: 'Obscure text', initialValue: true), + size: context.knobs.list(label: 'Size', options: ZetaWidgetSize.values), + footerIcon: context.knobs.listOrNull( + label: 'Icon', + options: [ + ZetaIcons.star_half_round, + ZetaIcons.add_alert_round, + ZetaIcons.add_box_round, + ZetaIcons.barcode_round, + ], + labelBuilder: (value) { + if (value == ZetaIcons.star_half_round) return 'ZetaIcons.star_half_round'; + if (value == ZetaIcons.add_alert_round) return 'ZetaIcons.add_alert_round'; + if (value == ZetaIcons.add_box_round) return 'ZetaIcons.add_box_round'; + if (value == ZetaIcons.barcode_round) return 'ZetaIcons.barcode_round'; + return ''; + }, + initialOption: null, + ), + footerText: context.knobs.string(label: 'Footer Text'), + hintText: context.knobs.string(label: 'Hint text'), + label: context.knobs.string(label: 'Label'), + onChanged: (_) => _formKey.currentState?.validate(), + validator: (_) => enableValidation ? validationString : null, + controller: _passwordController, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/example/widgetbook/components/spacing_widgetbook.dart b/example/widgetbook/components/spacing_widgetbook.dart deleted file mode 100644 index 95117c91..00000000 --- a/example/widgetbook/components/spacing_widgetbook.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:widgetbook/widgetbook.dart' hide DeviceType; -import 'package:zeta_example/pages/spacing_example.dart'; -import 'package:zeta_flutter/zeta_flutter.dart'; - -const List typeSelector = [ - ZetaSpacingType.square, - ZetaSpacingType.squish, - ZetaSpacingType.inline, - ZetaSpacingType.inlineStart, - ZetaSpacingType.inlineEnd, - ZetaSpacingType.stack, -]; -WidgetbookComponent spacingWidgetbook() { - final tShirtSizes = { - 'xxs': Dimensions.xxs, - 'xs': Dimensions.xs, - 's': Dimensions.s, - 'm': Dimensions.m, - 'l': Dimensions.l, - 'xl': Dimensions.xl, - 'xxl': Dimensions.xxl, - 'xxxl': Dimensions.xxxl, - }; - return WidgetbookComponent( - name: 'Spacing', - useCases: [ - WidgetbookUseCase( - name: 'Defined numbers', - builder: (context) => SingleChildScrollView( - child: Row( - children: [ - Expanded( - child: ColoredBox( - color: const Color(0xFFcce2fa), - child: ZetaSpacing( - const SpacingItem(), - size: context.knobs.list( - label: 'Size', - labelBuilder: (p0) => 'x${p0 ~/ 4}', - options: const [ - Dimensions.x0, - Dimensions.x1, - Dimensions.x2, - Dimensions.x3, - Dimensions.x3, - Dimensions.x4, - Dimensions.x5, - Dimensions.x6, - Dimensions.x7, - Dimensions.x8, - Dimensions.x9, - Dimensions.x10, - Dimensions.x12, - Dimensions.x16, - Dimensions.x20, - Dimensions.x24, - ], - ), - type: context.knobs.list(label: 'Spacing Type', options: typeSelector), - ), - ), - ), - ], - ), - ), - ), - WidgetbookUseCase( - name: 'T-Shirt Sizes', - builder: (context) => SingleChildScrollView( - child: Row( - children: [ - Expanded( - child: ColoredBox( - color: const Color(0xFFcce2fa), - child: ZetaSpacing( - const SpacingItem(), - size: context.knobs.list( - label: 'Size', - labelBuilder: (p0) => tShirtSizes.entries.firstWhere((element) => element.value == p0).key, - options: tShirtSizes.values.toList(), - ), - type: context.knobs.list(label: 'Spacing Type', options: typeSelector), - ), - ), - ), - ], - ), - ), - ), - WidgetbookUseCase( - name: 'Numbers', - builder: (context) => SingleChildScrollView( - child: Row( - children: [ - Expanded( - child: ColoredBox( - color: const Color(0xFFcce2fa), - child: ZetaSpacing( - const SpacingItem(), - size: (context.knobs.double.slider( - label: 'Size (rounding to nearest even int)', - min: 0, - max: 96, - initialValue: 0, - ) ~/ - 2) * - 2, - type: context.knobs.list(label: 'Spacing Type', options: typeSelector), - ), - ), - ), - ], - ), - ), - ), - ], - ); -} diff --git a/example/widgetbook/components/typography_widgetbook.dart b/example/widgetbook/components/typography_widgetbook.dart deleted file mode 100644 index d7f5949b..00000000 --- a/example/widgetbook/components/typography_widgetbook.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:widgetbook/widgetbook.dart'; -import 'package:zeta_example/pages/typography_example.dart'; -import 'package:zeta_flutter/zeta_flutter.dart'; - -WidgetbookComponent textWidgetBook() { - final dedicatedSizes = { - 'Body small': ZetaText.zetaBodySmall, - 'Body medium': ZetaText.zetaBodyMedium, - 'Body large': ZetaText.zetaBodyLarge, - 'Headline small': ZetaText.zetaHeadingSmall, - 'Headline medium': ZetaText.zetaHeadingMedium, - 'Headline large': ZetaText.zetaHeadingLarge, - 'Display small': ZetaText.zetaDisplaySmall, - 'Display medium': ZetaText.zetaDisplayMedium, - 'Display large': ZetaText.zetaDisplayLarge, - }; - return WidgetbookComponent( - name: 'Typography', - useCases: [ - WidgetbookUseCase( - name: 'Tokens', - builder: (context) => const _TextWrapper(), - ), - WidgetbookUseCase( - name: 'Universal sizes', - builder: (context) => Container( - color: Theme.of(context).colorScheme.background, - padding: const EdgeInsets.all(Dimensions.l), - child: ZetaText( - exampleText, - fontSize: context.knobs.list( - label: 'Sizes', - labelBuilder: (p0) => p0 == 14 ? 'x3_5' : 'x${p0! ~/ 4}', - options: const [ - Dimensions.x3, - Dimensions.x3_5, - Dimensions.x4, - Dimensions.x5, - Dimensions.x6, - Dimensions.x7, - Dimensions.x8, - Dimensions.x9, - Dimensions.x10, - Dimensions.x11, - Dimensions.x12, - Dimensions.x13, - ], - ), - ), - ), - ), - WidgetbookUseCase( - name: 'Dedicated sizes', - builder: (context) => Container( - color: Theme.of(context).colorScheme.background, - padding: const EdgeInsets.all(Dimensions.l), - child: ZetaText( - exampleText, - style: context.knobs.list( - label: 'Sizes', - labelBuilder: (p0) => dedicatedSizes.entries.firstWhere((element) => element.value == p0).key, - options: dedicatedSizes.values.toList(), - ), - ), - ), - ), - ], - ); -} - -class _TextWrapper extends StatelessWidget { - const _TextWrapper(); - - @override - Widget build(BuildContext context) { - final colors = Zeta.of(context).colors; - return Container( - padding: const EdgeInsets.all(Dimensions.l), - child: ZetaText( - context.knobs.string(label: 'Input text', initialValue: exampleText.split(',').first), - decoration: context.knobs.boolean(label: 'Underline') ? TextDecoration.underline : null, - first: context.knobs.boolean(label: 'First'), - last: context.knobs.boolean(label: 'Last'), - fontStyle: context.knobs.boolean(label: 'Italic') ? FontStyle.italic : null, - fontWeight: context.knobs.list( - label: 'Font Weight', - labelBuilder: (p0) => p0 == FontWeight.w400 - ? 'Default' - : p0 == FontWeight.w300 - ? 'Light' - : 'Medium', - options: const [FontWeight.w400, FontWeight.w300, FontWeight.w500], - ), - resetHeight: context.knobs.boolean(label: 'Reset height'), - textColor: context.knobs.list( - label: 'Text color', - labelBuilder: (p0) => p0?.value == colors.textDefault.value ? 'Default' : 'Subtle', - options: [colors.textDefault, colors.textSubtle], - ), - textDirection: context.knobs.list( - label: 'Text direction', - options: const [ - TextDirection.ltr, - TextDirection.rtl, - ], - ), - upperCase: context.knobs.boolean(label: 'Upper case'), - maxWidth: context.knobs.double.slider(label: 'Width', initialValue: 66, max: 100, min: 10, divisions: 90), - fontSize: context.knobs.double.slider(label: 'Font size', initialValue: 12, divisions: 42, min: 12, max: 96), - ), - ); - } -} diff --git a/example/widgetbook/theme/color_widgetbook.dart b/example/widgetbook/theme/color_widgetbook.dart new file mode 100644 index 00000000..8beaf9cb --- /dev/null +++ b/example/widgetbook/theme/color_widgetbook.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_example/pages/theme/color_example.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +WidgetbookComponent colorWidgetBook() { + return WidgetbookComponent( + name: 'Colors', + useCases: [ + WidgetbookUseCase( + name: 'Light Mode', + builder: (BuildContext context) { + return ZetaProvider( + initialThemeMode: ThemeMode.light, + builder: (_, __, ___) => ColorBody(), + ); + }, + ), + WidgetbookUseCase( + name: 'Dark Mode', + builder: (BuildContext context) { + return ZetaProvider( + initialThemeMode: ThemeMode.dark, + builder: (_, __, ___) => ColorBody(), + ); + }, + ) + ], + ); +} + +class ColorBody extends StatelessWidget { + const ColorBody({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return LayoutBuilder(builder: (_, constraints) { + final colors = Zeta.of(context).colors; + + final Map swatches = { + 'Blue': colors.blue, + 'Green': colors.green, + 'Red': colors.red, + 'Orange': colors.orange, + 'Purple': colors.purple, + 'Yellow': colors.yellow, + 'Teal': colors.teal, + 'Pink': colors.pink, + 'Grey Warm': colors.warm, + 'Grey Cool': colors.cool, + }; + final Map textIcon = { + 'textDefault': colors.textDefault, + 'textSubtle': colors.textSubtle, + 'textDisabled': colors.textDisabled, + 'textInverse': colors.textInverse, + }; + final Map border = { + 'borderDefault': colors.borderDefault, + 'borderSubtle': colors.borderSubtle, + 'borderDisabled': colors.borderDisabled, + 'borderSelected': colors.borderSelected, + }; + final Map links = { + 'linkDefault': colors.link, + 'linkVisited': colors.linkVisited, + }; + final Map backdrop = { + 'surfacePrimary': colors.surfacePrimary, + 'surfaceDisabled': colors.surfaceDisabled, + 'surfaceHovered': colors.surfaceHovered, + 'surfaceSecondary': colors.surfaceSecondary, + 'surfaceTertiary': colors.surfaceTertiary, + 'surfaceSelectedHovered': colors.surfaceSelectedHovered, + 'surfaceSelected': colors.surfaceSelected, + }; + + final Map alerts = { + 'positive': colors.positive, + 'negative': colors.negative, + 'warning': colors.warning, + 'info': colors.info, + }; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: ZetaSpacing.l), + child: SingleChildScrollView( + child: Column( + children: [ + const SizedBox(height: ZetaSpacing.l), + MyRow(children: textIcon, title: 'Text and icon styles'), + MyRow(children: border, title: 'Border styles'), + MyRow(children: links, title: 'Links'), + MyRow(children: backdrop, title: 'Backdrop colors'), + MyRow(children: alerts, title: 'Alert colors'), + Row(children: [Text('Full color swatches', style: ZetaTextStyles.displayMedium)]) + .paddingVertical(ZetaSpacing.x8), + ...swatches.entries.map( + (value) { + return Row( + children: List.generate(10, (index) => 100 - (10 * index)).map( + (e) { + return Expanded( + child: Container( + height: constraints.maxWidth / 10, + color: value.value[e], + child: FittedBox( + fit: BoxFit.scaleDown, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + DefaultTextStyle( + style: ZetaTextStyles.bodyMedium.copyWith( + color: calculateTextColor(value.value[e] ?? Colors.white), + ), + child: Column( + children: [ + Text('${value.key.toLowerCase().replaceAll(' ', '')}-$e'), + Text( + value.value[e].toString().replaceAll('Color(0xff', '#').substring(0, 7), + ), + ], + ), + ), + ], + ), + ), + ), + ); + }, + ).toList(), + ); + }, + ), + const SizedBox(height: ZetaSpacing.l), + ], + ), + ), + ); + }); + } +} diff --git a/example/widgetbook/theme/typography_widgetbook.dart b/example/widgetbook/theme/typography_widgetbook.dart new file mode 100644 index 00000000..6eb20fe5 --- /dev/null +++ b/example/widgetbook/theme/typography_widgetbook.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; + +import 'package:zeta_flutter/zeta_flutter.dart'; + +WidgetbookComponent textWidgetBook() { + final dedicatedSizes = { + 'Display large': ZetaTextStyles.displayLarge, + 'Display medium': ZetaTextStyles.displayMedium, + 'Display small': ZetaTextStyles.displaySmall, + 'Heading 1': ZetaTextStyles.heading1, + 'Heading 2': ZetaTextStyles.heading2, + 'Heading 3': ZetaTextStyles.heading3, + 'Title large': ZetaTextStyles.titleLarge, + 'Title medium': ZetaTextStyles.titleMedium, + 'Title small': ZetaTextStyles.titleSmall, + 'Body large': ZetaTextStyles.bodyLarge, + 'Body medium': ZetaTextStyles.bodyMedium, + 'Body small': ZetaTextStyles.bodySmall, + 'Label large': ZetaTextStyles.labelLarge, + 'Label medium': ZetaTextStyles.labelMedium, + 'Label small': ZetaTextStyles.labelSmall, + 'Label indicator': ZetaTextStyles.labelIndicator, + 'Label tiny': ZetaTextStyles.labelTiny, + }; + return WidgetbookComponent( + name: 'Typography', + useCases: [ + WidgetbookUseCase( + name: 'Text styles', + builder: (context) => Container( + color: Theme.of(context).colorScheme.background, + padding: const EdgeInsets.all(ZetaSpacing.l), + child: Text( + context.knobs.string(label: 'Text', initialValue: 'The quick brown fox jumps over the lazy dog.'), + style: context.knobs.list( + label: 'Sizes', + labelBuilder: (p0) => dedicatedSizes.entries.firstWhere((element) => element.value == p0).key, + options: dedicatedSizes.values.toList(), + ), + ), + ), + ), + ], + ); +} diff --git a/example/widgetbook/widgetbook.dart b/example/widgetbook/widgetbook.dart index df609965..507d869a 100644 --- a/example/widgetbook/widgetbook.dart +++ b/example/widgetbook/widgetbook.dart @@ -2,11 +2,17 @@ import 'package:flutter/material.dart'; import 'package:widgetbook/widgetbook.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; +import 'components/accordion_widgetbook.dart'; +import 'components/avatar_widgetbook.dart'; +import 'components/badges_widgetbook.dart'; +import 'components/bottom_sheet_widgetbook.dart'; +import 'components/button_widgetbook.dart'; import 'components/checkbox_widgetbook.dart'; -import 'components/color_widgetbook.dart'; -import 'components/grid_widgetbook.dart'; -import 'components/spacing_widgetbook.dart'; -import 'components/typography_widgetbook.dart'; +import 'theme/color_widgetbook.dart'; +import 'components/banner_widgetbook.dart'; +import 'components/chip_widgetbook.dart'; +import 'components/password_input_widgetbook.dart'; +import 'theme/typography_widgetbook.dart'; import 'utils/zebra.dart'; class HotReload extends StatelessWidget { @@ -19,14 +25,24 @@ class HotReload extends StatelessWidget { return Widgetbook.material( directories: [ WidgetbookCategory( - name: 'widgets', + name: 'Components', + isInitiallyExpanded: false, children: [ - gridWidgetBook(), - spacingWidgetbook(), - textWidgetBook(), - colorWidgetBook(), - checkboxWidgetBook() - ], + badgeWidgetBook(), + avatarWidgetBook(), + checkboxWidgetBook(), + buttonWidgetBook(), + BannerWidgetBook(), + accordionWidgetBook(), + chipWidgetBook(), + passwordInputWidgetBook(), + bottomSheetWidgetBook(), + ]..sort((a, b) => a.name.compareTo(b.name)), + ), + WidgetbookCategory( + name: 'Theme', + isInitiallyExpanded: false, + children: [textWidgetBook(), colorWidgetBook(), checkboxWidgetBook()], ), ], addons: [ diff --git a/firebase.json b/firebase.json new file mode 100644 index 00000000..4a130f50 --- /dev/null +++ b/firebase.json @@ -0,0 +1,11 @@ +{ + "hosting": { + "public": "build", + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ] + } +} \ No newline at end of file diff --git a/lib/src/assets/fonts/zeta-icons-round.ttf b/lib/src/assets/fonts/zeta-icons-round.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2ae882a0031348b787a87e85c35a8087867ff726 GIT binary patch literal 145320 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�e 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%0DsTTS8+`3Y><9xkqw&BMdSG;&AIyi2kM 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 Ua1Pb1HCthj{pDw literal 0 HcmV?d00001 diff --git a/lib/src/assets/fonts/zeta-icons-sharp.ttf b/lib/src/assets/fonts/zeta-icons-sharp.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f21be8b437af796464bc691b7fe48fc14f6b456d GIT binary patch literal 116564 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%0A^aOL-EmfY=G8K7c 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 `Jwe$))qx9h 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
    O%$K`Hj#piq0^j9m`-{G&1H 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 createState() => _ZetaAccordionState(); + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('rounded', rounded)) + ..add(DiagnosticsProperty('contained', contained)) + ..add(StringProperty('title', title)) + ..add(DiagnosticsProperty('isOpen', isOpen)); + } +} + +class _ZetaAccordionState extends State with TickerProviderStateMixin { + late bool _isOpen; + late bool _disabled; + late final AnimationController _controller; + late final Animation _animation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(milliseconds: 300), + reverseDuration: const Duration(milliseconds: 200), + vsync: this, + ); + _animation = CurvedAnimation( + parent: _controller, + curve: Curves.fastOutSlowIn, + ); + init(); + } + + @override + void didUpdateWidget(ZetaAccordion oldWidget) { + init(); + super.didUpdateWidget(oldWidget); + } + + void init() { + _isOpen = widget.isOpen; + _disabled = widget.child == null; + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final zetaColors = Zeta.of(context).colors; + final borderColor = _disabled ? zetaColors.borderDisabled : zetaColors.borderSubtle; + return DecoratedBox( + decoration: BoxDecoration( + border: widget.contained ? Border.all(color: borderColor) : Border(top: BorderSide(color: borderColor)), + borderRadius: widget.rounded ? ZetaRadius.minimal : ZetaRadius.none, + ), + child: Padding( + padding: const EdgeInsets.all(1), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextButton( + style: ButtonStyle( + shape: MaterialStatePropertyAll( + RoundedRectangleBorder(borderRadius: widget.rounded ? ZetaRadius.minimal : ZetaRadius.none), + ), + overlayColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.hovered)) { + return zetaColors.cool.shade20; + } + if (states.contains(MaterialState.pressed)) { + return zetaColors.cool.shade30; + } + + if (states.contains(MaterialState.focused)) { + return Colors.transparent; + } + + return null; + }), + side: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.focused)) { + return BorderSide(color: zetaColors.blue.shade50, width: 2); + } + return null; + }), + ), + onPressed: _disabled + ? null + : () => setState(() { + if (_isOpen) { + _controller.reverse(); + _isOpen = false; + } else { + _isOpen = true; + _controller.forward(); + } + }), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + DefaultTextStyle( + style: ZetaTextStyles.titleMedium.apply( + color: _disabled ? zetaColors.textDisabled : zetaColors.textDefault, + ), + child: Flexible(child: Text(widget.title)), + ), + Padding( + padding: const EdgeInsets.only(left: ZetaSpacing.x4), + child: Icon( + _isOpen + ? (widget.rounded ? ZetaIcons.remove_round : ZetaIcons.remove_sharp) + : (widget.rounded ? ZetaIcons.add_round : ZetaIcons.add_sharp), + color: _disabled ? zetaColors.iconDisabled : zetaColors.iconDefault, + ), + ), + ], + ), + ), + ), + SizeTransition( + sizeFactor: _animation, + axisAlignment: -1, + child: Padding( + padding: const EdgeInsets.fromLTRB(ZetaSpacing.x4, 0, ZetaSpacing.x4, ZetaSpacing.x4), + child: DefaultTextStyle(style: ZetaTextStyles.titleSmall, child: widget.child ?? const SizedBox()), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/components/avatars/avatar.dart b/lib/src/components/avatars/avatar.dart new file mode 100644 index 00000000..376c9f28 --- /dev/null +++ b/lib/src/components/avatars/avatar.dart @@ -0,0 +1,235 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import '../../../zeta_flutter.dart'; + +/// [ZetaAvatar] size +enum ZetaAvatarSize { + /// [xl] 64 pixels + xl, + + /// [l] 48 pixels + l, + + /// [m] 40 pixels + m, + + /// [s] 32 pixels + s, + + /// [xs] 24 pixels + xs, +} + +/// ZetaAvatar component +class ZetaAvatar extends StatelessWidget { + /// Constructor for [ZetaAvatar] + const ZetaAvatar({ + super.key, + this.size = ZetaAvatarSize.xl, + this.image, + this.initials, + this.backgroundColor, + this.lowerBadge, + this.upperBadge, + this.borderColor, + }); + + /// Constructor for [ZetaAvatar] with image. + const ZetaAvatar.image({ + super.key, + this.size = ZetaAvatarSize.xl, + this.image, + this.lowerBadge, + this.upperBadge, + this.borderColor, + }) : backgroundColor = null, + initials = null; + + /// Constructor for [ZetaAvatar] with initials. + const ZetaAvatar.initials({ + super.key, + required this.initials, + this.size = ZetaAvatarSize.xl, + this.lowerBadge, + this.upperBadge, + this.borderColor, + this.backgroundColor, + }) : image = null; + + /// Constructor for [ZetaAvatar] with initials from a full name. + ZetaAvatar.fromName({ + super.key, + required String name, + this.size = ZetaAvatarSize.xl, + this.lowerBadge, + this.upperBadge, + this.borderColor, + this.backgroundColor, + }) : image = null, + initials = name.initials; + + /// The size of the [ZetaAvatar] + final ZetaAvatarSize size; + + /// Image to display for avatar. + /// + /// [image] takes priority over [initials]. + final Widget? image; + + /// String to display initials. + final String? initials; + + /// Background color. + final Color? backgroundColor; + + /// Shows border around the avatar + final Color? borderColor; + + /// Status badge shown at lower right corner of avatar. + final ZetaIndicator? lowerBadge; + + /// Notification Badge shown at top right corner of avatar. + final ZetaIndicator? upperBadge; + + bool get _showPlaceholder => image == null && (initials == null || initials!.isEmpty); + + @override + Widget build(BuildContext context) { + final zetaColors = Zeta.of(context).colors; + + final borderSize = size.borderSize; + final sizePixels = size.pixelSize; + final contentSizePixels = size.pixelSize - (borderColor != null ? size.borderSize * 2 : 0); + + final innerContent = ClipRRect( + borderRadius: BorderRadius.circular(64), + clipBehavior: Clip.hardEdge, + child: image ?? + (_showPlaceholder + ? Container( + transform: Matrix4.translationValues(-contentSizePixels * 0.2, -contentSizePixels * 0.1, 0), + child: IconTheme( + data: IconThemeData( + color: Zeta.of(context).colors.cool, + size: contentSizePixels * 1.4, + ), + child: const Icon(ZetaIcons.user_round), + ), + ) + : Center( + child: Text( + size == ZetaAvatarSize.xs ? initials!.substring(0, 1) : initials!, + style: TextStyle(fontSize: size.fontSize, letterSpacing: -0.5), + ), + )), + ); + + return Stack( + children: [ + Container( + margin: EdgeInsets.all(upperBadge == null && lowerBadge == null ? 0 : 3), + width: sizePixels, + height: sizePixels, + decoration: BoxDecoration( + border: borderColor != null ? Border.all(color: borderColor!, width: borderSize / ZetaSpacing.x0_5) : null, + borderRadius: ZetaRadius.full, + color: backgroundColor ?? (_showPlaceholder ? zetaColors.surfacePrimary : zetaColors.cool.shade20), + ), + child: borderColor != null + ? Container( + width: contentSizePixels, + height: contentSizePixels, + decoration: BoxDecoration( + color: _showPlaceholder ? backgroundColor ?? zetaColors.surfaceHovered : null, + border: Border.all(color: zetaColors.surfacePrimary, width: borderSize / ZetaSpacing.x0_5), + borderRadius: ZetaRadius.full, + ), + child: ClipRRect(borderRadius: ZetaRadius.full, clipBehavior: Clip.hardEdge, child: innerContent), + ) + : innerContent, + ), + if (upperBadge != null) + Positioned( + right: 1, + child: upperBadge!.copyWith(size: size.indicatorSize), + ), + if (lowerBadge != null) + Positioned( + right: 1, + bottom: 1, + child: lowerBadge!.copyWith(size: size.indicatorSize), + ), + ], + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('size', size)) + ..add(DiagnosticsProperty('name', initials)) + ..add(DiagnosticsProperty('specialStatus', lowerBadge)) + ..add(DiagnosticsProperty('badge', upperBadge)) + ..add(DiagnosticsProperty('backgroundColor', backgroundColor)) + ..add(ColorProperty('statusColor', borderColor)); + } +} + +extension on ZetaAvatarSize { + double get pixelSize { + switch (this) { + case ZetaAvatarSize.xl: + return ZetaSpacing.x16; + case ZetaAvatarSize.l: + return ZetaSpacing.x12; + case ZetaAvatarSize.m: + return ZetaSpacing.x10; + case ZetaAvatarSize.s: + return ZetaSpacing.x8; + case ZetaAvatarSize.xs: + return ZetaSpacing.x6; + } + } + + ZetaWidgetSize get indicatorSize { + switch (this) { + case ZetaAvatarSize.xl: + case ZetaAvatarSize.l: + case ZetaAvatarSize.m: + return ZetaWidgetSize.large; + case ZetaAvatarSize.s: + return ZetaWidgetSize.medium; + + case ZetaAvatarSize.xs: + return ZetaWidgetSize.small; + } + } + + double get borderSize { + switch (this) { + case ZetaAvatarSize.xl: + case ZetaAvatarSize.l: + case ZetaAvatarSize.m: + return ZetaSpacing.x1; + + case ZetaAvatarSize.s: + case ZetaAvatarSize.xs: + return ZetaSpacing.x0_5; + } + } + + double get fontSize { + switch (this) { + case ZetaAvatarSize.xl: + return ZetaSpacing.x5; + case ZetaAvatarSize.l: + return ZetaSpacing.x4; + case ZetaAvatarSize.m: + return ZetaSpacing.x3_5; + case ZetaAvatarSize.s: + case ZetaAvatarSize.xs: + return ZetaSpacing.x3; + } + } +} diff --git a/lib/src/components/badges/badge.dart b/lib/src/components/badges/badge.dart new file mode 100644 index 00000000..b4fa87d8 --- /dev/null +++ b/lib/src/components/badges/badge.dart @@ -0,0 +1,57 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +/// Zeta Badge. +/// +/// Text badges notify users of line items that need attention. +class ZetaBadge extends StatelessWidget { + ///Constructs [ZetaBadge]. + const ZetaBadge({ + required this.label, + this.status = ZetaWidgetStatus.info, + this.rounded = true, + super.key, + }); + + /// {@macro zeta-component-rounded} + final bool rounded; + + /// {@template zeta-component-badge-status} + /// Indicates the status of the badge. + /// + /// Defaults to "info" + /// {@endtemplate} + final ZetaWidgetStatus status; + + /// Label of the badge. + final String label; + + @override + Widget build(BuildContext context) { + final Color backgroundColor = status.colorSwatch(context); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: ZetaSpacing.x1, vertical: ZetaSpacing.x0_5), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: rounded ? ZetaRadius.minimal : ZetaRadius.minimal, + ), + child: Text( + label, + style: ZetaTextStyles.labelSmall.apply(color: backgroundColor.onColor), + overflow: TextOverflow.ellipsis, + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(StringProperty('label', label)) + ..add(EnumProperty('status', status)) + ..add(DiagnosticsProperty('rounded', rounded)); + } +} diff --git a/lib/src/components/badges/indicator.dart b/lib/src/components/badges/indicator.dart new file mode 100644 index 00000000..52785236 --- /dev/null +++ b/lib/src/components/badges/indicator.dart @@ -0,0 +1,161 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import '../../../zeta_flutter.dart'; + +/// [ZetaIndicator] type. +enum ZetaIndicatorType { + /// Shows an icon on [ZetaIndicator]. Defaults to [ZetaIcons.star_round]. + icon, + + /// Shows a number on [ZetaIndicator] from provided [ZetaIndicator.value]. + notification, +} + +/// ZetaIndicator. +/// +/// Indicators are used to show the status of a user or any messages/notifications they might have. +class ZetaIndicator extends StatelessWidget { + /// Constructor for [ZetaIndicator]. + const ZetaIndicator({ + super.key, + required this.type, + this.size = ZetaWidgetSize.large, + this.icon, + this.value, + this.inverse = false, + }); + + /// Constructor for [ZetaIndicator] of type [ZetaIndicatorType.icon]. + const ZetaIndicator.icon({ + super.key, + this.size = ZetaWidgetSize.large, + this.inverse = false, + this.icon, + }) : type = ZetaIndicatorType.icon, + value = null; + + /// Constructor for [ZetaIndicator] of type [ZetaIndicatorType.notification]. + const ZetaIndicator.notification({ + super.key, + this.size = ZetaWidgetSize.large, + this.inverse = false, + this.icon, + this.value, + }) : type = ZetaIndicatorType.notification; + + /// The type of the [ZetaIndicator] - icon or notification. + final ZetaIndicatorType type; + + /// The size of the [ZetaIndicator]. Default is [ZetaWidgetSize.large] + final ZetaWidgetSize size; + + /// Inverse the border color. + final bool inverse; + + /// Indicator icon, default: `ZetaIcons.star_round`. + final Icon? icon; + + /// Value for the type `notification`. + final int? value; + + /// Creates a clone. + ZetaIndicator copyWith({ + ZetaIndicatorType? type, + ZetaWidgetSize? size, + Icon? icon, + int? value, + bool? inverse, + }) { + return ZetaIndicator( + type: type ?? this.type, + size: size ?? this.size, + icon: icon ?? this.icon, + value: value ?? this.value, + inverse: inverse ?? this.inverse, + ); + } + + @override + Widget build(BuildContext context) { + final zetaColors = Zeta.of(context).colors; + final Color backgroundColor = (type == ZetaIndicatorType.icon ? zetaColors.blue : zetaColors.negative); + final Color foregroundColor = backgroundColor.onColor; + final sizePixels = _getSizePixels(size, type); + + return Container( + width: sizePixels + ZetaSpacing.x1, + height: sizePixels + ZetaSpacing.x1, + decoration: BoxDecoration( + color: (inverse ? foregroundColor : Colors.transparent), + borderRadius: ZetaRadius.full, + ), + child: Center( + child: Container( + width: sizePixels, + height: sizePixels, + decoration: BoxDecoration(color: backgroundColor, borderRadius: BorderRadius.circular(ZetaSpacing.x4)), + child: ClipRRect( + borderRadius: BorderRadius.circular(ZetaSpacing.x4), + clipBehavior: Clip.hardEdge, + child: size == ZetaWidgetSize.small ? null : _buildContent(foregroundColor), + ), + ), + ), + ); + } + + Widget _buildContent(Color foregroundColor) { + switch (type) { + case ZetaIndicatorType.icon: + final iconSize = _getIconSize(size); + return Center( + child: IconTheme( + data: IconThemeData(color: foregroundColor, size: iconSize), + child: icon ?? const Icon(ZetaIcons.star_round), + ), + ); + case ZetaIndicatorType.notification: + return Center( + child: Text( + value.formatMaxChars(), + style: (size == ZetaWidgetSize.large ? ZetaTextStyles.labelIndicator : ZetaTextStyles.labelTiny) + .apply(color: foregroundColor), + ), + ); + } + } + + /// Returns the size of [ZetaWidgetSize] in pixels. + double _getSizePixels(ZetaWidgetSize size, ZetaIndicatorType type) { + switch (size) { + case ZetaWidgetSize.large: + return ZetaSpacing.x4; + case ZetaWidgetSize.medium: + return type == ZetaIndicatorType.icon ? ZetaSpacing.x3 : ZetaSpacing.x3_5; + case ZetaWidgetSize.small: + return ZetaSpacing.x2; + } + } + + double _getIconSize(ZetaWidgetSize size) { + switch (size) { + case ZetaWidgetSize.large: + return ZetaSpacing.x3; + case ZetaWidgetSize.medium: + return ZetaSpacing.x2; + case ZetaWidgetSize.small: + return 0; + } + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', type)) + ..add(DiagnosticsProperty('size', size)) + ..add(DiagnosticsProperty('value', value)) + ..add(DiagnosticsProperty('icon', icon)) + ..add(DiagnosticsProperty('inverseBorder', inverse)); + } +} diff --git a/lib/src/components/badges/priority_pill.dart b/lib/src/components/badges/priority_pill.dart new file mode 100644 index 00000000..a95002c0 --- /dev/null +++ b/lib/src/components/badges/priority_pill.dart @@ -0,0 +1,72 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +///Zeta Priority Pill. +/// +/// This badge is used to indicate the order of importance. +class ZetaPriorityPill extends StatelessWidget { + ///Constructs [ZetaPriorityPill] + const ZetaPriorityPill({ + required this.index, + required this.priority, + this.rounded = true, + super.key, + }); + + /// {@macro zeta-component-rounded} + final bool rounded; + + /// Leading number in component. + final int index; + + /// Text in main part of component. + final String priority; + + @override + Widget build(BuildContext context) { + final theme = Zeta.of(context); + final backgroundColor = theme.colors.primary; + final Color foregroundColor = backgroundColor.onColor; + + return DecoratedBox( + decoration: BoxDecoration( + borderRadius: rounded ? ZetaRadius.full : ZetaRadius.none, + color: backgroundColor.shade10, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + alignment: Alignment.center, + height: ZetaSpacing.x7, + width: ZetaSpacing.x7, + decoration: BoxDecoration( + shape: rounded ? BoxShape.circle : BoxShape.rectangle, + color: backgroundColor, + ), + child: Text(index.formatMaxChars(), style: ZetaTextStyles.bodyMedium.apply(color: foregroundColor)), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: ZetaSpacing.x2, vertical: ZetaSpacing.x1), + child: Text( + priority, + style: ZetaTextStyles.bodyMedium, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(IntProperty('index', index)) + ..add(StringProperty('priority', priority)) + ..add(DiagnosticsProperty('rounded', rounded)); + } +} diff --git a/lib/src/components/badges/status_label.dart b/lib/src/components/badges/status_label.dart new file mode 100644 index 00000000..e403d0a2 --- /dev/null +++ b/lib/src/components/badges/status_label.dart @@ -0,0 +1,68 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import '../../../zeta_flutter.dart'; + +/// Zeta Status Label. +/// +/// To help some information, labels, or errors stand out, we present them with badges. +/// They can look like buttons, but users can’t select them. They just guide users to things they should pay attention to. +class ZetaStatusLabel extends StatelessWidget { + ///Constructs [ZetaStatusLabel]. + const ZetaStatusLabel({ + super.key, + required this.label, + this.rounded = true, + this.status = ZetaWidgetStatus.info, + this.customIcon, + }); + + /// {@macro zeta-component-rounded} + final bool rounded; + + /// {@macro zeta-component-badge-status} + final ZetaWidgetStatus status; + + /// Text displayed on label. + final String label; + + /// Optional custom icon. If null, default circle icon is used. + final IconData? customIcon; + + @override + Widget build(BuildContext context) { + final ZetaColorSwatch colors = status.colorSwatch(context); + + return DecoratedBox( + decoration: BoxDecoration( + color: colors.shade10, + border: Border.all(color: colors.border), + borderRadius: rounded ? ZetaRadius.full : ZetaRadius.minimal, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: ZetaSpacing.x2, vertical: ZetaSpacing.x0_5), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(customIcon ?? Icons.circle, size: ZetaSpacing.x2, color: colors.icon), + const SizedBox(width: ZetaSpacing.xs), + Text( + label, + style: ZetaTextStyles.bodyMedium.apply(color: colors.shade10.onColor), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(StringProperty('label', label)) + ..add(DiagnosticsProperty('rounded', rounded)) + ..add(DiagnosticsProperty('customIcon', customIcon)) + ..add(EnumProperty('severity', status)); + } +} diff --git a/lib/src/components/badges/tag.dart b/lib/src/components/badges/tag.dart new file mode 100644 index 00000000..877141ab --- /dev/null +++ b/lib/src/components/badges/tag.dart @@ -0,0 +1,171 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +///Tag Direction options for [ZetaTag]. +enum ZetaTagDirection { + ///Tag facing left. + left, + + ///Tag facing right. + right +} + +///Zeta Tag +class ZetaTag extends StatelessWidget { + ///Constructs [ZetaTag]. + const ZetaTag({ + super.key, + this.direction = ZetaTagDirection.left, + required this.label, + this.rounded = true, + }); + + /// Constructs left facing [ZetaTag]. + const ZetaTag.left({super.key, required this.label, this.rounded = true}) : direction = ZetaTagDirection.left; + + ///Constructs right facing [ZetaTag]. + const ZetaTag.right({super.key, required this.label, this.rounded = true}) : direction = ZetaTagDirection.right; + + ///Determines the direction of the tag + /// + /// Defaults to left + final ZetaTagDirection direction; + + /// {@zeta-component-rounded} + final bool rounded; + + ///tag label + final String label; + + /// Fixed container size + static const Size _containerSize = Size(ZetaSpacing.x9, ZetaSpacing.x7); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + if (direction == ZetaTagDirection.right) _buildCustomPaint(context), + Container( + decoration: BoxDecoration( + color: Zeta.of(context).colors.surfaceHovered, + borderRadius: _getBorderRadius(), + ), + height: _containerSize.height, + constraints: BoxConstraints(minWidth: _containerSize.width), + child: Center( + child: FittedBox( + child: Padding( + padding: const EdgeInsets.fromLTRB(ZetaSpacing.x2, 1, ZetaSpacing.x2, 1), + child: Text( + label, + style: ZetaTextStyles.bodyMedium, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ), + ), + if (direction == ZetaTagDirection.left) _buildCustomPaint(context), + ], + ); + } + + BorderRadius? _getBorderRadius() { + if (!rounded) return null; + if (direction == ZetaTagDirection.right) { + return const BorderRadius.only( + topRight: Radius.circular(ZetaSpacing.x0_5), + bottomRight: Radius.circular(ZetaSpacing.x0_5), + ); + } else { + return const BorderRadius.only( + topLeft: Radius.circular(ZetaSpacing.x0_5), + bottomLeft: Radius.circular(ZetaSpacing.x0_5), + ); + } + } + + Widget _buildCustomPaint(BuildContext context) { + return CustomPaint( + size: const Size(ZetaSpacing.x3, ZetaSpacing.x7), + painter: _TagPainter( + color: Zeta.of(context).colors.surfaceHovered, + direction: direction, + rounded: rounded, + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(EnumProperty('direction', direction)) + ..add(StringProperty('label', label)) + ..add(DiagnosticsProperty('rounded', rounded)); + } +} + +class _TagPainter extends CustomPainter { + const _TagPainter({ + required this.color, + required this.direction, + required this.rounded, + }); + + final Color color; + final ZetaTagDirection direction; + final bool rounded; + + @override + void paint(Canvas canvas, Size size) { + final Paint paint = Paint() + ..color = color + ..style = PaintingStyle.fill; + final path = _drawPath(size, rounded, direction == ZetaTagDirection.right); + + canvas.drawPath(path, paint); + } + + Path _drawPath(Size size, bool rounded, bool isRight) { + /// Const points of path + const double a = 0.1; + const double b = 0.04; + const double c = 0.48; + const double d = 0.55; + + // Dynamic points on path + final double e = isRight ? size.width : 0; + final double f = isRight ? 1 : -1; + + final path = Path() + ..moveTo(e + f, 0) + ..lineTo(e, 0); + if (rounded) { + path + ..lineTo(size.width * (isRight ? a : 1 - a), size.height * (1 - d)) + ..cubicTo( + size.width * (isRight ? b : 1 - b), + size.height * c, + size.width * (isRight ? b : 1 - b), + size.height * (1 - c), + size.width * (isRight ? a : 1 - a), + size.height * d, + ); + } else { + path.lineTo(isRight ? 0 : size.width, size.height / 2); + } + + path + ..lineTo(e, size.height) + ..lineTo(e + f, size.height) + ..close(); + + return path; + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/src/components/badges/workcloud_indicator.dart b/lib/src/components/badges/workcloud_indicator.dart new file mode 100644 index 00000000..3a79c04a --- /dev/null +++ b/lib/src/components/badges/workcloud_indicator.dart @@ -0,0 +1,164 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +///Indicator Type +enum ZetaWorkcloudIndicatorType { + /// Red. + one, + + /// Orange + two, + + /// Blue. + three, + + /// Green. + four, + + /// Purple. + five, + + /// Pink. + six, + + /// Yellow. + seven, + + /// Teal + eight, + + /// Cool grey. + nine, + + /// Warn grey. + ten, +} + +extension on ZetaWorkcloudIndicatorType { + ZetaColorSwatch color(BuildContext context) { + final colors = Zeta.of(context).colors; + switch (this) { + case ZetaWorkcloudIndicatorType.one: + return colors.red; + case ZetaWorkcloudIndicatorType.two: + return colors.orange; + case ZetaWorkcloudIndicatorType.three: + return colors.blue; + case ZetaWorkcloudIndicatorType.four: + return colors.green; + case ZetaWorkcloudIndicatorType.five: + return colors.purple; + case ZetaWorkcloudIndicatorType.six: + return colors.pink; + case ZetaWorkcloudIndicatorType.seven: + return colors.yellow; + case ZetaWorkcloudIndicatorType.eight: + return colors.teal; + case ZetaWorkcloudIndicatorType.nine: + return colors.cool; + case ZetaWorkcloudIndicatorType.ten: + return colors.warm; + } + } +} + +/// Zeta Workcloud Indicator. +/// +/// There are 10 available levels in which ether the values 1 through 10 can be used, +/// or icons can be passed. +class ZetaWorkcloudIndicator extends StatelessWidget { + ///Constructs [ZetaWorkcloudIndicator] + const ZetaWorkcloudIndicator({ + super.key, + this.priorityType = ZetaWorkcloudIndicatorType.one, + this.prioritySize = ZetaWidgetSize.small, + this.label, + this.index, + this.icon, + }); + + /// The type of priority. + final ZetaWorkcloudIndicatorType priorityType; + + /// The size of Priority Pill. + /// + /// Defaults to 'small'. + final ZetaWidgetSize prioritySize; + + /// Text label. Not shown when [prioritySize] is [ZetaWidgetSize.small] + final String? label; + + /// Index value. Typically a number. + /// + /// If null, and no icon is provided, the index will match the [priorityType]. + /// + /// It is recommended to not exceed 2 characters here. + final String? index; + + /// Custom icon. If not null, this will replace the index text. + final IconData? icon; + + @override + Widget build(BuildContext context) { + final ZetaColorSwatch color = priorityType.color(context); + final textStyle = prioritySize == ZetaWidgetSize.large ? ZetaTextStyles.labelMedium : ZetaTextStyles.labelTiny; + + return DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(ZetaSpacing.l), + color: color.shade20, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox.square( + dimension: prioritySize == ZetaWidgetSize.large ? ZetaSpacing.x6 : ZetaSpacing.x5, + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: priorityType == ZetaWorkcloudIndicatorType.nine ? color.shade80 : color, + ), + child: Center( + child: icon != null + ? Icon( + icon, + size: prioritySize == ZetaWidgetSize.large ? ZetaSpacing.x4 : ZetaSpacing.x3_5, + color: color.onColor, + ) + : Text( + index ?? (priorityType.index + 1).toString(), + style: textStyle.apply(color: color.onColor), + textAlign: TextAlign.center, + ), + ), + ), + ), + if (prioritySize != ZetaWidgetSize.small) + Container( + constraints: const BoxConstraints(minWidth: ZetaSpacing.x9), + padding: const EdgeInsets.symmetric(horizontal: ZetaSpacing.xs), + child: Text(label ?? '', style: textStyle, overflow: TextOverflow.ellipsis), + ), + ], + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add( + EnumProperty('priorityType', priorityType), + ) + ..add( + EnumProperty('prioritySize', prioritySize), + ) + ..add(StringProperty('label', label)) + ..add(StringProperty('index', index)) + ..add(DiagnosticsProperty('icon', icon)); + } +} diff --git a/lib/src/components/banners/in_page_banner.dart b/lib/src/components/banners/in_page_banner.dart new file mode 100644 index 00000000..227703be --- /dev/null +++ b/lib/src/components/banners/in_page_banner.dart @@ -0,0 +1,140 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +///Zeta In Page Banner +class ZetaInPageBanner extends StatelessWidget { + ///Constructs [ZetaInPageBanner] + const ZetaInPageBanner({ + super.key, + required this.content, + this.rounded = true, + this.status = ZetaWidgetStatus.info, + this.onClose, + this.title, + this.customIcon, + this.actions = const [], + }); + + ///The content of the banner. Typically [Text]. + final Widget content; + + /// {@macro zeta-component-rounded} + final bool rounded; + + ///Determines the color of the banner. + /// + ///Defaults to [ZetaWidgetStatus.info]. + final ZetaWidgetStatus status; + + ///Title of the banner. + final String? title; + + /// Leading icon on top left of banner. + final IconData? customIcon; + + /// Action buttons to show at the bottom of the banner. + final List actions; + + /// Called when the button 'Close' is tapped. + /// + /// If null, close icon will not appear. + final VoidCallback? onClose; + + @override + Widget build(BuildContext context) { + final theme = Zeta.of(context); + final colors = status.colorSwatch(context); + final hasTitle = title != null; + + return DecoratedBox( + decoration: BoxDecoration( + color: colors.surface, + border: Border.all(color: colors.border), + borderRadius: rounded ? ZetaRadius.minimal : ZetaRadius.none, + ), + child: Padding( + padding: const EdgeInsets.all(ZetaSpacing.x0_5), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(top: ZetaSpacing.x3, start: ZetaSpacing.x2_5), + child: Icon( + status.icon(rounded: rounded), + size: ZetaSpacing.x5, + color: status == ZetaWidgetStatus.neutral ? theme.colors.textDefault : colors.icon, + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: ZetaSpacing.x2_5), + if (hasTitle) + Text( + title!, + style: ZetaTextStyles.titleSmall, + ).paddingBottom(ZetaSpacing.xxs), + DefaultTextStyle( + style: ZetaTextStyles.bodyMedium.apply(color: theme.colors.textDefault), + child: content, + ), + if (actions.isNotEmpty) + Row( + children: actions + .map((e) => e.copyWith(size: ZetaWidgetSize.medium, type: ZetaButtonType.outlineSubtle)) + .divide(const SizedBox.square(dimension: ZetaSpacing.x2)) + .toList(), + ).paddingTop(ZetaSpacing.x4), + const SizedBox(height: ZetaSpacing.x2_5), + ], + ), + ), + if (onClose != null) + IconButton( + onPressed: onClose, + icon: Icon( + !rounded ? ZetaIcons.close_sharp : ZetaIcons.close_round, + size: ZetaSpacing.x5, + ), + ), + ].divide(const SizedBox.square(dimension: ZetaSpacing.x2)).toList(), + ), + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add( + ObjectFlagProperty.has( + 'onCloseFunction', + onClose, + ), + ) + ..add(DiagnosticsProperty('rounded', rounded)) + ..add(EnumProperty('severity', status)) + ..add(StringProperty('title', title)) + ..add(DiagnosticsProperty('customIcon', customIcon)); + } +} + +extension on ZetaWidgetStatus { + IconData icon({required bool rounded}) { + switch (this) { + case ZetaWidgetStatus.positive: + return rounded ? ZetaIcons.check_circle_round : ZetaIcons.check_circle_sharp; + case ZetaWidgetStatus.warning: + return rounded ? ZetaIcons.warning_round : ZetaIcons.warning_sharp; + case ZetaWidgetStatus.negative: + return rounded ? ZetaIcons.error_round : ZetaIcons.error_sharp; + case ZetaWidgetStatus.neutral: + case ZetaWidgetStatus.info: + return rounded ? ZetaIcons.info_round : ZetaIcons.info_sharp; + } + } +} diff --git a/lib/src/components/banners/system_banner.dart b/lib/src/components/banners/system_banner.dart new file mode 100644 index 00000000..deccf34a --- /dev/null +++ b/lib/src/components/banners/system_banner.dart @@ -0,0 +1,101 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import '../../../zeta_flutter.dart'; + +/// [ZetaSystemBanner] type +enum ZetaSystemBannerStatus { + /// Primary background. + primary, + + /// Green background. + positive, + + /// Yellow background. + warning, + + /// Red background. + negative, +} + +/// ZetaSystemBanner. Extends [MaterialBanner]. +/// +/// A banner displays an important, succinct message, and provides action for users to address. +/// It draws the attention to the message by displaying it at the top in various colors. +class ZetaSystemBanner extends MaterialBanner { + /// Constructor for [ZetaSystemBanner]. See [MaterialBanner] for more information. + ZetaSystemBanner({ + required BuildContext context, + required String title, + super.key, + IconData? leadingIcon, + ZetaSystemBannerStatus type = ZetaSystemBannerStatus.primary, + bool titleStart = false, + Widget? trailing, + }) : super( + content: Builder( + builder: (context) { + final backgroundColor = _backgroundColorFromType(context, type); + final foregroundColor = backgroundColor.onColor; + if (context.mounted) { + // ignore: invalid_use_of_visible_for_testing_member + final statusBarColor = SystemChrome.latestStyle?.statusBarColor; + if (Platform.isAndroid && statusBarColor != backgroundColor) { + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + statusBarColor: backgroundColor, + systemNavigationBarIconBrightness: backgroundColor.isDark ? Brightness.light : Brightness.dark, + ), + ); + } + } + + return DefaultTextStyle( + style: ZetaTextStyles.titleSmall.copyWith( + color: foregroundColor, + overflow: TextOverflow.ellipsis, + height: 1, + ), + child: Row( + mainAxisAlignment: titleStart ? MainAxisAlignment.center : MainAxisAlignment.start, + children: [ + if (leadingIcon != null) + Padding( + padding: const EdgeInsets.only(right: ZetaSpacing.x2), + child: Icon( + leadingIcon, + color: foregroundColor, + size: ZetaSpacing.x6, + ), + ), + Flexible(child: Text(title)), + ], + ), + ); + }, + ), + backgroundColor: _backgroundColorFromType(context, type), + actions: [ + IconTheme( + data: IconThemeData(color: _backgroundColorFromType(context, type).onColor), + child: trailing ?? const SizedBox(), + ), + ], + ); + + static ZetaColorSwatch _backgroundColorFromType(BuildContext context, ZetaSystemBannerStatus type) { + final zeta = Zeta.of(context); + + switch (type) { + case ZetaSystemBannerStatus.primary: + return zeta.colors.primary; + case ZetaSystemBannerStatus.positive: + return zeta.colors.positive; + case ZetaSystemBannerStatus.warning: + return zeta.colors.orange; + case ZetaSystemBannerStatus.negative: + return zeta.colors.negative; + } + } +} diff --git a/lib/src/components/bottom sheets/bottom_sheet.dart b/lib/src/components/bottom sheets/bottom_sheet.dart new file mode 100644 index 00000000..db1447c2 --- /dev/null +++ b/lib/src/components/bottom sheets/bottom_sheet.dart @@ -0,0 +1,108 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +/// Component [ZetaBottomSheet] +class ZetaBottomSheet extends StatelessWidget { + /// Constructor for [ZetaBottomSheet]. + const ZetaBottomSheet({ + super.key, + this.title, + this.body, + this.centerTitle = true, + }); + + /// The title of [ZetaBottomSheet]. + final String? title; + + /// Whether title should be center or left aligned. + /// + /// Defaults to true (center aligned). + final bool centerTitle; + + /// The content of [ZetaBottomSheet]. + final Widget? body; + + @override + Widget build(BuildContext context) { + final colors = Zeta.of(context).colors; + + return Container( + padding: const EdgeInsets.fromLTRB( + ZetaSpacing.x5, + 0, + ZetaSpacing.x5, + ZetaSpacing.x5, + ), + decoration: BoxDecoration( + color: colors.surfaceSecondary, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(ZetaSpacing.x6), + topRight: Radius.circular(ZetaSpacing.x6), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Align( + child: SizedBox( + height: ZetaSpacing.x9, + child: Padding( + padding: const EdgeInsets.only(top: ZetaSpacing.x2), + child: Icon( + Icons.maximize_rounded, + size: ZetaSpacing.x16, + color: colors.surfaceDisabled, + ), + ), + ), + ), + if (title != null) + Align( + alignment: centerTitle ? Alignment.center : Alignment.centerLeft, + child: Text( + title!, + style: ZetaTextStyles.titleMedium, + ), + ), + Material( + color: colors.surfaceSecondary, + child: body ?? const SizedBox(), + ), + ], + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('title', title)) + ..add(DiagnosticsProperty('centerTitle', centerTitle)); + } +} + +/// Function to show [ZetaBottomSheet]. +/// +/// Uses [showModalBottomSheet] for functionality, but with Zeta styling and simplified functionality. +Future showZetaBottomSheet({ + required BuildContext context, + String? title, + Widget? body, + bool centerTitle = true, + bool isDismissible = true, + bool enableDrag = true, +}) { + return showModalBottomSheet( + context: context, + isDismissible: isDismissible, + enableDrag: enableDrag, + builder: (BuildContext context) => ZetaBottomSheet( + title: title, + body: body, + centerTitle: centerTitle, + ), + ); +} diff --git a/lib/src/components/bottom sheets/menu_items.dart b/lib/src/components/bottom sheets/menu_items.dart new file mode 100644 index 00000000..750c3ed7 --- /dev/null +++ b/lib/src/components/bottom sheets/menu_items.dart @@ -0,0 +1,150 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import '../../../zeta_flutter.dart'; + +/// The type of the [ZetaMenuItem] +enum ZetaMenuItemType { + /// [ZetaMenuItem] with icon and text in a row + horizontal, + + /// [ZetaMenuItem] with icon and text in a column + vertical, +} + +/// Zeta Menu Item component. +/// +/// Typically used as body of [ZetaBottomSheet]. +class ZetaMenuItem extends StatelessWidget { + /// Constructor for the component [ZetaMenuItem]. + /// + /// Typically, [ZetaMenuItem.horizontal] or [ZetaMenuItem.vertical] constructors should be used. + const ZetaMenuItem({ + super.key, + required this.type, + required this.label, + this.onTap, + this.leading, + this.trailing, + }); + + /// Creates horizontal menu item + const ZetaMenuItem.horizontal({ + super.key, + required this.label, + this.onTap, + this.leading, + this.trailing, + }) : type = ZetaMenuItemType.horizontal; + + /// Creates horizontal menu item + const ZetaMenuItem.vertical({ + super.key, + required this.label, + Widget? icon, + this.onTap, + }) : type = ZetaMenuItemType.vertical, + leading = icon, + trailing = null; + + /// The type of the [ZetaMenuItem] - horizontal or vertical. + final ZetaMenuItemType type; + + /// What to do when [ZetaMenuItem] is pressed. + final VoidCallback? onTap; + + /// The label of the [ZetaMenuItem]. + final Widget label; + + /// The leading widget of the [ZetaMenuItem]. + /// + /// Icon is at leading edge for [ZetaMenuItem.horizontal], and center aligned for [ZetaMenuItem.vertical]. + /// + /// Typically an [Icon]. + final Widget? leading; + + /// The trailing widget of the [ZetaMenuItem]. + /// + /// Only used for [ZetaMenuItem.horizontal]. + /// + /// Typically an icon. + final Widget? trailing; + + bool get _enabled => onTap != null; + + VoidCallback? get _onTap => _enabled ? onTap : null; + + @override + Widget build(BuildContext context) { + final colors = Zeta.of(context).colors; + + final Widget text = DefaultTextStyle( + style: ZetaTextStyles.labelLarge.apply(color: _enabled ? colors.textDefault : colors.textDisabled), + child: label, + ); + + switch (type) { + case ZetaMenuItemType.horizontal: + return ConstrainedBox( + constraints: const BoxConstraints(minHeight: ZetaSpacing.x12), + child: InkWell( + onTap: _onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: ZetaSpacing.x4, vertical: ZetaSpacing.x3), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + if (leading != null) + Padding(padding: const EdgeInsets.only(right: ZetaSpacing.x2), child: leading), + Expanded(child: text), + ], + ), + ), + if (trailing != null) + IconTheme( + data: _iconThemeData(colors, _enabled, ZetaSpacing.x6), + child: trailing ?? const Icon(Icons.keyboard_arrow_right), + ), + ], + ), + ), + ), + ); + case ZetaMenuItemType.vertical: + return InkWell( + onTap: _onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: ZetaSpacing.x4, vertical: ZetaSpacing.x3), + child: Column( + children: [ + if (leading != null) + Padding( + padding: const EdgeInsets.only(bottom: ZetaSpacing.x3), + child: IconTheme( + data: _iconThemeData(colors, _enabled, ZetaSpacing.x8), + child: leading!, + ), + ), + text, + ], + ), + ), + ); + } + } + + static IconThemeData _iconThemeData(ZetaColors colors, bool enabled, double size) => IconThemeData( + color: enabled ? colors.iconSubtle : colors.iconDisabled, + size: size, + ); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', type)) + ..add(DiagnosticsProperty('onTap', onTap)); + } +} diff --git a/lib/src/components/buttons/button.dart b/lib/src/components/buttons/button.dart new file mode 100644 index 00000000..70ae5644 --- /dev/null +++ b/lib/src/components/buttons/button.dart @@ -0,0 +1,300 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +///Button types +enum ZetaButtonType { + /// Background: Primary color; defaults to blue. + /// Border: None. + primary, + + /// Background: Secondary color; defaults to yellow. + /// Border: None. + secondary, + + /// Background: Positive color; defaults to green. + /// Border: None. + positive, + + /// Background: Negative color; defaults to red. + /// Border: None. + negative, + + /// Background: None. + /// Border: Primary color; defaults to blue. + outline, + + /// Background: None. + /// Border: Subtle color; defaults to cool grey. + outlineSubtle, + + /// Background: None. + /// Border: None. + /// Foreground color: Primary; defaults to blue. + text, +} + +extension on ZetaButtonType { + ZetaColorSwatch color(ZetaColors colors) { + switch (this) { + case ZetaButtonType.secondary: + return colors.secondary; + case ZetaButtonType.positive: + return colors.positive; + case ZetaButtonType.negative: + return colors.negative; + case ZetaButtonType.outline: + case ZetaButtonType.primary: + return colors.primary; + case ZetaButtonType.outlineSubtle: + case ZetaButtonType.text: + return colors.cool; + } + } + + bool get border => this == ZetaButtonType.outline || this == ZetaButtonType.outlineSubtle; + bool get solid => index < 4; +} + +extension on ZetaWidgetBorder { + BorderRadius get radius { + switch (this) { + case ZetaWidgetBorder.sharp: + return ZetaRadius.none; + case ZetaWidgetBorder.rounded: + return ZetaRadius.minimal; + case ZetaWidgetBorder.full: + return ZetaRadius.full; + } + } +} + +///Zeta Button +class ZetaButton extends StatelessWidget { + ///Constructs [ZetaButton] + const ZetaButton({ + required this.label, + this.onPressed, + this.type = ZetaButtonType.primary, + this.size = ZetaWidgetSize.medium, + this.borderType = ZetaWidgetBorder.rounded, + super.key, + }); + + /// Constructs [ZetaButton] with Primary theme. + const ZetaButton.primary({ + required this.label, + this.onPressed, + this.size = ZetaWidgetSize.medium, + this.borderType = ZetaWidgetBorder.rounded, + super.key, + }) : type = ZetaButtonType.primary; + + /// Constructs [ZetaButton] with Secondary theme. + const ZetaButton.secondary({ + required this.label, + this.onPressed, + this.size = ZetaWidgetSize.medium, + this.borderType = ZetaWidgetBorder.rounded, + super.key, + }) : type = ZetaButtonType.secondary; + + /// Constructs [ZetaButton] with Positive theme. + const ZetaButton.positive({ + required this.label, + this.onPressed, + this.size = ZetaWidgetSize.medium, + this.borderType = ZetaWidgetBorder.rounded, + super.key, + }) : type = ZetaButtonType.positive; + + /// Constructs [ZetaButton] with Negative theme. + const ZetaButton.negative({ + required this.label, + this.onPressed, + this.size = ZetaWidgetSize.medium, + this.borderType = ZetaWidgetBorder.rounded, + super.key, + }) : type = ZetaButtonType.negative; + + /// Constructs [ZetaButton] with Outline theme. + const ZetaButton.outline({ + required this.label, + this.onPressed, + this.size = ZetaWidgetSize.medium, + this.borderType = ZetaWidgetBorder.rounded, + super.key, + }) : type = ZetaButtonType.outline; + + /// Constructs [ZetaButton] with Outline Subtle theme. + const ZetaButton.outlineSubtle({ + required this.label, + this.onPressed, + this.size = ZetaWidgetSize.medium, + this.borderType = ZetaWidgetBorder.rounded, + super.key, + }) : type = ZetaButtonType.outlineSubtle; + + /// Constructs [ZetaButton] with text theme. + const ZetaButton.text({ + required this.label, + this.onPressed, + this.size = ZetaWidgetSize.medium, + this.borderType = ZetaWidgetBorder.rounded, + super.key, + }) : type = ZetaButtonType.text; + + ///Button label + final String label; + + ///Called when the button is tapped or otherwise activated. + final VoidCallback? onPressed; + + ///The coloring type of the button + final ZetaButtonType type; + + ///Whether or not the button is sharp or rounded + ///Defaults to rounded + final ZetaWidgetBorder borderType; + + /// Size of the button. Defaults to large. + final ZetaWidgetSize size; + + /// Creates a clone. + ZetaButton copyWith({ + String? label, + VoidCallback? onPressed, + ZetaButtonType? type, + ZetaWidgetSize? size, + ZetaWidgetBorder? borderType, + Key? key, + }) { + return ZetaButton( + label: label ?? this.label, + onPressed: onPressed ?? this.onPressed, + type: type ?? this.type, + size: size ?? this.size, + borderType: borderType ?? this.borderType, + key: key ?? this.key, + ); + } + + @override + Widget build(BuildContext context) { + final colors = Zeta.of(context).colors; + return ConstrainedBox( + constraints: BoxConstraints(minHeight: _minConstraints, minWidth: _minConstraints), + child: FilledButton( + onPressed: onPressed, + style: _buttonStyle(colors), + child: SelectionContainer.disabled( + child: label.isEmpty + ? const SizedBox() + : Text( + label, + style: _textStyle, + ).paddingHorizontal(_textPadding), + ), + ), + ); + } + + ButtonStyle _buttonStyle(ZetaColors colors) { + return ButtonStyle( + minimumSize: MaterialStateProperty.all(const Size.square(32)), + shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: borderType.radius)), + backgroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return colors.surfaceDisabled; + } + if (states.contains(MaterialState.pressed)) { + return type.solid ? type.color(colors).shade70 : colors.primary.shade10; + } + if (states.contains(MaterialState.hovered)) { + return type.solid ? type.color(colors).shade50 : colors.cool.shade20; + } + return type.solid ? type.color(colors) : Colors.transparent; + }, + ), + foregroundColor: MaterialStateProperty.resolveWith( + (states) { + if (states.contains(MaterialState.disabled)) { + return colors.textDisabled; + } + switch (type) { + case ZetaButtonType.outline: + case ZetaButtonType.text: + return colors.primary; + case ZetaButtonType.outlineSubtle: + return colors.textDefault; + case ZetaButtonType.primary: + case ZetaButtonType.secondary: + case ZetaButtonType.positive: + case ZetaButtonType.negative: + return type.color(colors).onColor; + } + }, + ), + overlayColor: MaterialStateProperty.resolveWith((Set states) { + return null; + }), + side: MaterialStateProperty.resolveWith((Set states) { + if (type.border && states.contains(MaterialState.disabled)) { + return BorderSide(color: colors.cool.shade40); + } + // TODO(thelukewalton): This removes a defualt border when focused, rather than adding a second border when focused. + if (states.contains(MaterialState.focused)) { + return BorderSide(color: colors.blue, width: ZetaSpacing.x0_5); + } + if (type.border) { + return BorderSide(color: type == ZetaButtonType.outline ? colors.primary.border : colors.borderDefault); + } + + return null; + }), + elevation: const MaterialStatePropertyAll(0), + padding: MaterialStateProperty.all(EdgeInsets.zero), + ); + } + + TextStyle get _textStyle => size == ZetaWidgetSize.small ? ZetaTextStyles.labelMedium : ZetaTextStyles.labelLarge; + + double get _minConstraints { + switch (size) { + case ZetaWidgetSize.large: + return ZetaSpacing.x12; + + case ZetaWidgetSize.medium: + return ZetaSpacing.x10; + + case ZetaWidgetSize.small: + return ZetaSpacing.x8; + } + } + + double get _textPadding { + switch (size) { + case ZetaWidgetSize.large: + return ZetaSpacing.m; + + case ZetaWidgetSize.medium: + return ZetaSpacing.x3_5; + + case ZetaWidgetSize.small: + return ZetaSpacing.x2_5; + } + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(StringProperty('label', label)) + ..add(ObjectFlagProperty.has('onPressed', onPressed)) + ..add(EnumProperty('type', type)) + ..add(EnumProperty('borderType', borderType)) + ..add(EnumProperty('size', size)); + } +} diff --git a/lib/src/components/buttons/fab.dart b/lib/src/components/buttons/fab.dart new file mode 100644 index 00000000..2d7eb2b7 --- /dev/null +++ b/lib/src/components/buttons/fab.dart @@ -0,0 +1,211 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../zeta_flutter.dart'; + +/// Color type for [ZetaFAB]. +enum ZetaFabType { + /// Primary color scheme. Defaults to blue. + primary, + + /// Secondary color scheme. Defaults to yellow. + secondary, + + /// Inverse color scheme. Defaults to dark grey. + inverse +} + +///Defines the size of the floating action button +enum ZetaFabSize { + /// [small] 56 pixels + small, + + /// [large] 96 pixels + large, +} + +///Zeta Floating Action Button Component. +class ZetaFAB extends StatefulWidget { + ///Constructs [ZetaFAB]. + const ZetaFAB({ + required this.scrollController, + required this.label, + this.type = ZetaFabType.primary, + this.size = ZetaFabSize.small, + this.shape = ZetaWidgetBorder.full, + this.icon = ZetaIcons.add_round, + this.onPressed, + super.key, + }); + + /// Defines the color of the button. + /// + /// Defaults to [ZetaFabType.primary] + final ZetaFabType type; + + /// The Size of the button. + /// + /// Defaults to [ZetaFabSize.small]. + final ZetaFabSize size; + + /// The shape for the button. + /// + /// Defaults to [ZetaWidgetBorder.full]. + final ZetaWidgetBorder shape; + + /// Called when the button is tapped or otherwise activated. + final VoidCallback? onPressed; + + /// The [ZetaFAB] uses this controller to react to scroll change + /// and shrink/expand itself + final ScrollController scrollController; + + ///The label of the button + final String label; + + /// Icon for the button + /// + /// Defaults to [ZetaIcons.add_round]. + final IconData icon; + + @override + State createState() => _ZetaFABState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(EnumProperty('buttonType', type)) + ..add(EnumProperty('buttonSize', size)) + ..add(EnumProperty('buttonShape', shape)) + ..add(ObjectFlagProperty.has('onPressed', onPressed)) + ..add( + DiagnosticsProperty( + 'scrollController', + scrollController, + ), + ) + ..add(StringProperty('buttonLabel', label)) + ..add(DiagnosticsProperty('buttonIcon', icon)); + } +} + +class _ZetaFABState extends State { + bool _isExpanded = false; + + @override + void initState() { + super.initState(); + _isExpanded = false; + widget.scrollController.addListener(_scrollListener); + } + + void _scrollListener() { + final expanded = widget.scrollController.position.userScrollDirection == ScrollDirection.reverse; + if (_isExpanded != expanded) { + setState(() => _isExpanded = expanded); + } + } + + @override + void dispose() { + widget.scrollController.removeListener(_scrollListener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final colors = widget.type.colors(context); + final backgroundColor = widget.type == ZetaFabType.inverse ? colors.shade80 : colors.shade60; + + return ElevatedButton( + onPressed: widget.onPressed, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.zero, + shape: widget.shape.buttonShape(isExpanded: _isExpanded, size: widget.size), + backgroundColor: backgroundColor, + foregroundColor: backgroundColor.onColor, + ).copyWith( + overlayColor: MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.hovered)) return colors.hover; + if (states.contains(MaterialState.pressed)) return colors.selected; + return null; + }), + side: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.focused)) { + // TODO(thelukewalton): This removes a defualt border when focused, rather than adding a second border when focused. + return BorderSide(color: Zeta.of(context).colors.blue.shade50, width: ZetaSpacing.x0_5); + } + return null; + }, + ), + ), + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + child: Padding( + padding: _isExpanded + ? const EdgeInsets.symmetric(horizontal: ZetaSpacing.x3_5, vertical: ZetaSpacing.x3) + : EdgeInsets.all(widget.size.padding), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(widget.icon, size: widget.size.iconSize), + if (_isExpanded) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(widget.label, style: ZetaTextStyles.labelLarge), + ], + ), + ].divide(const SizedBox(width: ZetaSpacing.x2)).toList(), + ), + ), + ), + ); + } +} + +extension on ZetaFabType { + ZetaColorSwatch colors(BuildContext context) { + final zetaColors = Zeta.of(context).colors; + switch (this) { + case ZetaFabType.primary: + return zetaColors.primary; + case ZetaFabType.secondary: + return zetaColors.secondary; + case ZetaFabType.inverse: + return zetaColors.cool; + } + } +} + +extension on ZetaWidgetBorder { + OutlinedBorder buttonShape({required bool isExpanded, required ZetaFabSize size}) { + if (this == ZetaWidgetBorder.full && !isExpanded) { + return const CircleBorder(); + } + if (this == ZetaWidgetBorder.sharp) { + return const RoundedRectangleBorder(); + } + return RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + isExpanded + ? this == ZetaWidgetBorder.full + ? size == ZetaFabSize.small + ? ZetaSpacing.x7 + : ZetaSpacing.x12 + : ZetaSpacing.x2 + : size == ZetaFabSize.small + ? ZetaSpacing.x2 + : ZetaSpacing.x4, + ), + ); + } +} + +extension on ZetaFabSize { + double get iconSize => this == ZetaFabSize.small ? ZetaSpacing.x6 : ZetaSpacing.x9; + double get padding => this == ZetaFabSize.small ? ZetaSpacing.x4 : ZetaSpacing.x7_5; +} diff --git a/lib/src/components/checkbox.dart b/lib/src/components/checkbox.dart deleted file mode 100644 index 10a4c92b..00000000 --- a/lib/src/components/checkbox.dart +++ /dev/null @@ -1,201 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import '../../zeta_flutter.dart'; - -/// Zeta Checkbox border type -enum BorderType { - ///sharp border - sharp, - - ///rounded border - rounded, -} - -///Zeta Checkbox -class ZetaCheckbox extends StatelessWidget { - /// Constructs a [ZetaCheckbox]. - const ZetaCheckbox({ - required this.value, - required this.onChanged, - this.borderType = BorderType.sharp, - this.label, - this.labelStyle, - this.checkboxSize = const Size(25, 25), - this.selectedColor, - this.unselectedColor, - this.unselectedBorderColor, - this.unselectedBorderWidth = 2.5, - this.disabledColor, - this.isEnabled = true, - this.iconSize = 18.0, - super.key, - }) : assert(iconSize > 0, 'Icon size must be greater than 0'); - - /// Whether the checkbox is selected, unselected or null (indeterminate) - final bool? value; - - /// Called when the value of the checkbox should change. - final ValueChanged onChanged; - - /// The type of border to display - /// - /// defaults to sharp - final BorderType borderType; - - /// The label displayed next to the checkbox - final String? label; - - /// Style to use on the label - final TextStyle? labelStyle; - - ///Size of the checkbox - final Size checkboxSize; - - /// The color to use when this checkbox is checked. - final Color? selectedColor; - - ///Color of the checkbox when it's not selected - final Color? unselectedColor; - - ///Color of the border when the checkbox not selected - final Color? unselectedBorderColor; - - ///Width of the border when the checkbox is not selected - /// - /// Defaults to 2.5 - final double unselectedBorderWidth; - - ///Size of the icon displayed inside the checkbox - final double iconSize; - - ///If checkbox is enabled - /// - ///defaults to true - final bool isEnabled; - - ///Color of the checkbox when it's disabled - final Color? disabledColor; - - @override - Widget build(BuildContext context) { - final theme = Zeta.of(context); - final ValueNotifier isFocused = ValueNotifier(false); - - return FocusableActionDetector( - onFocusChange: (bool focus) => isFocused.value = focus, - child: GestureDetector( - onTap: _handleOnTap, - child: _buildCheckboxRow(theme, isFocused), - ), - ); - } - - void _handleOnTap() { - if (isEnabled) { - onChanged( - value == null - ? true - : value! - ? false - : null, - ); - } - } - - Widget _buildCheckboxRow( - Zeta theme, - ValueNotifier isFocused, - ) { - return Flex( - direction: Axis.horizontal, - mainAxisSize: MainAxisSize.min, - children: [ - _buildCheckboxContainer(theme, isFocused), - if (label != null) ...[ - Flexible( - child: Padding( - padding: const EdgeInsets.only(left: Dimensions.s), - child: Text( - label!, - style: labelStyle ?? ZetaText.zetaTitleMedium, - ), - ), - ), - ], - ], - ); - } - - Widget _buildCheckboxContainer(Zeta theme, ValueNotifier isFocused) { - return ValueListenableBuilder( - valueListenable: isFocused, - builder: (context, focused, child) { - return Container( - decoration: BoxDecoration( - boxShadow: _getBoxShadow(theme, focused), - color: _getCheckboxBackgroundColor(theme), - border: Border.all( - color: _getCheckboxBorderColor(theme), - width: unselectedBorderWidth, - ), - borderRadius: BorderRadius.circular( - borderType == BorderType.rounded ? 4.0 : 0.0, - ), - ), - width: checkboxSize.width, - height: checkboxSize.height, - child: _getCheckboxIcon(theme), - ); - }, - ); - } - - List _getBoxShadow(Zeta theme, bool focused) { - if (!focused) return []; - return [ - BoxShadow( - spreadRadius: 3, - color: theme.colors.surfaceSelectedHovered, - ), - ]; - } - - Widget _getCheckboxIcon(Zeta theme) { - if (value == null) return const SizedBox.shrink(); - return Icon( - value! ? Icons.check : Icons.remove, - color: isEnabled ? theme.colors.white : theme.colors.textDisabled, - size: iconSize, - ); - } - - Color _getCheckboxBackgroundColor(Zeta theme) { - if (!isEnabled) return disabledColor ?? theme.colors.surfaceDisabled; - if (value == null) return unselectedColor ?? theme.colors.surfaceSecondary; - return selectedColor ?? theme.colors.primary; - } - - Color _getCheckboxBorderColor(Zeta theme) { - if (!isEnabled || value != null) return Colors.transparent; - return unselectedBorderColor ?? theme.colors.borderDefault; - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('value', value)) - ..add(ObjectFlagProperty>.has('onChanged', onChanged)) - ..add(EnumProperty('borderType', borderType)) - ..add(StringProperty('label', label)) - ..add(DiagnosticsProperty('labelStyle', labelStyle)) - ..add(DiagnosticsProperty('checkboxSize', checkboxSize)) - ..add(ColorProperty('selectedColor', selectedColor)) - ..add(ColorProperty('unselectedColor', unselectedColor)) - ..add(ColorProperty('unselectedBorderColor', unselectedBorderColor)) - ..add(DoubleProperty('unselectedBorderWidth', unselectedBorderWidth)) - ..add(ColorProperty('disabledColor', disabledColor)) - ..add(DiagnosticsProperty('isEnabled', isEnabled)) - ..add(DoubleProperty('iconSize', iconSize)); - } -} diff --git a/lib/src/components/checkbox/checkbox.dart b/lib/src/components/checkbox/checkbox.dart new file mode 100644 index 00000000..22a42060 --- /dev/null +++ b/lib/src/components/checkbox/checkbox.dart @@ -0,0 +1,243 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import '../../../zeta_flutter.dart'; + +/// Zeta Checkbox. +/// +/// Checkboxes allow users to select one or more items from a set. Checkboxes can turn an option on or off. +class ZetaCheckbox extends FormField { + /// Constructs a [ZetaCheckbox]. + ZetaCheckbox({ + super.key, + this.value = false, + this.label, + this.onChanged, + this.rounded = true, + this.useIndeterminate = false, + super.validator, + super.autovalidateMode, + super.restorationId, + }) : super( + initialValue: value, + enabled: onChanged != null, + builder: (field) { + return _Checkbox( + label: label, + onChanged: (changedValue) { + field.didChange(changedValue); + onChanged?.call(changedValue); + }, + rounded: rounded, + useIndeterminate: useIndeterminate, + value: value, + error: !field.isValid, + ); + }, + ); + + /// {@macro zeta-component-rounded} + final bool rounded; + + /// Whether the indeterminate state should be supported. + /// + /// Defaults to false. + final bool useIndeterminate; + + /// Whether the checkbox is selected, unselected or null (indeterminate) + final bool? value; + + /// Called when the value of the checkbox should change. + final ValueChanged? onChanged; + + /// The label displayed next to the checkbox + final String? label; + + @override + ZetaCheckboxFormFieldState createState() => ZetaCheckboxFormFieldState(); + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('isChecked', value)) + ..add(StringProperty('label', label)) + ..add(DiagnosticsProperty('useIndeterminate', useIndeterminate)) + ..add(DiagnosticsProperty('rounded', rounded)) + ..add(ObjectFlagProperty?>.has('onChanged', onChanged)); + } +} + +/// [FormFieldState] for [ZetaCheckbox]. +class ZetaCheckboxFormFieldState extends FormFieldState { + @override + ZetaCheckbox get widget => super.widget as ZetaCheckbox; +} + +class _Checkbox extends StatefulWidget { + const _Checkbox({ + this.value = false, + this.onChanged, + this.label, + this.rounded = true, + this.useIndeterminate = false, + this.error = false, + }); + + /// Whether the checkbox is selected, unselected or null (indeterminate) + final bool? value; + + /// Called when the value of the checkbox should change. + final ValueChanged? onChanged; + + /// The label displayed next to the checkbox + final String? label; + + /// {@macro zeta-component-rounded} + final bool rounded; + + /// Whether the indeterminate state should be supported. + /// + /// Defaults to false. + final bool useIndeterminate; + + final bool error; + + @override + State<_Checkbox> createState() => _CheckboxState(); + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('value', value)) + ..add(ObjectFlagProperty?>.has('onChanged', onChanged)) + ..add(StringProperty('label', label)) + ..add(DiagnosticsProperty('rounded', rounded)) + ..add(DiagnosticsProperty('useIndeterminate', useIndeterminate)) + ..add(DiagnosticsProperty('error', error)); + } +} + +class _CheckboxState extends State<_Checkbox> { + bool get _enabled => widget.onChanged != null; + + bool? get _value => widget.useIndeterminate ? widget.value : (widget.value ?? false); + + bool? get _updatedValue { + if (widget.useIndeterminate) { + if (widget.value == null) { + return false; + } else if (widget.value!) { + return null; + } else { + return true; + } + } else { + return !_value!; + } + } + + bool _isFocused = false; + bool _isHovered = false; + + @override + Widget build(BuildContext context) { + return Semantics( + checked: _value ?? false, + mixed: widget.useIndeterminate, + enabled: _enabled, + child: MouseRegion( + cursor: _enabled ? SystemMouseCursors.click : SystemMouseCursors.forbidden, + onEnter: (event) => setState(() => _isHovered = true), + onExit: (event) => setState(() => _isHovered = false), + child: _enabled + ? FocusableActionDetector( + onFocusChange: (bool focus) => setState(() => _isFocused = focus), + child: GestureDetector( + onTap: _enabled ? () => widget.onChanged?.call(_updatedValue) : null, + child: _buildContent(context), + ), + ) + : _buildContent(context), + ), + ); + } + + Flex _buildContent(BuildContext context) { + final theme = Zeta.of(context); + + final icon = _value != null && !_value! + ? const SizedBox.shrink() + : Icon( + _value != null + ? widget.rounded + ? ZetaIcons.check_round + : ZetaIcons.check_sharp + : widget.rounded + ? ZetaIcons.remove_round + : ZetaIcons.remove_sharp, + color: _enabled ? theme.colors.white : theme.colors.textDisabled, + size: ZetaSpacing.x3_5, + ); + + return Flex( + direction: Axis.horizontal, + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + boxShadow: [ + if (_isFocused && _enabled) + BoxShadow( + spreadRadius: 2, + blurStyle: BlurStyle.solid, + color: theme.colors.blue.shade50, + ), + ], + color: _getBackground(theme), + border: _enabled ? Border.all(color: _getBorderColor(theme), width: ZetaSpacing.x0_5) : null, + borderRadius: widget.rounded ? ZetaRadius.minimal : ZetaRadius.none, + ), + width: ZetaSpacing.x5, + height: ZetaSpacing.x5, + child: icon, + ), + if (widget.label != null) ...[ + Flexible( + child: Padding( + padding: const EdgeInsets.only(left: ZetaSpacing.s), + child: Text(widget.label!, style: ZetaTextStyles.bodyLarge), + ), + ), + ], + ], + ); + } + + Color _getBackground(Zeta theme) { + final ZetaColorSwatch color = widget.error ? theme.colors.error : theme.colors.primary; + + if (!_enabled || (_value != null && !_value!)) return theme.colors.surfacePrimary; + if (_isHovered) return color.hover; + + return color; + } + + Color _getBorderColor(Zeta theme) { + final ZetaColorSwatch color = widget.error ? theme.colors.error : theme.colors.cool; + + if (_isHovered) return color.shade90; + + return color; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('value', widget.value)) + ..add(ObjectFlagProperty>.has('onChanged', widget.onChanged)) + ..add(DiagnosticsProperty('rounded', widget.rounded)) + ..add(StringProperty('label', widget.label)) + ..add(DiagnosticsProperty('useIndeterminate', widget.useIndeterminate)); + } +} diff --git a/lib/src/components/chips/assist_chip.dart b/lib/src/components/chips/assist_chip.dart new file mode 100644 index 00000000..4dc7658d --- /dev/null +++ b/lib/src/components/chips/assist_chip.dart @@ -0,0 +1,14 @@ +import 'chip.dart'; + +/// Zeta Assist Chip. +/// +/// Extends [ZetaChip]. +class ZetaAssistChip extends ZetaChip { + /// Creates a [ZetaAssistChip]. + const ZetaAssistChip({ + super.key, + required super.label, + super.leading, + super.rounded, + }) : super(type: ZetaChipType.assist); +} diff --git a/lib/src/components/chips/chip.dart b/lib/src/components/chips/chip.dart new file mode 100644 index 00000000..26a006c9 --- /dev/null +++ b/lib/src/components/chips/chip.dart @@ -0,0 +1,177 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +export './assist_chip.dart'; +export './filter_chip.dart'; +export './input_chip.dart'; + +/// The type of [ZetaChip] +enum ZetaChipType { + /// Input Chip + input, + + /// Filter Chip + filter, + + /// Assist Chip + assist, +} + +/// Zeta Chip component. +/// +/// This covers the board functionality of [ZetaAssistChip], [ZetaFilterChip] and [ZetaInputChip]. +class ZetaChip extends StatefulWidget { + /// Constructs a [ZetaChip]. + const ZetaChip({ + super.key, + required this.label, + required this.type, + this.leading, + this.rounded = true, + this.trailing, + this.selected, + this.onTap, + }); + + /// Type of [Chip]. + final ZetaChipType type; + + /// The label on the [ZetaChip] + final String label; + + /// Leading component. Typically an [Icon]. + final Widget? leading; + + /// {@macro zeta-component-rounded} + final bool rounded; + + /// Trailing component. Typically an [Icon]. + final Widget? trailing; + + /// Whether the [ZetaFilterChip] is selected. + final bool? selected; + + /// Callback when chip is tapped. + final VoidCallback? onTap; + + @override + State createState() => _ZetaChipState(); + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(EnumProperty('type', type)) + ..add(StringProperty('label', label)) + ..add(DiagnosticsProperty('rounded', rounded)) + ..add(DiagnosticsProperty('selected', selected)) + ..add(ObjectFlagProperty.has('onTap', onTap)); + } +} + +class _ZetaChipState extends State { + bool selected = false; + + Widget? get leading => + widget.leading ?? (selected ? Icon(widget.rounded ? ZetaIcons.check_round : ZetaIcons.check_sharp) : null); + + @override + void initState() { + super.initState(); + selected = widget.selected ?? false; + } + + @override + Widget build(BuildContext context) { + final colors = Zeta.of(context).colors; + final foregroundColor = selected ? colors.textInverse : colors.textDefault; + return FilledButton( + onPressed: () { + if (widget.type == ZetaChipType.filter) { + setState(() => selected = !selected); + } + }, + style: ButtonStyle( + shape: MaterialStateProperty.all( + RoundedRectangleBorder(borderRadius: widget.rounded ? ZetaRadius.full : ZetaRadius.none), + ), + textStyle: MaterialStateProperty.all(ZetaTextStyles.bodyMedium), + backgroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.disabled)) { + return colors.surfaceDisabled; + } + if (selected) { + return colors.cool.shade90; + } + if (states.contains(MaterialState.pressed) || states.contains(MaterialState.dragged)) { + return colors.surfaceSelected; + } + + if (states.contains(MaterialState.hovered)) { + return colors.surfaceHovered; + } + + return colors.surfacePrimary; + }), + foregroundColor: MaterialStateProperty.all(foregroundColor), + mouseCursor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.disabled)) { + return SystemMouseCursors.forbidden; + } + if (states.contains(MaterialState.dragged)) { + return SystemMouseCursors.grabbing; + } + return SystemMouseCursors.click; + }), + elevation: MaterialStateProperty.all(0), + side: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.focused)) { + return BorderSide(width: ZetaSpacing.x0_5, color: colors.blue.shade50); + } + return BorderSide(color: colors.borderDefault); + }), + padding: MaterialStateProperty.all( + EdgeInsets.fromLTRB( + widget.leading != null ? ZetaSpacing.x2_5 : ZetaSpacing.x3, + 0, + widget.trailing != null ? ZetaSpacing.x2_5 : ZetaSpacing.x3, + 0, + ), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (leading != null) + IconTheme( + data: IconThemeData( + color: foregroundColor, + size: ZetaSpacing.x5, + ), + child: leading!, + ), + Text(widget.label), + if (widget.trailing != null) + IconTheme( + data: IconThemeData( + color: foregroundColor, + size: ZetaSpacing.x5, + ), + child: widget.trailing!, + ), + ].divide(const SizedBox.square(dimension: ZetaSpacing.x2)).toList(), + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('rounded', widget.rounded)) + ..add(StringProperty('label', widget.label)) + ..add(EnumProperty('type', widget.type)) + ..add(DiagnosticsProperty('selected', widget.selected)); + } +} diff --git a/lib/src/components/chips/filter_chip.dart b/lib/src/components/chips/filter_chip.dart new file mode 100644 index 00000000..bcb08f4e --- /dev/null +++ b/lib/src/components/chips/filter_chip.dart @@ -0,0 +1,14 @@ +import 'chip.dart'; + +/// Zeta Filter Chip. +/// +/// Extends [ZetaChip]. +class ZetaFilterChip extends ZetaChip { + /// Creates a [ZetaInputChip]. + const ZetaFilterChip({ + super.key, + required super.label, + super.rounded, + super.selected, + }) : super(type: ZetaChipType.filter); +} diff --git a/lib/src/components/chips/input_chip.dart b/lib/src/components/chips/input_chip.dart new file mode 100644 index 00000000..896af5b3 --- /dev/null +++ b/lib/src/components/chips/input_chip.dart @@ -0,0 +1,15 @@ +import 'chip.dart'; + +/// Zeta Input Chip. +/// +/// Extends [ZetaChip]. +class ZetaInputChip extends ZetaChip { + /// Creates a [ZetaInputChip]. + const ZetaInputChip({ + super.key, + required super.label, + super.leading, + super.rounded, + super.trailing, + }) : super(type: ZetaChipType.input); +} diff --git a/lib/src/components/grid.dart b/lib/src/components/grid.dart deleted file mode 100644 index d41cce71..00000000 --- a/lib/src/components/grid.dart +++ /dev/null @@ -1,156 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import '../theme/breakpoints.dart'; -import '../tokens.dart' as tokens; - -import '../utils/extensions.dart'; - -extension _Spacing on DeviceType { - num maxCrossAxisCount(num col) { - switch (this) { - case DeviceType.mobilePortrait: - return tokens.Grid.mobilePortraitCount; - case DeviceType.mobileLandscape: - return tokens.Grid.mobileLandscapeCount; - case DeviceType.tablet: - return col == tokens.Grid.maxCols ? tokens.Grid.tabletCount : col; - case DeviceType.desktop: - case DeviceType.desktopL: - case DeviceType.desktopXL: - return col; - } - } - - double axisSpacing(double col) { - switch (this) { - case DeviceType.mobilePortrait: - case DeviceType.mobileLandscape: - return tokens.Dimensions.x2; - case DeviceType.tablet: - case DeviceType.desktop: - case DeviceType.desktopL: - case DeviceType.desktopXL: - return col == tokens.Grid.maxCols ? tokens.Dimensions.x3 : tokens.Dimensions.x4; - } - } -} - -/// Zeta Grid. -class ZetaGrid extends StatelessWidget { - /// Constructs a [ZetaGrid]. - const ZetaGrid({ - required this.children, - this.col = tokens.Grid.defaultCols, - this.noGaps = false, - this.asymmetricWeight, - this.hybrid = false, - super.key, - }) : assert( - asymmetricWeight == null || (asymmetricWeight > 0 && asymmetricWeight < tokens.Grid.defaultCols), - 'If defined, asymmetricWeight should be in the range 1-11', - ); - - /// Number of columns in grid. Should be an even number between 2 and 16, although values above 12 should be used sparingly. - /// - /// Defaults to 12. - final double col; - - /// Removes gutters (gaps) from between children. - /// - /// Defaults to false. - final bool noGaps; - - /// `Required` list of children to be placed into the grid. - final List children; - - /// If not null, creates a 2 column asymmetric grid, where the first item has this weight. - /// - /// Example: `asymmetricWeight: 1` will create a grid with 2 columns, where the first has a width of 1/12 and the second has a width of 11/12. - final int? asymmetricWeight; - - /// If the Grid should allow custom child widths. - /// - /// Custom sized widgets can be mixed with Flexible wrapped widgets. - /// - /// This is potentially dangerous as children could exceed screen width, causing overflow errors. - /// - /// Defaults to false. - final bool hybrid; - - /// Util to return the smaller of 2 values. - num returnSmaller(num in1, num in2) { - return in1 > in2 ? in2 : in1; - } - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (context, constraints) { - final DeviceType deviceType = constraints.deviceType; - final crossAxisCount = returnSmaller(deviceType.maxCrossAxisCount(col), col); - final divider = (children.length / crossAxisCount).ceil(); - final List rows = []; - final double gutterSize = noGaps ? tokens.Dimensions.x0 : deviceType.axisSpacing(col); - final Widget gutter = SizedBox(width: gutterSize, height: gutterSize); - final Widget widget; - List children2 = [...children]; - - if (hybrid) { - if (children.length < col) { - children2 = [...children, ...List.generate((col - children.length).toInt(), (index) => const SizedBox())]; - } else if (children.length > col) { - children2 = children.getRange(0, col.toInt()).toList(); - } else { - children2 = children; - } - widget = Row( - children: [ - Expanded(child: Row(children: children2.divide(gutter).toList())), - ], - ); - } else if (asymmetricWeight != null) { - widget = Row( - children: [ - Flexible(flex: asymmetricWeight ?? 1, fit: FlexFit.tight, child: children.first), - gutter, - Flexible( - flex: (tokens.Grid.defaultCols - (asymmetricWeight ?? 1)).toInt(), - fit: FlexFit.tight, - child: children[1], - ), - ], - ); - } else { - if (children2.length % col != 0) { - children2.addAll(List.generate((col - (children.length % col)).toInt(), (index) => const SizedBox())); - } - for (int i = 0; i < divider; i++) { - rows.add( - Row( - children: children2 - .sublist((i * crossAxisCount).toInt(), ((children2.length / divider) * (i + 1)).round()) - .map((e) => Expanded(child: e)) - .divide(gutter) - .toList(), - ), - ); - } - widget = Column(children: rows.divide(gutter).toList()); - } - - return Padding(padding: const EdgeInsets.all(tokens.Grid.gridMargin), child: widget); - }, - ); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('hybrid', hybrid)) - ..add(DoubleProperty('col', col)) - ..add(DiagnosticsProperty('noGaps', noGaps)) - ..add(IntProperty('asymmetricWeight', asymmetricWeight)); - } -} diff --git a/lib/src/components/password/password_input.dart b/lib/src/components/password/password_input.dart new file mode 100644 index 00000000..59f508a7 --- /dev/null +++ b/lib/src/components/password/password_input.dart @@ -0,0 +1,244 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +///Extension for password visibility state handling +extension on ValueNotifier { + void toggle() => value = !value; +} + +///Zeta Password Input +class ZetaPasswordInput extends StatefulWidget { + ///Constructs [ZetaPasswordInput] + const ZetaPasswordInput({ + this.size = ZetaWidgetSize.large, + this.validator, + this.onChanged, + this.obscureText = true, + this.enabled = true, + this.controller, + this.hintText, + this.label, + this.footerText, + this.footerIcon, + this.rounded = false, + super.key, + }); + + /// Controls the text being edited. + final TextEditingController? controller; + + /// Whether the text is obscured. Useful for passwords. Defaults to true. + final bool obscureText; + + /// Text that suggests what sort of input the field accepts. + final String? hintText; + + /// Whether the input field is enabled or disabled. Defaults to true. + final bool enabled; + + /// Optional label text to display above the input field. + final String? label; + + /// Optional footer text to display below the input field. + final String? footerText; + + /// Optional icon to display beside the footer text. + final IconData? footerIcon; + + /// {@macro zeta-component-rounded} + final bool rounded; + + /// Defines the size of the input field. Can be [ZetaWidgetSize.small], [ZetaWidgetSize.medium], or [ZetaWidgetSize.large]. + /// Defaults to [ZetaWidgetSize.large]. + final ZetaWidgetSize size; + + /// An optional method that validates an input. Returns an error string to + /// display if the input is invalid, or null otherwise. + final String? Function(String?)? validator; + + /// Called when the user initiates a change to the [ZetaPasswordInput] + final void Function(String)? onChanged; + + @override + State createState() => _ZetaPasswordInputState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add( + DiagnosticsProperty('controller', controller), + ) + ..add(DiagnosticsProperty('obscureText', obscureText)) + ..add(StringProperty('hintText', hintText)) + ..add(DiagnosticsProperty('enabled', enabled)) + ..add(StringProperty('label', label)) + ..add(StringProperty('footerText', footerText)) + ..add(DiagnosticsProperty('footerIcon', footerIcon)) + ..add( + ObjectFlagProperty.has( + 'validator', + validator, + ), + ) + ..add( + ObjectFlagProperty.has( + 'onChanged', + onChanged, + ), + ) + ..add(EnumProperty('size', size)) + ..add(DiagnosticsProperty('rounded', rounded)); + } +} + +class _ZetaPasswordInputState extends State { + late final ValueNotifier _obscureTextNotifier; + String? _errorMessage; + + @override + void initState() { + super.initState(); + _obscureTextNotifier = ValueNotifier(widget.obscureText); + widget.controller?.addListener(_validate); + } + + void _validate() { + if (widget.validator != null && widget.controller != null) { + setState(() => _errorMessage = widget.validator!(widget.controller!.text)); + } + } + + @override + void dispose() { + _obscureTextNotifier.dispose(); + widget.controller?.removeListener(_validate); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final theme = Zeta.of(context); + final defaultBorderColor = _errorMessage != null ? theme.colors.error.border : theme.colors.borderDefault; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.label != null) + Padding( + padding: const EdgeInsets.symmetric(vertical: ZetaSpacing.x1), + child: Text(widget.label!, style: ZetaTextStyles.bodyMedium), + ), + SizedBox( + height: _inputHeight, + child: ValueListenableBuilder( + valueListenable: _obscureTextNotifier, + builder: (context, obscureValue, child) { + return TextFormField( + controller: widget.controller, + obscureText: obscureValue, + onChanged: widget.onChanged, + style: _textStyle, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(horizontal: ZetaSpacing.x2), + filled: true, + fillColor: _getBackgroundColor(theme.colors), + enabledBorder: _getBorder(defaultBorderColor), + focusedBorder: _getBorder( + _errorMessage != null ? theme.colors.error.border : theme.colors.primary.border, + width: ZetaSpacing.x0_5, + ), + disabledBorder: _getBorder(theme.colors.borderDefault), + border: _getBorder(defaultBorderColor), + enabled: widget.enabled, + hintText: widget.hintText, + hintStyle: _textStyle, + suffixIcon: ValueListenableBuilder( + valueListenable: _obscureTextNotifier, + builder: (context, value, child) { + return IconButton( + padding: const EdgeInsets.symmetric(vertical: ZetaSpacing.x1), + icon: Icon( + value ? ZetaIcons.visibility_off_sharp : ZetaIcons.visibility_sharp, + size: widget.size == ZetaWidgetSize.small ? ZetaSpacing.x4 : ZetaSpacing.x5, + ), + color: widget.enabled ? theme.colors.iconDefault : theme.colors.iconDisabled, + onPressed: () => _obscureTextNotifier.toggle(), + ); + }, + ), + ), + ); + }, + ), + ), + if (widget.footerText != null || widget.footerIcon != null || _errorMessage != null) + Padding( + padding: const EdgeInsets.only(top: ZetaSpacing.x1), + child: Row( + children: [ + if (_errorMessage != null) ...[ + Icon( + ZetaIcons.error_round, + size: ZetaSpacing.x4, + color: theme.colors.error.border, + ), + const SizedBox(width: ZetaSpacing.x1), + Text( + _errorMessage!, + style: ZetaTextStyles.bodySmall.apply(color: theme.colors.error.border), + ), + ], + if (_errorMessage == null && widget.footerIcon != null) ...[ + Icon( + widget.footerIcon, + size: ZetaSpacing.x4, + color: widget.enabled ? theme.colors.iconDefault : theme.colors.iconDisabled, + ), + const SizedBox(width: ZetaSpacing.x1), + ], + if (_errorMessage == null && widget.footerText != null) ...[ + Text( + widget.footerText!, + style: ZetaTextStyles.bodySmall.apply( + color: widget.enabled ? theme.colors.textSubtle : theme.colors.textDefault, + ), + ), + ], + ], + ), + ), + ], + ); + } + + OutlineInputBorder _getBorder(Color color, {double width = 1}) { + return OutlineInputBorder( + borderSide: BorderSide(color: color, width: width), + borderRadius: widget.rounded ? ZetaRadius.minimal : ZetaRadius.none, + ); + } + + Color _getBackgroundColor(ZetaColors zetaColors) { + if (!widget.enabled) return zetaColors.surfaceDisabled; + if (_errorMessage != null) return zetaColors.error.surface; + return zetaColors.surfacePrimary; + } + + double get _inputHeight { + switch (widget.size) { + case ZetaWidgetSize.small: + return ZetaSpacing.x8; + case ZetaWidgetSize.medium: + return ZetaSpacing.x10; + case ZetaWidgetSize.large: + return ZetaSpacing.x12; + } + } + + TextStyle get _textStyle => + (widget.size == ZetaWidgetSize.small ? ZetaTextStyles.bodyMedium : ZetaTextStyles.bodyLarge) + .copyWith(height: 1.2); +} diff --git a/lib/src/components/spacing.dart b/lib/src/components/spacing.dart deleted file mode 100644 index f58138d6..00000000 --- a/lib/src/components/spacing.dart +++ /dev/null @@ -1,191 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; - -import '../tokens.dart'; - -/// Spacing types for [ZetaSpacing]. - -enum ZetaSpacingType { - /// {@template zeta-spacing-square} - /// Identical spacing on all four sides. - /// - /// It’s use is widespread, across many components at varying sizes. - /// {@endtemplate} - square, - - /// {@template zeta-spacing-squish} - /// Identical padding on top and bottom, nothing on sides. - /// - /// While less common than its squared counterpart, a squish occurred frequently in elements (like a button) and cell-like containers like a data table or list item. - /// {@endtemplate} - squish, - - /// {@template zeta-spacing-stack} - /// Padding on bottom only. - /// - /// With all due respect to horizontally scrolled UI, the overwhelming majority scroll vertically. And that means one thing, we stack things. We stack message on heading on data table. We stack modules in rails. We stack copy, pills & toolbars, all in a card, each in a grid. Heck, infinite scroll means infinite stack! We stack, stack, stack. - /// {@endtemplate} - stack, - - /// {@template zeta-spacing-inline-only} - /// Padding on start and end only. - /// {@endtemplate} - /// {@template zeta-spacing-inline} - /// We arrange objects inline, wrapping as they flow like text from the left or right. Such objects — pills, tags, breadcrumbs, and more — may stand alone or stack and mingle with other objects. - /// {@endtemplate} - inline, - - /// {@template zeta-spacing-inline-start} - /// Padding on start only. - /// - /// {@macro zeta-spacing-inline} - /// - /// {@endtemplate} - inlineStart, - - /// {@template zeta-spacing-inline-end} - /// Padding on end only. - /// {@endtemplate} - inlineEnd, -} - -/// Zeta Spacing widget. -class ZetaSpacing extends StatelessWidget { - /// Constructs [ZetaSpacing]. - const ZetaSpacing( - this.child, { - this.type = ZetaSpacingType.square, - this.size = Dimensions.x0, - super.key, - }) : assert( - size % _mod == 0 && size >= 0 && size <= Dimensions.x24, - 'Size should be a whole, even number, and be no larger than [x24]', - ); - - /// Constructs a [ZetaSpacing] widget with [ZetaSpacingType.square] insets. - /// - /// {@macro zeta-spacing-square} - const ZetaSpacing.square(this.child, {this.size = Dimensions.x0, super.key}) : type = ZetaSpacingType.square; - - /// Constructs a [ZetaSpacing] widget with [ZetaSpacingType.squish] insets. - /// - /// {@macro zeta-spacing-squish} - const ZetaSpacing.squish(this.child, {this.size = Dimensions.x0, super.key}) : type = ZetaSpacingType.squish; - - /// Constructs a [ZetaSpacing] widget with [ZetaSpacingType.stack] insets. - /// - /// {@macro zeta-spacing-stack} - const ZetaSpacing.stack(this.child, {this.size = Dimensions.x0, super.key}) : type = ZetaSpacingType.stack; - - /// Constructs a [ZetaSpacing] widget with [ZetaSpacingType.inline] insets. - /// - /// {@macro zeta-spacing-inline-only} - /// - /// {@macro zeta-spacing-inline} - const ZetaSpacing.inline(this.child, {this.size = Dimensions.x0, super.key}) : type = ZetaSpacingType.inline; - - /// Constructs a [ZetaSpacing] widget with [ZetaSpacingType.inlineStart] insets. - /// - /// {@macro zeta-spacing-inline-start} - /// - /// {@macro zeta-spacing-inline} - const ZetaSpacing.inlineStart(this.child, {this.size = Dimensions.x0, super.key}) - : type = ZetaSpacingType.inlineStart; - - /// Constructs a [ZetaSpacing] widget with [ZetaSpacingType.inlineEnd] insets. - /// - /// {@macro zeta-spacing-inline-end} - /// - /// {@macro zeta-spacing-inline} - const ZetaSpacing.inlineEnd(this.child, {this.size = Dimensions.x0, super.key}) : type = ZetaSpacingType.inlineEnd; - static const double _mod = 2; - - /// Child to be wrapped with spacing insets. - final Widget child; - - /// [ZetaSpacingType] insets applied to [child]. - /// - /// Defaults to [ZetaSpacingType.square]. - final ZetaSpacingType? type; - - /// Size of insets to be applied around [child]. - /// - /// Should be an even number, and be no larger than [Dimensions.x24]. - /// - /// Defaults to [Dimensions.x0]. - final double size; - - @override - Widget build(BuildContext context) { - switch (type ?? ZetaSpacingType.square) { - case ZetaSpacingType.square: - return child.square(size); - case ZetaSpacingType.squish: - return child.squish(size); - case ZetaSpacingType.stack: - return child.stack(size); - case ZetaSpacingType.inline: - return child.inline(size); - case ZetaSpacingType.inlineStart: - return child.inlineStart(size); - case ZetaSpacingType.inlineEnd: - return child.inlineEnd(size); - } - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(EnumProperty('type', type)) - ..add(DoubleProperty('size', size)); - } -} - -/// Extension to get Spacing type as [EdgeInsets] from a double. -extension SpacingSize on double { - /// {@macro zeta-spacing-square} - EdgeInsets get square => EdgeInsets.all(this); - - /// {@macro zeta-spacing-squish} - EdgeInsets get squish => EdgeInsets.symmetric(vertical: this); - - /// {@macro zeta-spacing-stack} - EdgeInsets get stack => EdgeInsets.only(bottom: this); - - /// {@macro zeta-spacing-inline} - EdgeInsetsDirectional get inline => EdgeInsetsDirectional.only(start: this, end: this); - - /// {@macro zeta-spacing-inline-start} - EdgeInsetsDirectional get inlineStart => EdgeInsetsDirectional.only(start: this); - - /// {@macro zeta-spacing-inline-end} - EdgeInsetsDirectional get inlineEnd => EdgeInsetsDirectional.only(end: this); -} - -/// Extension to add spacing to any [Widget]. -extension SpacingWidget on Widget { - /// {@macro zeta-spacing-square} - Widget square(double space) => Padding(padding: space.square, child: this); - - /// {@macro zeta-spacing-squish} - Widget squish(double space) => Padding(padding: space.squish, child: this); - - /// {@macro zeta-spacing-stack} - Widget stack(double space) => Padding(padding: space.stack, child: this); - - /// {@macro zeta-spacing-inline-only} - /// - /// {@macro zeta-spacing-inline} - Widget inline(double space) => Padding(padding: space.inline, child: this); - - /// {@macro zeta-spacing-inline-end} - /// - /// {@macro zeta-spacing-inline} - Widget inlineStart(double space) => Padding(padding: space.inlineStart, child: this); - - /// {@macro zeta-spacing-inline-end} - /// - /// {@macro zeta-spacing-inline} - Widget inlineEnd(double space) => Padding(padding: space.inlineEnd, child: this); -} diff --git a/lib/src/components/text.dart b/lib/src/components/text.dart deleted file mode 100644 index 4132446e..00000000 --- a/lib/src/components/text.dart +++ /dev/null @@ -1,772 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import '../../zeta_flutter.dart'; -import '../tokens.dart' as tokens; - -/// {@template zeta-component-text} -/// ZetaText component. -/// -/// Applies Zeta style to text, including fontFamily, size, lineHeight, spacing and other modifiers. -/// -/// {@endtemplate} -/// See also: -/// * [Text]. -class ZetaText extends StatelessWidget { - /// Constructor for [ZetaText]. - const ZetaText( - this.data, { - this.style, - this.resetHeight = false, - this.textColor, - this.fontSize, - this.maxWidth, - this.fontWeight, - this.fontStyle, - this.upperCase = false, - this.decoration, - this.textDirection = TextDirection.ltr, - this.first = false, - this.last = false, - super.key, - }); - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-body-xs} - ZetaText.bodyXSmall( - this.data, { - this.resetHeight = false, - this.maxWidth, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - super.key, - }) : style = zetaBodyXSmall; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-body-s} - ZetaText.bodySmall( - this.data, { - this.resetHeight = false, - this.maxWidth, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - super.key, - }) : style = zetaBodySmall; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-body-m} - ZetaText.bodyMedium( - this.data, { - this.resetHeight = false, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.maxWidth, - super.key, - }) : style = zetaBodyMedium; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-body-l} - ZetaText.bodyLarge( - this.data, { - this.resetHeight = false, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.maxWidth, - super.key, - }) : style = zetaBodyLarge; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-label-s} - ZetaText.labelSmall( - this.data, { - this.resetHeight = false, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - super.key, - this.maxWidth, - }) : style = zetaLabelSmall; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-label-m} - ZetaText.labelMedium( - this.data, { - this.resetHeight = false, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.maxWidth, - super.key, - }) : style = zetaLabelMedium; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-label-l} - ZetaText.labelLarge( - this.data, { - this.resetHeight = false, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.maxWidth, - super.key, - }) : style = zetaLabelLarge; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-title-s} - ZetaText.titleSmall( - this.data, { - this.resetHeight = false, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.maxWidth, - super.key, - }) : style = zetaTitleSmall; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-title-m} - ZetaText.titleMedium( - this.data, { - this.resetHeight = false, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.maxWidth, - super.key, - }) : style = zetaTitleMedium; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-title-l} - ZetaText.titleLarge( - this.data, { - this.resetHeight = false, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.maxWidth, - super.key, - }) : style = zetaTitleLarge; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-heading-s} - ZetaText.headingSmall( - this.data, { - this.resetHeight = false, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.maxWidth, - super.key, - }) : style = zetaHeadingSmall; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-heading-m} - ZetaText.headingMedium( - this.data, { - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.resetHeight = false, - this.maxWidth, - super.key, - }) : style = zetaHeadingMedium; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-heading-l} - ZetaText.headingLarge( - this.data, { - this.resetHeight = false, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.maxWidth, - super.key, - }) : style = zetaHeadingLarge; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-display-s} - ZetaText.displaySmall( - this.data, { - this.resetHeight = false, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.maxWidth, - super.key, - }) : style = zetaDisplaySmall; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-display-m} - ZetaText.displayMedium( - this.data, { - this.resetHeight = false, - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.maxWidth, - super.key, - }) : style = zetaDisplayMedium; - - /// {@macro zeta-component-text} - /// - /// {@macro zeta-type-display-l} - ZetaText.displayLarge( - this.data, { - this.decoration, - this.fontSize, - this.fontStyle, - this.fontWeight, - this.first = false, - this.last = false, - this.textColor, - this.textDirection = TextDirection.ltr, - this.upperCase = false, - this.resetHeight = false, - this.maxWidth, - super.key, - }) : style = zetaDisplayLarge; - static const double _defaultChMultiplier = 66; - - /// Text styles for Zeta. - /// - /// {@macro zeta-theme} - static TextTheme textTheme = TextTheme( - displayLarge: zetaDisplayLarge, - displayMedium: zetaDisplayMedium, - displaySmall: zetaDisplaySmall, - headlineLarge: zetaHeadingLarge, - headlineMedium: zetaHeadingMedium, - headlineSmall: zetaHeadingSmall, - titleLarge: zetaTitleLarge, - titleMedium: zetaTitleMedium, - titleSmall: zetaTitleSmall, - bodyLarge: zetaBodyLarge, - bodyMedium: zetaBodyMedium, - bodySmall: zetaBodySmall, - labelLarge: zetaLabelLarge, - labelMedium: zetaLabelMedium, - labelSmall: zetaLabelSmall, - ); - - /// Builds text theme for app based on an instance of [ZetaColors]. - static TextTheme themeWithColor(Color color) { - return TextTheme( - displayLarge: zetaDisplayLarge.copyWith(color: color), - displayMedium: zetaDisplayMedium.copyWith(color: color), - displaySmall: zetaDisplaySmall.copyWith(color: color), - headlineLarge: zetaHeadingLarge.copyWith(color: color), - headlineMedium: zetaHeadingMedium.copyWith(color: color), - headlineSmall: zetaHeadingSmall.copyWith(color: color), - titleLarge: zetaTitleLarge.copyWith(color: color), - titleMedium: zetaTitleMedium.copyWith(color: color), - titleSmall: zetaTitleSmall.copyWith(color: color), - bodyLarge: zetaBodyLarge.copyWith(color: color), - bodyMedium: zetaBodyMedium.copyWith(color: color), - bodySmall: zetaBodySmall.copyWith(color: color), - labelLarge: zetaLabelLarge.copyWith(color: color), - labelMedium: zetaLabelMedium.copyWith(color: color), - labelSmall: zetaLabelSmall.copyWith(color: color), - ); - } - - /// {@template zeta-type-body-xs} - /// Smallest body text. - /// - /// Used for UI components and UI content design. - /// - /// {@endtemplate} - static TextStyle zetaBodyXSmall = const TextStyle( - fontSize: tokens.Dimensions.x3, - fontWeight: FontWeight.w400, - height: tokens.Dimensions.x3 / tokens.Dimensions.x4, - ); - - /// {@template zeta-type-body-s} - /// Small body text. - /// - /// Used for UI components and UI content design. - /// - /// See also: - /// * [TextTheme.bodySmall]. - /// {@endtemplate} - static TextStyle zetaBodySmall = const TextStyle( - fontSize: tokens.Dimensions.x3_5, - fontWeight: FontWeight.w400, - height: 18 / 14, - ); - - /// {@template zeta-type-body-m} - /// Medium body text. - /// - /// Used for overall content. - /// - /// See also: - /// * [TextTheme.bodyMedium]. - /// {@endtemplate} - static TextStyle zetaBodyMedium = const TextStyle( - fontSize: tokens.Dimensions.x4, - fontWeight: FontWeight.w400, - height: tokens.Dimensions.x6 / tokens.Dimensions.x4, - ); - - /// {@template zeta-type-body-l} - /// Large body text. - /// - /// Used for UI components and UI content design. - /// - /// See also: - /// * [TextTheme.bodyLarge]. - /// {@endtemplate} - static TextStyle zetaBodyLarge = const TextStyle( - fontSize: tokens.Dimensions.x5, - fontWeight: FontWeight.w400, - height: tokens.Dimensions.x6 / tokens.Dimensions.x5, - ); - - /// {@template zeta-type-label-s} - /// Small label text. - /// - /// Used for UI components and UI content. - /// - /// See also: - /// * [TextTheme.labelSmall]. - /// {@endtemplate} - static TextStyle zetaLabelSmall = const TextStyle( - fontSize: tokens.Dimensions.x3, - fontWeight: FontWeight.w500, - height: tokens.Dimensions.x4 / tokens.Dimensions.x3, - ); - - /// {@template zeta-type-label-m} - /// Medium label text. - /// - /// Used for UI components and UI content. - /// - /// See also: - /// * [TextTheme.labelMedium]. - /// {@endtemplate} - static TextStyle zetaLabelMedium = const TextStyle( - fontSize: tokens.Dimensions.x3_5, - fontWeight: FontWeight.w500, - height: tokens.Dimensions.x3_5 / tokens.Dimensions.x5, - ); - - /// {@template zeta-type-label-l} - /// Large label text. - /// - /// Used for UI components and UI content. - /// - /// See also: - /// * [TextTheme.labelLarge]. - /// {@endtemplate} - static TextStyle zetaLabelLarge = const TextStyle( - fontSize: tokens.Dimensions.x4, - fontWeight: FontWeight.w500, - height: tokens.Dimensions.x4 / tokens.Dimensions.x6, - ); - - /// {@template zeta-type-title-s} - /// Heading 6 / Small title text. - /// - /// Used for UI components and UI content design. - /// - /// See also: - /// * [TextTheme.titleSmall]. - /// {@endtemplate} - static TextStyle zetaTitleSmall = const TextStyle( - fontSize: tokens.Dimensions.x3, - fontWeight: FontWeight.w500, - height: tokens.Dimensions.x4 / tokens.Dimensions.x3, - ); - - /// {@template zeta-type-title-m} - /// Heading 5 / Medium title text. - /// - /// Used for UI components and UI content design. - /// - /// See also: - /// * [TextTheme.titleMedium]. - /// {@endtemplate} - static TextStyle zetaTitleMedium = const TextStyle( - fontSize: tokens.Dimensions.x4, - fontWeight: FontWeight.w500, - height: tokens.Dimensions.x5 / tokens.Dimensions.x4, - ); - -// TODO(tokens): How to add color and font family here? -// Both can be changed at runtime so can;t be const. -// But also how do we access them without state? - - /// {@template zeta-type-title-l} - /// Heading 4 / Large title text. - /// - /// Used for UI sections and landing pages. - /// - /// See also: - /// * [TextTheme.titleLarge]. - /// {@endtemplate} - static TextStyle zetaTitleLarge = const TextStyle( - fontSize: tokens.Dimensions.x5, - fontWeight: FontWeight.w500, - height: tokens.Dimensions.x4 / tokens.Dimensions.x5, - ); - - /// {@template zeta-type-heading-s} - /// Heading 3 text. - /// - /// Used for UI sections and landing pages. - /// - /// See also: - /// * [TextTheme.headlineSmall]. - /// {@endtemplate} - static TextStyle zetaHeadingSmall = const TextStyle( - fontSize: tokens.Dimensions.x6, - fontWeight: FontWeight.w500, - height: tokens.Dimensions.x7 / tokens.Dimensions.x6, - ); - - /// {@template zeta-type-heading-m} - /// Heading 2 text. - /// - /// Used for UI sections and landing pages. - /// - /// See also: - /// * [TextTheme.headlineMedium]. - /// {@endtemplate} - static TextStyle zetaHeadingMedium = const TextStyle( - fontSize: tokens.Dimensions.x7, - fontWeight: FontWeight.w500, - height: tokens.Dimensions.x8 / tokens.Dimensions.x7, - ); - - /// {@template zeta-type-heading-l} - /// Heading 1 text. - /// - /// Used for UI sections and landing pages. - /// See also: - /// * [TextTheme.headlineLarge]. - /// {@endtemplate} - static TextStyle zetaHeadingLarge = const TextStyle( - fontSize: tokens.Dimensions.x8, - fontWeight: FontWeight.w500, - height: tokens.Dimensions.x9 / tokens.Dimensions.x8, - ); - - /// {@template zeta-type-display-s} - /// Small display text. - /// - /// Used for landing page intros and sections. - /// - /// See also: - /// * [TextTheme.displaySmall]. - /// {@endtemplate} - static TextStyle zetaDisplaySmall = zetaHeadingSmall; - - /// {@template zeta-type-display-m} - /// Medium display text. - /// - /// Used for landing page intros and sections. - /// - /// See also: - /// * [TextTheme.displayMedium]. - /// {@endtemplate} - static TextStyle zetaDisplayMedium = zetaHeadingMedium; - - /// {@template zeta-type-display-l} - /// Large display text. - /// - /// Used for landing page intros. - /// See also: - /// * [TextTheme.displayLarge]. - /// {@endtemplate} - static TextStyle zetaDisplayLarge = zetaHeadingLarge; - - /// Gets approximate char width based on width of O in IBM Plex Sans - /// - /// Only works for IBM Plex. - static double _ch({double multiplier = _defaultChMultiplier, TextStyle? style}) { - final setStyle = style ?? zetaBodyMedium; - const plexCh = 0.6; - - return multiplier * plexCh * (setStyle.fontSize ?? tokens.Dimensions.x3); - } - - /// The text to be displayed. - /// - /// See also: - /// * [Text.data]. - final String? data; - - /// The style applied to the text. - /// - /// Defaults to [zetaBodyMedium]. - /// - /// See also: - /// * [Text.style]. - final TextStyle? style; - - /// Sets text color. - /// - /// See also: - /// * [TextStyle.color]. - final Color? textColor; - - /// Max width of Text box using [_ch]. Not measured in dp / px. - /// - /// [_ch] approximates width of a character using O as basis, so a maxWidth of 60 theoretically returns a max width containing 60 characters. - /// - /// Only works with 'IBM Plex Sans'. - final double? maxWidth; - - /// Font size override. - /// - /// {@template zeta-text-override} - /// Optional as this should be set using [style]. - /// {@endtemplate} - /// See also: - /// * [TextStyle.fontSize]. - final double? fontSize; - - /// Font weight override. - /// - /// {@macro zeta-text-override} - /// - /// See also: - /// * [TextStyle.fontWeight]. - final FontWeight? fontWeight; - - /// Font style override, used to set text to _italic_. - /// - /// - /// - /// See also: - /// * [TextStyle.fontStyle]. - /// * [FontStyle.italic]. - final FontStyle? fontStyle; - - /// Sets all text to uppercase. - /// - /// See also: - /// * [String.toUpperCase]. - final bool upperCase; - - /// Decoration override, used to apply decorations such as underline. - /// - /// See also: - /// * [TextDecoration.underline]. - /// * [TextDecoration]. - final TextDecoration? decoration; - - /// Text direction, used to set text to either Left to Right or Right to Left. - /// - /// See also: - /// * [TextDirection.values]. - final TextDirection textDirection; - - /// Sets padding top to 0. - /// - /// Set to true when this text is first in a list. - final bool first; - - /// Sets padding bottom to 0. - /// - /// Set to true when this text is last in a list. - final bool last; - - /// Sets the line height to 1 and spacing to 0. - /// - /// Defaults to false. - final bool resetHeight; - - EdgeInsets get _padding { - if (resetHeight || (first && last)) return tokens.Dimensions.x0.squish; - - return EdgeInsets.only( - top: first ? tokens.Dimensions.x0 : tokens.Dimensions.x2, - bottom: last ? tokens.Dimensions.x0 : tokens.Dimensions.x2, - ); - } - - double? get _fontSize { - if (fontSize == null) return null; - if (fontSize == tokens.Dimensions.x3_5) { - return tokens.Dimensions.x4 / tokens.Dimensions.x3_5; - } - - return ((fontSize ?? 1) + tokens.Dimensions.x1) / (fontSize ?? 1); - } - - @override - Widget build(BuildContext context) { - TextStyle thisStyle = (style ?? ZetaText.zetaBodyMedium).copyWith( - fontSize: style?.fontSize, - fontWeight: style?.fontWeight, - height: style?.height, - ); - - String data = this.data ?? ''; - final Color color = textColor ?? Zeta.of(context).colors.textDefault; - - thisStyle = thisStyle.copyWith( - fontSize: (fontSize ?? thisStyle.fontSize ?? tokens.Typography.defaultTextSize) * - MediaQuery.of(context).textScaleFactor, - height: _fontSize, - fontWeight: fontWeight, - decoration: decoration ?? TextDecoration.none, - fontStyle: fontStyle, - color: color, - ); - - if (resetHeight) thisStyle = thisStyle.copyWith(height: 1); - if (upperCase) data = data.toUpperCase(); - - return Padding( - padding: _padding, - child: maxWidth == null - ? Text(data, style: thisStyle, textDirection: textDirection) - : Align( - alignment: textDirection == TextDirection.rtl ? Alignment.centerRight : Alignment.centerLeft, - child: SizedBox( - width: maxWidth == null ? null : _ch(multiplier: maxWidth ?? 0, style: thisStyle), - child: Text(data, style: thisStyle, textDirection: textDirection), - ), - ), - ); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(StringProperty('data', data)) - ..add(DiagnosticsProperty('style', style)) - ..add(ColorProperty('textColor', textColor)) - ..add(DoubleProperty('maxWidth', maxWidth)) - ..add(DoubleProperty('fontSize', fontSize)) - ..add(DiagnosticsProperty('fontWeight', fontWeight)) - ..add(EnumProperty('fontStyle', fontStyle)) - ..add(DiagnosticsProperty('upperCase', upperCase)) - ..add(DiagnosticsProperty('decoration', decoration)) - ..add(EnumProperty('textDirection', textDirection)) - ..add(DiagnosticsProperty('first', first)) - ..add(DiagnosticsProperty('last', last)) - ..add(DiagnosticsProperty('resetHeight', resetHeight)); - } -} - -/// Extension to add Zeta's extra small text size. -extension XSmall on TextTheme { - /// Smallest body text size. - TextStyle? get bodyXSmall { - return ZetaText.zetaBodyXSmall; - } -} diff --git a/lib/src/theme/colors.dart b/lib/src/theme/colors.dart index 9abdc4f3..1e54689e 100644 --- a/lib/src/theme/colors.dart +++ b/lib/src/theme/colors.dart @@ -30,7 +30,7 @@ class ZetaColors { Color? surfaceTertiary, bool adjust = true, }) : primary = _adjustedValue(primary, ZetaColorBase.blue, adjust, brightness, contrast), - secondary = _adjustedValue(secondary, primary ?? ZetaColorBase.blue, adjust, brightness, contrast), + secondary = _adjustedValue(secondary, primary ?? ZetaColorBase.yellow, adjust, brightness, contrast), error = _adjustedValue(error, ZetaColorBase.red, adjust, brightness, contrast), cool = _adjustedValue(cool, ZetaColorBase.greyCool, adjust, brightness, ZetaContrast.aa), warm = _adjustedValue(warm, ZetaColorBase.greyWarm, adjust, brightness, ZetaContrast.aa), @@ -169,7 +169,7 @@ class ZetaColors { /// Secondary color used in app. /// - /// Defaults to `ZetaColors.cool.90`. + /// Defaults to [ZetaColorBase.yellow] /// /// Maps to [ColorScheme.secondary]. final ZetaColorSwatch secondary; @@ -316,6 +316,9 @@ class ZetaColors { /// {@macro zeta-color-dark} Color get borderSelected => cool.shade90; + /// True if current [ZetaColors] object uses dark mode colors. + bool get isDarkMode => brightness == Brightness.dark; + // Links /// Link color. @@ -340,7 +343,6 @@ class ZetaColors { /// Secondary surface color. /// - /// /// * `ZetaColors.cool.10`. final Color surfaceSecondary; diff --git a/lib/src/tokens.dart b/lib/src/theme/tokens.dart similarity index 60% rename from lib/src/tokens.dart rename to lib/src/theme/tokens.dart index fdfe7837..5a31e659 100644 --- a/lib/src/tokens.dart +++ b/lib/src/theme/tokens.dart @@ -1,14 +1,16 @@ -import '../../zeta_flutter.dart'; +import 'package:flutter/material.dart'; -/// Tokens that are used for [ZetaSpacing]. +/// Tokens that are used for spacing. /// /// Values are doubles, and can be used for padding, margins and other spacings. -class Dimensions { +/// +// TODO(thelukewalton): Refactor to match latest designs. +class ZetaSpacing { /// Base multiplier used to calculate spacing values. static const double spacingBaseMultiplier = 4; - /// 0dp space. - static const double x0 = spacingBaseMultiplier * 0; + /// 2dp space. + static const double x0_5 = spacingBaseMultiplier * 0.5; /// 4dp space. static const double x1 = spacingBaseMultiplier; @@ -22,6 +24,9 @@ class Dimensions { /// 8dp space. static const double xs = spacingBaseMultiplier * 2; + /// 10dp space. + static const double x2_5 = spacingBaseMultiplier * 2.5; + /// 12dp space. static const double x3 = spacingBaseMultiplier * 3; @@ -49,6 +54,9 @@ class Dimensions { /// 28dp space. static const double x7 = spacingBaseMultiplier * 7; + /// 30dp space. + static const double x7_5 = spacingBaseMultiplier * 7.5; + /// 32dp space. static const double x8 = spacingBaseMultiplier * 8; @@ -92,42 +100,20 @@ class Dimensions { static const double xxxl = spacingBaseMultiplier * 24; } -/// Tokens that are used for [ZetaText]. -class Typography { - /// Font family used across Zeta. - /// - /// Zeta library contains IBM Plex Sans with latin script. - /// In the case of non-latin languages, this can be overridden: - /// - /// Default text size. - /// - /// Defaults to [Dimensions.s]. - static const double defaultTextSize = Dimensions.s; - - /// Base text color. - // static const Color text = Color(0xFF1D1E23); - /// Subtle text color. - // static const Color subtleText = Color(0xFF545963); - // TODO(tokens): Work out how to do tokens properly -} - -/// Tokens that are used for [ZetaGrid]. -class Grid { - /// Maximum number of cols that can be displayed. - static const double maxCols = 16; - - /// Default number of cols. - static const double defaultCols = 12; +/// Tokens used for Border Radius. +class ZetaRadius { + /// No border radius; 0px radius. + static const BorderRadius none = BorderRadius.zero; - /// Default margin for grid component. - static const double gridMargin = Dimensions.x6; + /// Smallest amount of border radius; 4px radius. + static const BorderRadius minimal = BorderRadius.all(Radius.circular(ZetaSpacing.xxs)); - /// Number of columns to show on a portrait mobile, as defined in [DeviceType.mobilePortrait]. - static const mobilePortraitCount = 2; + /// Border radius used when rounded parameter is true; 8px radius. + static const BorderRadius rounded = BorderRadius.all(Radius.circular(ZetaSpacing.xs)); - /// Number of columns to show on a landscape mobile, as defined in [DeviceType.mobileLandscape]. - static const mobileLandscapeCount = 4; + /// Wide border radius; 24px radius. + static const BorderRadius wide = BorderRadius.all(Radius.circular(ZetaSpacing.m)); - /// Number of columns to show on a tablet, as defined in [DeviceType.tablet]. - static const tabletCount = 8; + /// Largest amount of border radius; 360px radius. + static const BorderRadius full = BorderRadius.all(Radius.circular(360)); } diff --git a/lib/src/theme/typography.dart b/lib/src/theme/typography.dart new file mode 100644 index 00000000..62857bbe --- /dev/null +++ b/lib/src/theme/typography.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import '../../zeta_flutter.dart'; + +/// Typography in Zeta style. +/// +/// We have decided to adopt IBM Plex Sans typeface for our digital solutions. +/// This new typeface is clean, distinctive and designed for digital world use cases. +/// The typeface is free and available in multiple languages, making it ideal for localization. +/// More info can be found at: www.ibm.com/plex +class ZetaTextStyles { + /// Largest of the display styles. + /// + /// {@template zeta-text-display} + /// As the largest text on the screen, display styles are reserved for short, + /// important text or numerals. They work best on large screens. + /// {@endtemplate} + static const TextStyle displayLarge = TextStyle(fontSize: 52, fontWeight: FontWeight.w300, height: 64 / 52); + + /// Middle size of the display styles. + /// + /// {@macro zeta-text-display} + static const TextStyle displayMedium = TextStyle(fontSize: 42, fontWeight: FontWeight.w300, height: 56 / 42); + + /// Smallest of the display styles. + /// + /// {@macro zeta-text-display} + static const TextStyle displaySmall = TextStyle(fontSize: 36, fontWeight: FontWeight.w300, height: 48 / 36); + + /// Largest of the headline styles. + /// + ///{@template zeta-text-headline} + /// Headline styles are smaller than display styles. They're best-suited for + /// short, high-emphasis text on smaller screens. + /// {@endtemplate} + static const TextStyle heading1 = TextStyle(fontSize: 32, fontWeight: FontWeight.w500, height: 40 / 32); + + /// Middle size of the headline styles. + /// + /// {@macro zeta-text-headline} + static const TextStyle heading2 = TextStyle(fontSize: 28, fontWeight: FontWeight.w500, height: 32 / 28); + + /// Smallest of the headline styles. + /// + /// {@macro zeta-text-headline} + static const TextStyle heading3 = TextStyle(fontSize: 24, fontWeight: FontWeight.w500, height: 32 / 24); + + /// Largest of the title styles. + /// + /// {@template zeta-text-title} + /// Titles are smaller than headline styles and should be used for shorter, + /// medium-emphasis text. + /// {@endtemplate} + static const TextStyle titleLarge = TextStyle(fontSize: 32, fontWeight: FontWeight.w500, height: 20 / 28); + + /// Middle size of the title styles. + /// + /// {@macro zeta-text-title} + static const TextStyle titleMedium = TextStyle(fontSize: 18, fontWeight: FontWeight.w500, height: 24 / 18); + + /// Smallest of the title styles. + /// + /// {@macro zeta-text-title} + static const TextStyle titleSmall = TextStyle(fontSize: 16, fontWeight: FontWeight.w500, height: 24 / 16); + + /// Largest of the body styles. + /// + /// {@template zeta-text-body} + /// Body styles are used for longer passages of text. + /// {@endtemplate} + static const TextStyle bodyLarge = TextStyle(fontSize: 16, fontWeight: FontWeight.w400, height: 16 / 24); + + /// Middle size of the body styles. + /// + /// {@macro zeta-text-body} + /// + /// The default Text style for [Zeta]. + static const TextStyle bodyMedium = TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 20 / 14); + + /// Smallest of the body styles. + /// + /// {@macro zeta-text-body} + static const TextStyle bodySmall = TextStyle(fontSize: 12, fontWeight: FontWeight.w400, height: 16 / 12); + + /// Largest of the label styles. + /// + /// {@template zeta-text-label} + /// Label styles are smaller, utilitarian styles, used for areas of the UI + /// such as text inside of components or very small supporting text in the + /// content body, like captions. + /// {@endtemplate} + /// + /// Used for text on [ZetaButton]. + static const TextStyle labelLarge = TextStyle(fontSize: 16, fontWeight: FontWeight.w500, height: 24 / 16); + + /// Middle size of the label styles. + /// + /// {@macro zeta-text-label} + static const TextStyle labelMedium = TextStyle(fontSize: 14, fontWeight: FontWeight.w500, height: 20 / 14); + + /// Small size of the label styles. + /// + /// {@macro zeta-text-label} + static const TextStyle labelSmall = TextStyle(fontSize: 12, fontWeight: FontWeight.w500, height: 16 / 12); + + /// Label text style used specifically for Indicator. + /// {@macro zeta-text-label} + static const TextStyle labelIndicator = TextStyle(fontSize: 12, fontWeight: FontWeight.w500, height: 14 / 12); + + /// Smallest of the label styles. + /// + /// {@macro zeta-text-label} + static const TextStyle labelTiny = TextStyle(fontSize: 11, fontWeight: FontWeight.w500, height: 14 / 11); +} + +/// [ZetaTextStyles] combined into a [TextTheme]. +const TextTheme zetaTextTheme = TextTheme( + displayLarge: ZetaTextStyles.displayLarge, + displayMedium: ZetaTextStyles.displayMedium, + displaySmall: ZetaTextStyles.displaySmall, + headlineLarge: ZetaTextStyles.heading1, + headlineMedium: ZetaTextStyles.heading2, + headlineSmall: ZetaTextStyles.heading3, + titleLarge: ZetaTextStyles.titleLarge, + titleMedium: ZetaTextStyles.titleMedium, + titleSmall: ZetaTextStyles.titleSmall, + bodyLarge: ZetaTextStyles.bodyLarge, + bodyMedium: ZetaTextStyles.bodyMedium, + bodySmall: ZetaTextStyles.bodySmall, + labelLarge: ZetaTextStyles.labelLarge, + labelMedium: ZetaTextStyles.labelMedium, + labelSmall: ZetaTextStyles.labelSmall, +); diff --git a/lib/src/utils/enums.dart b/lib/src/utils/enums.dart new file mode 100644 index 00000000..787a4f24 --- /dev/null +++ b/lib/src/utils/enums.dart @@ -0,0 +1,43 @@ +import '../../zeta_flutter.dart'; + +/// Border options for [ZetaButton]. +enum ZetaWidgetBorder { + /// Sharp border + sharp, + + /// Slightly rounded border. + rounded, + + /// Fully rounded border. + full, +} + +/// Size options for [ZetaIndicator], [ZetaWorkcloudIndicator], [ZetaButton], [ZetaPasswordInput]. +enum ZetaWidgetSize { + /// large + large, + + /// medium + medium, + + /// small + small, +} + +/// Status options for [ZetaBadge], [ZetaStatusLabel], [ZetaInPageBanner]. +enum ZetaWidgetStatus { + /// Information widget; defaults to purple color scheme. + info, + + /// Positive widget; defaults to green color scheme. + positive, + + /// Warning widget; defaults to yellow color scheme. + warning, + + /// Negative widget; defaults to red color scheme. + negative, + + /// Neutral widget; defaults to grey color scheme. + neutral, +} diff --git a/lib/src/utils/extensions.dart b/lib/src/utils/extensions.dart index 59eb32cc..a7347b6f 100644 --- a/lib/src/utils/extensions.dart +++ b/lib/src/utils/extensions.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import '../../zeta_flutter.dart'; + /// Extension to add dividers to any view that can take an iterable. /// /// Iterable can be converted to a list with [toList]. @@ -16,3 +18,80 @@ extension ListDivider on Iterable { } } } + +/// Extension to add spacing to any [Widget]. +extension SpacingWidget on Widget { + /// Equal padding on all sides. + Widget paddingAll(double space) => Padding(padding: EdgeInsets.all(space), child: this); + + /// Padding on start edge only. Is affected by whether the device is LTR or RTL: + /// + /// LTR: Left edge + /// RTL: Right edge + Widget paddingStart(double space) => Padding(padding: EdgeInsetsDirectional.only(start: space), child: this); + + /// Padding on start edge only. Is affected by whether the device is LTR or RTL: + /// + /// LTR: Right edge + /// RTL: Left edge + Widget paddingEnd(double space) => Padding(padding: EdgeInsetsDirectional.only(end: space), child: this); + + /// Padding on bottom only. + Widget paddingBottom(double space) => Padding(padding: EdgeInsets.only(bottom: space), child: this); + + /// Padding on top only. + Widget paddingTop(double space) => Padding(padding: EdgeInsets.only(top: space), child: this); + + /// Equal padding on top and bottom. + Widget paddingVertical(double space) => Padding(padding: EdgeInsets.symmetric(vertical: space), child: this); + + /// Equal padding on start and end. + Widget paddingHorizontal(double space) => Padding(padding: EdgeInsets.symmetric(horizontal: space), child: this); +} + +/// Extensions on [num]. +extension NumExtensions on num? { + /// Returns input as a formatted string with a maximum amount of characters. + /// + /// [maxChars] defaults to one. + String formatMaxChars([int maxChars = 1]) { + final strVal = this == null ? '' : this!.abs().toString(); + return strVal.length > maxChars ? '${'9' * maxChars}+' : strVal; + } +} + +/// Extensions on [ZetaWidgetStatus]. +extension ColorSwatches on ZetaWidgetStatus { + /// Gets color swatch from [ZetaWidgetStatus] + ZetaColorSwatch colorSwatch(BuildContext context) { + final colors = Zeta.of(context).colors; + switch (this) { + case ZetaWidgetStatus.info: + return colors.info; + case ZetaWidgetStatus.positive: + return colors.positive; + case ZetaWidgetStatus.warning: + return colors.warning; + case ZetaWidgetStatus.negative: + return colors.negative; + case ZetaWidgetStatus.neutral: + return colors.cool; + } + } +} + +/// Extensions on [String]. +extension StringExtensions on String? { + /// Returns initials from a name. + String get initials { + if (this == null) return ''; + final List nameParts = this!.split(RegExp(r'\W+'))..removeWhere((item) => item.isEmpty); + if (nameParts.isEmpty) return ''; + return (nameParts.length > 1 + ? nameParts[0].substring(0, 1) + nameParts[1].substring(0, 1) + : nameParts[0].length > 1 + ? nameParts[0].substring(0, 2) + : nameParts[0]) + .toUpperCase(); + } +} diff --git a/lib/zeta_flutter.dart b/lib/zeta_flutter.dart index f4babe3b..eb763dca 100644 --- a/lib/zeta_flutter.dart +++ b/lib/zeta_flutter.dart @@ -1,10 +1,24 @@ /// Zebra Design System (Zeta) - Flutter Component Library library zeta_flutter; -export 'src/components/checkbox.dart'; -export 'src/components/grid.dart'; -export 'src/components/spacing.dart'; -export 'src/components/text.dart'; +export 'src/assets/icons.dart'; +export 'src/components/accordion/accordion.dart'; +export 'src/components/avatars/avatar.dart'; +export 'src/components/badges/badge.dart'; +export 'src/components/badges/indicator.dart'; +export 'src/components/badges/priority_pill.dart'; +export 'src/components/badges/status_label.dart'; +export 'src/components/badges/tag.dart'; +export 'src/components/badges/workcloud_indicator.dart'; +export 'src/components/banners/in_page_banner.dart'; +export 'src/components/banners/system_banner.dart'; +export 'src/components/bottom sheets/bottom_sheet.dart'; +export 'src/components/bottom sheets/menu_items.dart'; +export 'src/components/buttons/button.dart'; +export 'src/components/buttons/fab.dart'; +export 'src/components/checkbox/checkbox.dart'; +export 'src/components/chips/chip.dart'; +export 'src/components/password/password_input.dart'; export 'src/theme/color_extensions.dart'; export 'src/theme/color_scheme.dart'; export 'src/theme/color_swatch.dart'; @@ -12,6 +26,8 @@ export 'src/theme/constants.dart'; export 'src/theme/contrast.dart'; export 'src/theme/theme_data.dart'; export 'src/theme/theme_service.dart'; -export 'src/tokens.dart'; +export 'src/theme/tokens.dart'; +export 'src/theme/typography.dart'; +export 'src/utils/enums.dart'; export 'src/utils/extensions.dart'; export 'src/zeta.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 4a8e8fba..d6920c2b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,8 @@ name: zeta_flutter -version: 0.1.1+1 -description: Zeta is the new, formal, standardized Zebra Design System based off the successes of ZDS (Zebra Design System). This package is in pre-release, and so many aspects are incomplete. +version: 0.2.0 +description: Zeta is the new, formal, standardized Zebra Design System based off + the successes of ZDS (Zebra Design System). This package is in pre-release, + and so many aspects are incomplete. homepage: https://github.com/zebradevs/zeta_flutter repository: https://github.com/zebradevs/zeta_flutter issue_tracker: https://github.com/zebradevs/zeta_flutter/issues @@ -23,8 +25,6 @@ dependencies: sdk: flutter dev_dependencies: - flutter_test: - sdk: flutter zds_analysis: ^1.0.0 flutter: @@ -37,4 +37,10 @@ flutter: weight: 400 - asset: lib/src/assets/fonts/IBMPlexSans-Medium.otf weight: 500 + - family: zeta-icons-round + fonts: + - asset: lib/src/assets/fonts/zeta-icons-round.ttf + - family: zeta-icons-sharp + fonts: + - asset: lib/src/assets/fonts/zeta-icons-sharp.ttf uses-material-design: true diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..61d4398e --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,7 @@ +{ + "packages": { + ".": { + "release-type": "dart" + } + } +} \ No newline at end of file