From 14d91f7d5a45c72dea00997bc2c011073cab34da Mon Sep 17 00:00:00 2001 From: LudovicPinel Date: Fri, 14 Apr 2023 09:07:31 +0200 Subject: [PATCH] Release 0.12.0 (#406) --- CHANGELOG.md | 10 + Gemfile.lock | 49 +-- .../InnovationCupTheme.swift | 3 + NOTICE.txt | 13 +- .../Internal/BottomSheedHeader.swift | 138 +++++++ .../ODSBottomSheetExpandingModifier.swift | 104 ++++++ .../ODSBottomSheetSize+extension.swift | 79 ++++ .../ODSBottomSheetStandardModifier.swift | 220 +++++++++++ .../BottomSheet/ODSBottomSheetExpanding.swift | 131 +++++++ .../BottomSheet/ODSBottomSheetStandard.swift | 120 ++++++ .../Buttons/Internal/ODSButtonStyles.swift | 2 +- .../Components/Cards/ODSCardSmall.swift | 2 +- .../Components/Lists/ODSListItemModels.swift | 1 - .../Components/Slider/ODSSliderView.swift | 352 +++++++++++++++--- .../Theme/Modifiers/ODSTabBarModifier.swift | 7 +- .../Theme/ODSComponentsColors.swift | 6 + .../Theme/View/ODSThemeableView.swift | 9 +- .../Utils/TabBar+readSize.swift | 85 +++++ .../project.pbxproj | 52 ++- .../xcshareddata/swiftpm/Package.resolved | 16 + .../Contents.json | 0 .../iconsCommunicationDIcCafe.svg | 0 .../Contents.json | 0 .../iconsCommunicationDIcCookingPot.svg | 0 .../Contents.json | 0 .../iconsCommunicationDIIcIceCream.svg | 0 .../Contents.json | 0 .../iconsCommunicationRUIcRestaurant.svg | 0 .../AboutImage_generic.png | Bin 0 -> 32183 bytes .../AboutImage_generic.svg | 18 - .../AboutImage_generic.imageset/Contents.json | 2 +- .../BottomSheet_generic.png | Bin 0 -> 3383 bytes .../Contents.json | 12 + .../Orange/BottomSheet.imageset/Contents.json | 12 + .../BottomSheet.imageset/bottomSheet.png | Bin 0 -> 3383 bytes .../OrangeDesignSystemDemoApp.swift | 2 +- .../Themes/ThemeSelectionView.swift | 1 + .../Views/About/ODSDemoAboutConfig.swift | 13 +- .../Views/Components/ComponentList.swift | 1 + .../Pages/Banners/BannerComponent.swift | 12 +- .../BottomSheet/BottomSheetComponent.swift | 56 +++ .../BottomSheetExpandingVariant.swift | 206 ++++++++++ .../BottomSheetExpandingVariantOptions.swift | 245 ++++++++++++ .../Standard/BottomSheetStandardVariant.swift | 107 ++++++ .../Pages/Buttons/ButtonsComponent.swift | 13 +- .../Pages/Buttons/IconVariant.swift | 60 +-- .../Pages/Cards/CardHorizontalVariant.swift | 8 +- .../Pages/Cards/CardSmallVariant.swift | 14 +- .../CardVerticalHeaderFirstVariant.swift | 8 +- .../Cards/CardVerticalImageFirstVariant.swift | 8 +- .../SelectionVariant/SelectionList.swift | 7 +- .../Lists/StandardVariant/StandardList.swift | 9 +- .../NavigationBarComponent.swift | 8 +- .../ActivityIndicatorVariant.swift | 6 +- .../ProgressBarVariant.swift | 64 ++-- .../Pages/Sliders/SlidersVariant.swift | 83 +++-- .../Pages/TabBar/TabBarVariant.swift | 10 +- .../CapitalizedTextInputsVariant.swift | 8 +- .../Components/Template/BottomSheet.swift | 90 ----- .../Template/CustomizableVariant.swift | 62 +++ OrangeDesignSystemDemo/fastlane/Fastfile | 63 +++- OrangeDesignSystemDemo/fastlane/README.md | 6 +- .../Sources/OrangeTheme/OrangeTheme.swift | 3 + Package.swift | 4 +- THIRD-PARTY.md | 3 + docs/Gemfile.lock | 1 + docs/_data/data_menus.yml | 2 + docs/components/sheetsBottom.md | 86 +++++ docs/components/sheetsBottom_docs.md | 4 + docs/components/slider.md | 71 ++-- 70 files changed, 2348 insertions(+), 439 deletions(-) create mode 100644 OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/BottomSheedHeader.swift create mode 100644 OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetExpandingModifier.swift create mode 100644 OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetSize+extension.swift create mode 100644 OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetStandardModifier.swift create mode 100644 OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/ODSBottomSheetExpanding.swift create mode 100644 OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/ODSBottomSheetStandard.swift create mode 100644 OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/TabBar+readSize.swift create mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcworkspace/xcshareddata/swiftpm/Package.resolved rename OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/{iconsCommunicationDIcCafe.imageset => Cafe.imageset}/Contents.json (100%) rename OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/{iconsCommunicationDIcCafe.imageset => Cafe.imageset}/iconsCommunicationDIcCafe.svg (100%) rename OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/{iconsCommunicationDIcCookingPot.imageset => CookingPot.imageset}/Contents.json (100%) rename OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/{iconsCommunicationDIcCookingPot.imageset => CookingPot.imageset}/iconsCommunicationDIcCookingPot.svg (100%) rename OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/{iconsCommunicationDIIcIceCream.imageset => IceCream.imageset}/Contents.json (100%) rename OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/{iconsCommunicationDIIcIceCream.imageset => IceCream.imageset}/iconsCommunicationDIIcIceCream.svg (100%) rename OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/{iconsCommunicationRUIcRestaurant.imageset => Restaurant.imageset}/Contents.json (100%) rename OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/{iconsCommunicationRUIcRestaurant.imageset => Restaurant.imageset}/iconsCommunicationRUIcRestaurant.svg (100%) create mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/AboutImage_generic.imageset/AboutImage_generic.png delete mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/AboutImage_generic.imageset/AboutImage_generic.svg create mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/BottomSheet_generic.imageset/BottomSheet_generic.png create mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/BottomSheet_generic.imageset/Contents.json create mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Orange/BottomSheet.imageset/Contents.json create mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Orange/BottomSheet.imageset/bottomSheet.png create mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/BottomSheetComponent.swift create mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariant.swift create mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift create mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Standard/BottomSheetStandardVariant.swift delete mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Template/BottomSheet.swift create mode 100644 OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Template/CustomizableVariant.swift create mode 100644 docs/components/sheetsBottom.md create mode 100644 docs/components/sheetsBottom_docs.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 85986377..2026682e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.12.0](https://github.com/Orange-OpenSource/ods-ios/compare/0.12.0...0.11.0) - 2023-04-14 + +- [DemoApp/SDK] Add Bottom Sheet component ([#325](https://github.com/Orange-OpenSource/ods-ios/issues/325)) +- [SDK] Accessibility issues on Slider (Bug [#385](https://github.com/Orange-OpenSource/ods-ios/issues/385)) +- [Build] Update Build scripts to prepare upload on internal portal ([#383](https://github.com/Orange-OpenSource/ods-ios/issues/383)) +- [DemoApp] Add animation on Bottom Sheet when oppening and closing, automatically open it when appears ([#377](https://github.com/Orange-OpenSource/ods-ios/issues/377)) +- [DemoApp] Customization bottom sheet title uniformity ([#378](https://github.com/Orange-OpenSource/ods-ios/issues/378)) +- [DemoApp] Lists icon not displaying (Bug [#375](https://github.com/Orange-OpenSource/ods-ios/issues/375)) +- [SDK] Value is not computed well if Slider configured with step less than 1 (Bug [#313](https://github.com/Orange-OpenSource/ods-ios/issues/313)) +- [DemoApp] Update About module illustrations with B&W images ([#371](https://github.com/Orange-OpenSource/ods-ios/issues/371)) ## [0.11.2](https://github.com/Orange-OpenSource/ods-ios/compare/0.11.2...0.10.0) - 2023-03-27 diff --git a/Gemfile.lock b/Gemfile.lock index bb01d659..50c500e2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.5) + CFPropertyList (3.0.6) rexml activesupport (6.1.6) concurrent-ruby (~> 1.0, >= 1.0.2) @@ -9,24 +9,24 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.4) + public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.687.0) - aws-sdk-core (3.168.4) + aws-partitions (1.745.0) + aws-sdk-core (3.171.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.61.0) + aws-sdk-kms (1.63.0) aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.117.2) + aws-sdk-s3 (1.120.1) aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) @@ -86,8 +86,8 @@ GEM escape (0.0.4) ethon (0.15.0) ffi (>= 1.15.0) - excon (0.96.0) - faraday (1.10.2) + excon (0.99.0) + faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -160,9 +160,9 @@ GEM fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.32.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-core (0.9.3) + google-apis-androidpublisher_v3 (0.38.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.0) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -171,10 +171,10 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.16.0) - google-apis-core (>= 0.9.1, < 2.a) - google-apis-playcustomapp_v1 (0.12.0) - google-apis-core (>= 0.9.1, < 2.a) + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) google-apis-storage_v1 (0.19.0) google-apis-core (>= 0.9.0, < 2.a) google-cloud-core (1.6.0) @@ -182,7 +182,7 @@ GEM google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.0) + google-cloud-errors (1.3.1) google-cloud-storage (1.44.0) addressable (~> 2.8) digest-crc (~> 0.4) @@ -191,7 +191,7 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.3.0) + googleauth (1.5.1) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -205,8 +205,8 @@ GEM i18n (1.10.0) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.6.1) - jwt (2.6.0) + json (2.6.3) + jwt (2.7.0) memoist (0.16.2) mini_magick (4.12.0) mini_mime (1.1.2) @@ -220,7 +220,7 @@ GEM netrc (0.11.0) optparse (0.1.1) os (1.1.4) - plist (3.6.0) + plist (3.7.0) public_suffix (4.0.7) rake (13.0.6) representable (3.2.0) @@ -239,7 +239,7 @@ GEM faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.8) + simctl (1.6.10) CFPropertyList naturally terminal-notifier (2.0.0) @@ -259,9 +259,9 @@ GEM unf_ext unf_ext (0.0.8.2) unicode-display_width (1.8.0) - webrick (1.7.0) + webrick (1.8.1) word_wrap (1.0.0) - xcodeproj (1.21.0) + xcodeproj (1.22.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -278,6 +278,7 @@ PLATFORMS arm64-darwin-21 x86_64-darwin-19 x86_64-darwin-20 + x86_64-darwin-21 DEPENDENCIES cocoapods (= 1.11.3) diff --git a/InnovationCupTheme/Sources/InnovationCupTheme/InnovationCupTheme.swift b/InnovationCupTheme/Sources/InnovationCupTheme/InnovationCupTheme.swift index 13d5960f..2034578c 100644 --- a/InnovationCupTheme/Sources/InnovationCupTheme/InnovationCupTheme.swift +++ b/InnovationCupTheme/Sources/InnovationCupTheme/InnovationCupTheme.swift @@ -77,6 +77,9 @@ public struct InnovationCupThemeFactory { theme.componentColors.functionalInfo = InnovationCupThemeColors.functionalInfo.colorDecription.color theme.componentColors.functionalAlert = InnovationCupThemeColors.functionalAlert.colorDecription.color + // Bottom sheet + theme.componentColors.bottomSheetHeaderBackground = InnovationCupThemeColors.tabBarItem.colorDecription.color + // Fonts: use the default ones // theme.font = { style in } diff --git a/NOTICE.txt b/NOTICE.txt index bc4c0cab..d50e0bde 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -69,11 +69,10 @@ OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Orange/Tab OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Orange/Text edit menu.imageset/Text edit menu.png OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Orange/Typography.imageset/Typography.svg -OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIIcIceCream.imageset/iconsCommunicationDIIcIceCream.svg -OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIcCafe.imageset/iconsCommunicationDIcCafe.svg -OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIcCookingPot.imageset/iconsCommunicationDIcCookingPot.svg -OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationRUIcRestaurant.imageset/iconsCommunicationRUIcRestaurant.svg -OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIIcIceCream.imageset/iconsCommunicationDIIcIceCream.svg -OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIcCafe.imageset/iconsCommunicationDIcCafe.svg +OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/IceCream.imageset/iconsCommunicationDIIcIceCream.svg +OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/Cafe.imageset/iconsCommunicationDIcCafe.svg +OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/CookingPot.imageset/iconsCommunicationDIcCookingPot.svg +OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/Restaurant.imageset/iconsCommunicationRUIcRestaurant.svg +OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/IceCream.imageset/iconsCommunicationDIIcIceCream.svg +OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/Cafe.imageset/iconsCommunicationDIcCafe.svg End of the parts list under Orange SA Copyright - diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/BottomSheedHeader.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/BottomSheedHeader.swift new file mode 100644 index 00000000..9e1eceb2 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/BottomSheedHeader.swift @@ -0,0 +1,138 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import SwiftUI + +struct BottomSheedHeader: View { + + // ======================= + // MARK: Stored Properties + // ======================= + + let title: String + let subtitle: String? + let icon: Image? + let applyRotation: Bool + + // ========== + // MARK: Body + // ========== + + var body: some View { + VStack(spacing: 0) { + VStack(spacing: ODSSpacing.none) { + RoundedRectangle(cornerRadius: 4) + .frame(width: 55, height: 4, alignment: .center) + .padding(.top, ODSSpacing.s) + .padding(.bottom, ODSSpacing.xs) + + VStack(spacing: ODSSpacing.none) { + HStack(spacing: ODSSpacing.xs) { + icon? + .foregroundColor(.primary) + .accessibility(hidden: true) + .odsFont(.headline) + .animation(.linear, value: applyRotation) + .rotationEffect(.degrees(applyRotation ? 180 : 0)) + + VStack(alignment: .leading, spacing: ODSSpacing.none) { + Text(title) + .odsFont(.headline) + .frame(maxWidth: .infinity, alignment: .leading) + + if let subtitle = self.subtitle { + Text(subtitle) + .odsFont(.subhead) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + } + .padding(.leading, ODSSpacing.s) + .padding(.trailing, ODSSpacing.m) + .padding(.bottom, ODSSpacing.s) + } + } + .background(Color(.systemGray6)) + .padding(.bottom, 10) + .cornerRadius(10) + .shadow(color: Color(UIColor.systemGray), radius: 4) + .padding(.bottom, -10) + .padding(.top, 10) + .mask(Rectangle().padding(.top, -40)) + + Divider() + } + } +} + +#if DEBUG +struct HeaderPreviewProvider_Previews: PreviewProvider { + + struct AnimatinoExample: View { + @State var applyRotation = false + + var body: some View { + BottomSheedHeader(title: "Rotation: \(applyRotation ? "Yes" : "No")", subtitle: nil, icon: Image(systemName: "chevron.up"), applyRotation: applyRotation) + .onTapGesture { + applyRotation.toggle() + } + + ODSButton(text: LocalizedStringKey(applyRotation ? "Remove Rotation" : "Apply Rotation"), emphasis: .highest) { + applyRotation.toggle() + } + } + } + + static var previews: some View { + VStack(spacing: 50) { + VStack { + Text("Title and Subtile") + .odsFont(.title2) + .frame(maxWidth: .infinity, alignment: .leading) + BottomSheedHeader(title: "Title", subtitle: "Subtitle", icon: nil, applyRotation: false) + } + + VStack { + Text("Title and icon (without rotation)") + .odsFont(.title2) + .frame(maxWidth: .infinity, alignment: .leading) + BottomSheedHeader(title: "Title", subtitle: nil, icon: Image(systemName: "chevron.down"), applyRotation: false) + } + + VStack { + Text("Title and icon (with rotation)") + .odsFont(.title2) + .frame(maxWidth: .infinity, alignment: .leading) + BottomSheedHeader(title: "Title", subtitle: nil, icon: Image(systemName: "chevron.down"), applyRotation: true) + } + + VStack { + Text("Title and icon (animated rotation)") + .odsFont(.title2) + .frame(maxWidth: .infinity, alignment: .leading) + AnimatinoExample() + } + } + .padding(.horizontal, 16) + } +} +#endif diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetExpandingModifier.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetExpandingModifier.swift new file mode 100644 index 00000000..509ee97a --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetExpandingModifier.swift @@ -0,0 +1,104 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import BottomSheet +import SwiftUI + +struct ODSBottomSheetExpandingModifier: ViewModifier where ContentView: View { + + // ======================= + // MARK: Stored Properties + // ======================= + + private let title: String + private let subtitle: String? + private let icon: Image? + private let mainContent: () -> ContentView + private var bottomSheetSize: Binding + @State private var bottomSheetPosition: BottomSheetPosition + + // ================= + // MARK: Initializer + // ================= + + init(title: String, + subtile: String? = nil, + icon: Image? = nil, + bottomSheetSize: Binding, + @ViewBuilder content: @escaping () -> ContentView) { + self.title = title + self.subtitle = subtile + self.icon = icon + self.mainContent = content + self.bottomSheetSize = bottomSheetSize + self.bottomSheetPosition = self.bottomSheetSize.wrappedValue.position + } + + // ========== + // MARK: Body + // ========== + + func body(content: Content) -> some View { + content + .bottomSheet( + bottomSheetPosition: $bottomSheetPosition, + switchablePositions: ODSBottomSheetSize.allCases.map { $0.position }, + headerContent: { + BottomSheedHeader(title: title, subtitle: subtitle, icon: icon, applyRotation: false) + .onTapGesture { + switch self.bottomSheetSize.wrappedValue { + case .small: + self.bottomSheetPosition = ODSBottomSheetSize.medium.position + case .medium: + self.bottomSheetPosition = ODSBottomSheetSize.large.position + case .large: + self.bottomSheetPosition = ODSBottomSheetSize.small.position + default: + break + } + } + }, + mainContent: mainContent + ) + .showDragIndicator(false) + .enableAppleScrollBehavior(true) + .enableContentDrag(true) + .enableTapToDismiss(true) + .onDismiss { + bottomSheetPosition = ODSBottomSheetSize.small.position + } + .customBackground(self.background) + .onChange(of: bottomSheetPosition) { newValue in + bottomSheetSize.wrappedValue = ODSBottomSheetSize(from: newValue) + } + + } + + // ===================== + // MARK: Private Helpers + // ===================== + + @ViewBuilder + private var background: some View { + Color(.clear) + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetSize+extension.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetSize+extension.swift new file mode 100644 index 00000000..47f0aaf1 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetSize+extension.swift @@ -0,0 +1,79 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +import BottomSheet + +extension ODSBottomSheetSize { + var position: BottomSheetPosition { + switch self { + case .hidden: + return .hidden + case .small: + return .dynamicBottom + case .medium: + return .relative(0.5) + case .large: + return .relativeTop(0.975) + } + } + + public init(from position: BottomSheetPosition) { + switch position { + case .hidden: + self = .hidden + case .dynamicBottom: + self = .small + case .relative(let ratio) where ratio == 0.5: + self = .medium + default: + self = .large + } + } +} + +#if DEBUG +extension BottomSheetPosition { + public var description: String { + switch self { + case .absolute(let s): + return "absolute (\(s))" + case .hidden: + return "Hidden" + case .dynamicBottom: + return "dynamicBottom" + case .dynamic: + return "dynamic" + case .dynamicTop: + return "dynamicTop" + case .relativeBottom(let s): + return "dynamicBottom(\(s))" + case .relative(let s): + return "relative(\(s))" + case .relativeTop(let s): + return "relativeTop(\(s))" + case .absoluteBottom(let s): + return "absoluteBottom(\(s))" + case .absoluteTop(let s): + return "absoluteTop(\(s))" + } + } +} +#endif diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetStandardModifier.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetStandardModifier.swift new file mode 100644 index 00000000..a2b1a41a --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetStandardModifier.swift @@ -0,0 +1,220 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import SwiftUI + +struct ODSBottomSheetStandardModifier: ViewModifier where BottomSheetContent: View { + + // ======================= + // MARK: Stored Properties + // ======================= + let isOpen: Binding + let headerConfig: ODSBottomSheetStandardHeaderConfig + @ViewBuilder let bottomSheetContent: () -> BottomSheetContent + @State private var bottomSheetHeaderSize = CGSize() + + // ========== + // MARK: Body + // ========== + + func body(content: Content) -> some View { + ZStack { + content + ODSBottomSheetStandard(isOpen: isOpen, headerSize: $bottomSheetHeaderSize, + headerConfig: headerConfig, content: bottomSheetContent) + } + } +} + +/// Used to configure the header of the bottom sheet __ODSBottomSheetStandard__. +struct ODSBottomSheetStandardHeaderConfig { + + // ======================= + // MARK: Stored Properties + // ======================= + + fileprivate let title: String + fileprivate let subtitle: String? + fileprivate let icon: Image? + fileprivate let animateIcon: Bool + + // ================== + // MARK: Initializers + // ================== + + /// Initilize the header of the __ODSBottomSheetStandard__ with title only. + /// - Parameters: + /// - title: The title of the bottom sheet + init(title: String) { + self.init(title: title, subtitle: nil, icon: nil, animateIcon: false) + } + + /// Initilize the header of the __ODSBottomSheetStandard__ with title and subtitle. + /// - Parameters: + /// - title: The title of the bottom sheet + /// - subtitle: The additional subtitle + init(title: String, subtitle: String) { + self.init(title: title, subtitle: subtitle, icon: nil, animateIcon: false) + } + + /// Initilize the header of the __ODSBottomSheetStandard__ with title and subtitle. + /// - Parameters: + /// - title: The title of the bottom sheet + /// - icon: The additional icon added near to the title + /// - animateIcon: To animate (ration to 180 degrees) when sheet is opening. + init(title: String, icon: Image, animateIcon: Bool = true) { + self.init(title: title, subtitle: nil, icon: icon, animateIcon: animateIcon) + } + + private init(title: String, subtitle: String?, icon: Image?, animateIcon: Bool) { + self.title = title + self.subtitle = subtitle + self.icon = icon + self.animateIcon = animateIcon + } +} + +/// The standard bottom sheet must be used only with a "simple, basic" content. If a more complex content must be added +/// prefer the __ odsBottomSheetExpanding__ modifiers. +/// +/// The view of standard bottom sheet can be used out of the __odsBottomSheetStandard__ modifiers. +/// +/// To do so, use it in a `ZStack` like this: +/// +/// struct YourView: View { +/// @State var isShowingSheet: Bool = false +/// var body: some View { +/// ZStack { +/// // Main content goes here. +/// ScrollView { +/// Text("Bottom sheet is \(isShowingSheet ? "Opened": "Closed")") +/// } +/// ODSBottomSheetStandard(isOpen: $isShowingSheet, headerConfig: ODSBottomSheetStandardHeaderConfig(title: "Customize")) { +/// // Bottom sheet content goes here +/// } +/// } +/// } +/// } +/// +struct ODSBottomSheetStandard: View where Content: View { + + // ======================= + // MARK: Stored Properties + // ======================= + + private let headerConfig: ODSBottomSheetStandardHeaderConfig + private let content: Content + private let isOpen: Binding + private let headerSize: Binding? + + // ================= + // MARK: Initializer + // ================= + + /// Initilize the bottom sheet view. + /// + /// - Parameters: + /// - isOpen: A binding to a Boolean value that determines whether to open the sheet + /// - headerSize: A binding to a `CGSize` value that provide the size of the header. + /// Nice to get its height to add padding at the bottom of the main conent view to avoid bottom sheet to overlap it. + /// - headerConfig: The header configuration. + /// - content: A closure that returns the content of the bottom sheet. + /// + // swiftlint:disable multiline_parameters_brackets + init(isOpen: Binding, + headerSize: Binding? = nil, + headerConfig: ODSBottomSheetStandardHeaderConfig, + @ViewBuilder content: @escaping () -> Content) { + self.isOpen = isOpen + self.headerSize = headerSize + self.headerConfig = headerConfig + self.content = content() + } + + // ========== + // MARK: Body + // ========== + + var body: some View { + VStack(spacing: ODSSpacing.none) { + Spacer() + + VStack(spacing: ODSSpacing.none) { + BottomSheedHeader(title: headerConfig.title, + subtitle: headerConfig.subtitle, + icon: headerConfig.icon, + applyRotation: applyRotation) + .onTapGesture { + withAnimation(Animation.linear) { + isOpen.wrappedValue.toggle() + } + } + .readSize { size in + headerSize?.wrappedValue = size + } + + if isOpen.wrappedValue { + content + .background(Color(UIColor.systemBackground)) + .transition(.asymmetric(insertion: .move(edge: .bottom), removal: .move(edge: .bottom))) + } + } + } + } + + // ============== + // MARK: Helpers + // ============== + + private var applyRotation: Bool { + headerConfig.animateIcon && self.isOpen.wrappedValue + } +} + +#if DEBUG +struct StandardBottomSheetPreviewProvider_Previews: PreviewProvider { + + struct BottomSheet: View { + @State var isOpen: Bool + + var body: some View { + ODSBottomSheetStandard(isOpen: $isOpen, + headerConfig: ODSBottomSheetStandardHeaderConfig(title: "Recipes", icon: Image(systemName: "chevron.up"))) { + VStack { + ODSListStandardItem(model: ODSListStandardItemModel(title: "Summer Salad", leadingIcon: ODSListItemLeadingIcon.icon(Image(systemName: "sun.max")))) + + ODSListStandardItem(model: ODSListStandardItemModel(title: "Bocoli Soup", leadingIcon: ODSListItemLeadingIcon.icon(Image(systemName: "heart")))) + + ODSListStandardItem(model: ODSListStandardItemModel(title: "Pesto farfalle", leadingIcon: ODSListItemLeadingIcon.icon(Image(systemName: "music.note")))) + + ODSListStandardItem(model: ODSListStandardItemModel(title: "Fig Sponge Cake", leadingIcon: ODSListItemLeadingIcon.icon(Image(systemName: "star")))) + } + .padding(.horizontal, 16) + } + } + } + + static var previews: some View { + BottomSheet(isOpen: true) + } +} +#endif diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/ODSBottomSheetExpanding.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/ODSBottomSheetExpanding.swift new file mode 100644 index 00000000..4c5e0b1d --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/ODSBottomSheetExpanding.swift @@ -0,0 +1,131 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import SwiftUI + +// swiftlint:disbale comment_spacing multiline_parameters_brackets vertical_parameter_alignment +public enum ODSBottomSheetSize: String, CaseIterable { + case hidden /// The state where the BottomSheet is hidden + case small /// The state where the BottomSheet is closed (`mainContent` is hidden) + case medium /// The state where the height of the BottomSheet is 50% of the screen + case large /// The state where the height of the BottomSheet is 90% of the screen +} + +extension View { + /// + /// ODS Sheet Bottom. + /// + /// Bottom sheets are surfaces anchored to the bottom of the screen that present users supplemental content. + /// It is useful for requesting a specific information or enabling a simple task related to the current context + /// of the current view or more globaly the application context. + /// + /// Unlike the standard bottom sheet __odsBottomSheetExpanding__ proposes open and close states, the expanding bottom sheet supports + /// multiple sizes or detents (small, medium and large). So a more complex and scrollable content can be proposed. + /// + /// The sheet expands when the user scrolls its contents, drags up or down the grabber (in header). A user can also tap the grabber to cycle + /// through the available sizes. + /// + /// This modifier adds a bottom sheet containing only title in header and a complex content. + /// It opens the sheet in the size provided by the initial value of a binding to a __ODSBottomSheetSize__ value. + /// The value is updated, when the sheet is expanded or closed (by swiping the content, tapping on header, tapping on dimming area, ...) + /// + /// The example below shows how to present a bottom sheet: + /// + /// struct BottomSheetPresentation: View { + /// @State private var bottomSheetSize: ODSBottomSheetSize = .large + /// + /// var body: some View { + /// VStack { + /// // Main content goes here. + /// Text("Bottom sheet size \(bottomSheetSize.rawValue)") + /// } + /// .odsBottomSheetExpanding(title: "Customize", bottomSheetSize: $bottomSheetSize) { + /// // Bottom sheet content goes here + /// } + /// } + /// } + /// + /// - Parameters: + /// - title: The title added in the header. + /// - bottomSheetSize: A binding to __ODSBottomSheetSize__ value that determines + /// the size of the sheet that you create in the modifier's `content` closure. + /// - content: A closure that returns the content of the bottom sheet. + /// + public func odsBottomSheetExpanding ( + title: String, + bottomSheetSize: Binding, + @ViewBuilder content: @escaping () -> Content + ) -> some View { + self.modifier(ODSBottomSheetExpandingModifier( + title: title, + subtile: nil, + icon: nil, + bottomSheetSize: bottomSheetSize, + content: content)) + } + + /// This modifier adds a bottom sheet containing title and subtitle in header and a complex content. + /// + /// - Parameters: + /// - title: The title added in the header. + /// - subtitle: The additionnal subtitle added in the header. + /// - bottomSheetSize: A binding to __ODSBottomSheetSize__ value that determines + /// the size of the sheet that you create in the modifier's `content` closure. + /// - content: A closure that returns the content of the bottom sheet. + /// + public func odsBottomSheetExpanding ( + title: String, + subtitle: String, + bottomSheetSize: Binding, + @ViewBuilder content: @escaping () -> Content + ) -> some View { + self.modifier(ODSBottomSheetExpandingModifier( + title: title, + subtile: subtitle, + icon: nil, + bottomSheetSize: bottomSheetSize, + content: content)) + } + + /// This modifier adds a bottom sheet containing title and icon in header and a complex content. + /// + /// - Parameters: + /// - title: The title added in the header. + /// - icon: The additional icon added near to the title in header. + /// - bottomSheetSize: A binding to __ODSBottomSheetSize__ value that determines + /// the size of the sheet that you create in the modifier's `content` closure. + /// - content: A closure that returns the content of the bottom sheet. + /// + public func odsBottomSheetExpanding ( + title: String, + icon: Image, + bottomSheetSize: Binding, + @ViewBuilder content: @escaping () -> Content + ) -> some View { + self.modifier(ODSBottomSheetExpandingModifier( + title: title, + subtile: nil, + icon: icon, + bottomSheetSize: bottomSheetSize, + content: content)) + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/ODSBottomSheetStandard.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/ODSBottomSheetStandard.swift new file mode 100644 index 00000000..c69c2d88 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/ODSBottomSheetStandard.swift @@ -0,0 +1,120 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import SwiftUI + +extension View { + + /// + /// ODS Sheet Bottom. + /// + /// Bottom sheets are surfaces anchored to the bottom of the screen that present users supplemental content. + /// It is useful for requesting a specific information or enabling a simple task related to the current context + /// of the current view or more globaly the application context. + /// + /// The standard bottom sheet must be used only with a "simple, basic" content. If a more complex content must be added + /// prefer the __ odsBottomSheetExpanding__ modifiers. + /// + /// + /// This modifier adds a bottom sheet containing only title in header. It opens the sheet + /// when a binding to a Boolean value that you provide is true. + /// + /// The example below shows how to present a bottom sheet: + /// + /// struct BottomSheetPresentation: View { + /// @State private var isOpen = false + /// + /// var body: some View { + /// VStack { + /// // Main content goes here. + /// Text("Bottom sheet is \(isOpen ? "Opened": "Closed")") + /// } + /// .odsBottomSheetStandard(isOpen: $isOpen, title: "Customize") { + /// // Bottom sheet content goes here + /// } + /// } + /// } + /// + /// - Parameters: + /// - isOpen: A binding to a Boolean value that determines whether + /// to open the sheet that you create in the modifier's + /// `content` closure. + /// - title: The title added in the header. + /// - content: A closure that returns the content of the bottom sheet. + /// + public func odsBottomSheetStandard ( + isOpen: Binding, + title: String, + @ViewBuilder content: @escaping () -> Content + ) -> some View { + self.modifier(ODSBottomSheetStandardModifier( + isOpen: isOpen, + headerConfig: ODSBottomSheetStandardHeaderConfig(title: title), + bottomSheetContent: content)) + } + + /// This modifier adds a bottom sheet containing title and subtitle in header. + /// + /// - Parameters: + /// - isOpen: A binding to a Boolean value that determines whether + /// to open the sheet that you create in the modifier's + /// `content` closure. + /// - title: The title added in the header. + /// - subtitle: Add a subtitle in header. + /// - content: A closure that returns the content of the bottom sheet. + /// + public func odsBottomSheetStandard ( + isOpen: Binding, + title: String, + subtitle: String, + @ViewBuilder content: @escaping () -> Content + ) -> some View { + self.modifier(ODSBottomSheetStandardModifier( + isOpen: isOpen, + headerConfig: ODSBottomSheetStandardHeaderConfig(title: title, subtitle: subtitle), + bottomSheetContent: content)) + } + + /// This modifier adds a bottom sheet containing title and icon in header. + /// + /// - Parameters: + /// - isOpen: A binding to a Boolean value that determines whether + /// to open the sheet that you create in the modifier's + /// `content` closure. + /// - title: The title added in the header. + /// - icon: Add a icon in header before title. + /// - animateIcon: To animate (ration to 180 degrees) when sheet is opening. + /// - content: A closure that returns the content of the bottom sheet. + /// + public func odsBottomSheetStandard ( + isOpen: Binding, + title: String, + icon: Image, + annimateIcon: Bool = true, + @ViewBuilder content: @escaping () -> Content + ) -> some View { + self.modifier(ODSBottomSheetStandardModifier( + isOpen: isOpen, + headerConfig: ODSBottomSheetStandardHeaderConfig(title: title, icon: icon, animateIcon: annimateIcon), + bottomSheetContent: content)) + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonStyles.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonStyles.swift index 9d6c2e46..13ebe0d1 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonStyles.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonStyles.swift @@ -29,7 +29,7 @@ import SwiftUI struct ODSButtonStyleModifier: ViewModifier { let emphasis: ODSButton.Emphasis @Environment(\.theme) var theme - + @ViewBuilder func body(content: Content) -> some View { switch emphasis { diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardSmall.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardSmall.swift index 53f1cee8..f47a9131 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardSmall.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardSmall.swift @@ -93,7 +93,7 @@ public struct ODSCardSmall: View { .accessibilityHidden(true) .frame(maxHeight: 100) .clipped() - + VStack(alignment: .leading, spacing: ODSSpacing.xs) { Text(model.title) .lineLimit(1) diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Lists/ODSListItemModels.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Lists/ODSListItemModels.swift index aed47c35..723f051c 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Lists/ODSListItemModels.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Lists/ODSListItemModels.swift @@ -172,4 +172,3 @@ public class ODSListSelectionItemModel: ODSListItemModel, ObservableObject { id = UUID() } } - diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Slider/ODSSliderView.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Slider/ODSSliderView.swift index 8c3715d4..f10e1824 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Slider/ODSSliderView.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Slider/ODSSliderView.swift @@ -29,95 +29,332 @@ import SwiftUI /// Based on the native `Slider`, the `ODSSLider` offers to the user the possibility /// to type direcly on the slider's track to get a value. /// -public struct ODSSlider: View where V: BinaryFloatingPoint, V.Stride: BinaryFloatingPoint, ValueLabel: View { - @Binding var value: V - public let range: ClosedRange - let step: V.Stride - let minimumValueLabel: () -> ValueLabel - let maximumValueLabel: () -> ValueLabel - - /// Creates an unlabeled slider to select a value from a given range, subject to a - /// step increment. + +// MARK: - Initializers with labels. +public struct ODSSlider where V: BinaryFloatingPoint, V.Stride: BinaryFloatingPoint, Label: View, ValueLabel: View { + + // ======================= + // MARK: Stored properties + // ======================= + + @Binding private var value: V + private let range: ClosedRange + private let label: Label + private let minimumValueLabel: ValueLabel + private let maximumValueLabel: ValueLabel + private let onEditingChanged: (Bool) -> Void + private let step: V.Stride? + + private let values: [V]? + @State private var isEditing: Bool { + didSet { + onEditingChanged(isEditing) + } + } + + // ================== + // MARK: Initializers + // ================== + + /// Creates a slider to select a value from a given range, subject to a + /// step increment, which displays the provided labels. /// /// - Parameters: - /// - value: The selected value within `range`. - /// - range: The range of the valid values. - /// - step: The distance between each valid value. default 1 + /// - value: The selected value within `bounds`. + /// - bounds: The range of the valid values. Defaults to `0...1`. + /// - step: The distance between each valid value. + /// - label: A `View` that describes the purpose of the instance. Not all + /// slider styles show the label, but even in those cases, SwiftUI + /// uses the label for accessibility. For example, VoiceOver uses the + /// label to identify the purpose of the slider. + /// - minimumValueLabel: A view that describes `bounds.lowerBound`. + /// - maximumValueLabel: A view that describes `bounds.upperBound`. + /// - onEditingChanged: A callback for when editing begins and ends. /// /// The `value` of the created instance is equal to the position of - /// the given value within `range`. + /// the given value within `bounds`. + /// + /// The slider calls `onEditingChanged` when editing begins and ends. For + /// example, on iOS, editing begins when the user starts to drag the thumb + /// along the slider's track. /// - public init(value: Binding, range: ClosedRange, step: V.Stride = 1) where ValueLabel == EmptyView { + /// - Remark: Accessibilty recommendation: + /// We recommand to not set information on `minimumValueLabel` and `maximumValueLabel` view using `.accessibilityHidden(true)` + /// + public init(value: Binding, in bounds: ClosedRange = 0...1, @ViewBuilder label: () -> Label, @ViewBuilder minimumValueLabel: () -> ValueLabel, @ViewBuilder maximumValueLabel: () -> ValueLabel, onEditingChanged: @escaping (Bool) -> Void = { _ in }) { + _value = value - self.range = range - self.step = step - maximumValueLabel = { - EmptyView() - } - minimumValueLabel = { - EmptyView() - } + self.range = bounds + self.step = nil + self.onEditingChanged = onEditingChanged + self.label = label() + self.minimumValueLabel = minimumValueLabel() + self.maximumValueLabel = maximumValueLabel() + + self.values = nil + self._isEditing = State(initialValue: false) } /// Creates a slider to select a value from a given range, subject to a - /// step increment. which displays the provided labels. + /// step increment, which displays the provided labels. /// /// - Parameters: - /// - value: The selected value within `range`. - /// - range: The range of the valid values. - /// - step: The distance between each valid value. default 1 - /// - minimumValueLabel: A view that describes `range.lowerBound`. - /// - maximumValueLabel: A view that describes `range.lowerBound`. + /// - value: The selected value within `bounds`. + /// - bounds: The range of the valid values. + /// - step: The distance between each valid value. + /// - label: A `View` that describes the purpose of the instance. Not all + /// slider styles show the label, but even in those cases, SwiftUI + /// uses the label for accessibility. For example, VoiceOver uses the + /// label to identify the purpose of the slider. + /// - minimumValueLabel: A view that describes `bounds.lowerBound`. + /// - maximumValueLabel: A view that describes `bounds.upperBound`. + /// - onEditingChanged: A callback for when editing begins and ends. /// /// The `value` of the created instance is equal to the position of - /// the given value within `range`. + /// the given value within `bounds`. + /// + /// The slider calls `onEditingChanged` when editing begins and ends. For + /// example, on iOS, editing begins when the user starts to drag the thumb + /// along the slider's track. + /// + /// - Remark: Accessibilty recommendation: + /// We recommand to not set information on `minimumValueLabel` and `maximumValueLabel` view using `.accessibilityHidden(true)` /// - public init(value: Binding, range: ClosedRange, step: V.Stride = 1, @ViewBuilder minimumLabelView: @escaping () -> ValueLabel, @ViewBuilder maximumLabelView: @escaping () -> ValueLabel) { + public init(value: Binding, in bounds: ClosedRange, step: V.Stride = 1, @ViewBuilder label: () -> Label, @ViewBuilder minimumValueLabel: () -> ValueLabel, @ViewBuilder maximumValueLabel: () -> ValueLabel, onEditingChanged: @escaping (Bool) -> Void = { _ in }) { + _value = value - self.range = range + self.range = bounds self.step = step - minimumValueLabel = minimumLabelView - maximumValueLabel = maximumLabelView + self.onEditingChanged = onEditingChanged + self.label = label() + self.minimumValueLabel = minimumValueLabel() + self.maximumValueLabel = maximumValueLabel() + + self.values = Array(stride(from: range.lowerBound, through: range.upperBound, by: step)) + self._isEditing = State(initialValue: false) + } +} + +// MARK: - Initializers with label, without value labels. +extension ODSSlider where ValueLabel == EmptyView { + + /// Creates a slider to select a value from a given range, which displays + /// the provided label. + /// + /// - Parameters: + /// - value: The selected value within `bounds`. + /// - bounds: The range of the valid values. Defaults to `0...1`. + /// - label: A `View` that describes the purpose of the instance. Not all + /// slider styles show the label, but even in those cases, SwiftUI + /// uses the label for accessibility. For example, VoiceOver uses the + /// label to identify the purpose of the slider. + /// - onEditingChanged: A callback for when editing begins and ends. + /// + /// The `value` of the created instance is equal to the position of + /// the given value within `bounds`, mapped into `0...1`. + /// + /// The slider calls `onEditingChanged` when editing begins and ends. For + /// example, on iOS, editing begins when the user starts to drag the thumb + /// along the slider's track. + public init(value: Binding, in bounds: ClosedRange = 0...1, @ViewBuilder label: () -> Label, onEditingChanged: @escaping (Bool) -> Void = { _ in }) { + self.init( + value: value, + in: bounds, + label: label, + minimumValueLabel: { EmptyView() }, + maximumValueLabel: { EmptyView() }, + onEditingChanged: onEditingChanged) + } + + /// Creates a slider to select a value from a given range, subject to a + /// step increment. which displays the provided label. + /// + /// - Parameters: + /// - value: The selected value within `bounds`. + /// - bounds: The range of the valid values. Defaults to `0...1`. + /// - step: The distance between each valid value. + /// - label: A `View` that describes the purpose of the instance. Not all + /// slider styles show the label, but even in those cases, SwiftUI + /// uses the label for accessibility. For example, VoiceOver uses the + /// label to identify the purpose of the slider. + /// - onEditingChanged: A callback for when editing begins and ends. + /// + /// The `value` of the created instance is equal to the position of + /// the given value within `bounds`. + /// + /// The slider calls `onEditingChanged` when editing begins and ends. For + /// example, on iOS, editing begins when the user starts to drag the thumb + /// along the slider's track. + public init(value: Binding, in bounds: ClosedRange, step: V.Stride = 1, @ViewBuilder label: () -> Label, onEditingChanged: @escaping (Bool) -> Void = { _ in }) { + self.init( + value: value, + in: bounds, + step: step, + label: label, + minimumValueLabel: { EmptyView() }, + maximumValueLabel: { EmptyView() }, + onEditingChanged: onEditingChanged) + } +} + +// MARK: - Initializers without labels. +extension ODSSlider where ValueLabel == EmptyView, Label == EmptyView { + + /// Creates a slider to select a value from a given range. + /// + /// - Parameters: + /// - value: The selected value within `bounds`. + /// - bounds: The range of the valid values. Defaults to `0...1`. + /// - onEditingChanged: A callback for when editing begins and ends. + /// + /// The `value` of the created instance is equal to the position of + /// the given value within `bounds`, mapped into `0...1`. + /// + /// The slider calls `onEditingChanged` when editing begins and ends. For + /// example, on iOS, editing begins when the user starts to drag the thumb + /// along the slider's track. + public init(value: Binding, in bounds: ClosedRange = 0...1, onEditingChanged: @escaping (Bool) -> Void = { _ in }) { + self.init( + value: value, + in: bounds, + label: { EmptyView() }, + minimumValueLabel: { EmptyView() }, + maximumValueLabel: { EmptyView() }, + onEditingChanged: onEditingChanged) } + /// Creates a slider to select a value from a given range, subject to a + /// step increment. + /// + /// - Parameters: + /// - value: The selected value within `bounds`. + /// - bounds: The range of the valid values. + /// - onEditingChanged: A callback for when editing begins and ends. + /// + /// The `value` of the created instance is equal to the position of + /// the given value within `bounds`. + /// + /// The slider calls `onEditingChanged` when editing begins and ends. For + /// example, on iOS, editing begins when the user starts to drag the thumb + /// along the slider's track. + public init(value: Binding, in bounds: ClosedRange, step: V.Stride = 1, onEditingChanged: @escaping (Bool) -> Void = { _ in }) { + self.init( + value: value, + in: bounds, + step: step, + label: { EmptyView() }, + minimumValueLabel: { EmptyView() }, + maximumValueLabel: { EmptyView() }, + onEditingChanged: onEditingChanged) + } +} + +// MARK: - View implementation. +extension ODSSlider: View { + + // ========== + // MARK: Body + // ========== + public var body: some View { VStack { HStack(alignment: .center) { - minimumValueLabel() + minimumValueLabel GeometryReader { geometry in - Slider( - value: $value, - in: range, - step: step) - .gesture(DragGesture(minimumDistance: 0).onChanged { value in - let percent = min(max(0, Float(value.location.x / geometry.size.width * 1)), 1) - let newValue = self.range.lowerBound + round(V(Double(percent)) * (self.range.upperBound - self.range.lowerBound)) - let rounded = round(V.Stride(newValue) / step) * step - self.$value.wrappedValue = V(rounded) - }) - .frame( - width: geometry.size.width, - height: geometry.size.height, - alignment: .center) + slider + .gesture( + DragGesture(minimumDistance: 0) + .onChanged { value in + let newValue = computeNewValue(for: value.location.x, in: geometry.size.width) + if !isEditing { + if newValue != self.$value.wrappedValue { + self.isEditing = true + } + } + + self.$value.wrappedValue = newValue + } + .onEnded { value in + self.$value.wrappedValue = computeNewValue(for: value.location.x, in: geometry.size.width) + self.isEditing = false + } + ) + .frame( + width: geometry.size.width, + height: geometry.size.height, + alignment: .center) } - maximumValueLabel() + maximumValueLabel } } } + + // ===================== + // MARK: private helpers + // ===================== + @ViewBuilder + var slider: some View { + if let step = self.step { + Slider(value: $value, in: range, step: step) { + self.label + } + } else { + Slider(value: $value, in: range) { + self.label + } + } + } + + private func computeNewValue(for xPosition: Double, in globalWidth: Double) -> V { + if xPosition >= globalWidth { + return range.upperBound + } else { + if xPosition <= 0 { + return range.lowerBound + } else { + let percent = xPosition / globalWidth + let computedValue = (V(percent) * (self.range.upperBound - self.range.lowerBound)) + self.range.lowerBound + + // Adjust newValue according to step + return adjustNewValue(from: computedValue) + } + } + } + + private func adjustNewValue(from computedValue: V) -> V { + guard let values = self.values else { + return computedValue + } + + var newValue = computedValue + var distance: V.Stride = .infinity + + for value in values { + let newDistance = value.distance(to: computedValue) + if abs(newDistance) < abs(distance) { + distance = newDistance + newValue = value + } else { + return newValue + } + } + + return newValue + } } #if DEBUG +// MARK: - Previews. struct ODSSlider_Previews: PreviewProvider { static var previews: some View { - VStack { ODSSlider( value: .constant(50), - range: 0 ... 100.0) + in: 0 ... 100.0) .padding([.leading, .trailing], ODSSpacing.s) } } @@ -126,15 +363,16 @@ struct ODSSlider_Previews: PreviewProvider { struct ODSSlider_Previews_with_label: PreviewProvider { static var previews: some View { - VStack { - ODSSlider( - value: .constant(50), - range: 0 ... 100.0) - { + ODSSlider(value: .constant(50), + in: 0 ... 100.0) { + Text("Spead") + } minimumValueLabel: { Image(systemName: "speaker.wave.1.fill") - } maximumLabelView: { + } maximumValueLabel: { Image(systemName: "speaker.wave.3.fill") + } onEditingChanged: { isEditing in + print("isEditing(\(isEditing))") } .padding([.leading, .trailing], ODSSpacing.s) } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/Modifiers/ODSTabBarModifier.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/Modifiers/ODSTabBarModifier.swift index 143c66fd..137a9d74 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/Modifiers/ODSTabBarModifier.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/Modifiers/ODSTabBarModifier.swift @@ -54,15 +54,16 @@ extension View { itemAppearance.normal.badgeBackgroundColor = uiBadgeColor itemAppearance.selected.badgeBackgroundColor = uiBadgeColor } - + let appearance = UITabBarAppearance() appearance.configureWithOpaqueBackground() + appearance.configureWithTransparentBackground() appearance.backgroundColor = backgroundColor?.uiColor - + appearance.stackedLayoutAppearance = itemAppearance appearance.inlineLayoutAppearance = itemAppearance appearance.compactInlineLayoutAppearance = itemAppearance - + UITabBar.appearance().standardAppearance = appearance UITabBar.appearance().scrollEdgeAppearance = appearance } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/ODSComponentsColors.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/ODSComponentsColors.swift index bb3e9dd3..847a131c 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/ODSComponentsColors.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/ODSComponentsColors.swift @@ -53,6 +53,9 @@ public struct ODSComponentColors { public var functionalAlert: Color public var functionalInfo: Color + // Bottom sheet + public var bottomSheetHeaderBackground: Color + // ================== // MARK: Initializers // ================== @@ -80,5 +83,8 @@ public struct ODSComponentColors { self.functionalPositive = .green self.functionalInfo = .blue self.functionalAlert = .yellow + + // Bottom sheet + self.bottomSheetHeaderBackground = Color(UIColor.systemGray6) } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/View/ODSThemeableView.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/View/ODSThemeableView.swift index 4aa1f088..e4f65a38 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/View/ODSThemeableView.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/View/ODSThemeableView.swift @@ -52,18 +52,18 @@ import SwiftUI /// public struct ODSThemeableView: View where Content: View { - + // ======================= // MARK: Stored Properties // ======================= private let theme: ODSTheme private let content: () -> Content - + // ================== // MARK: Initializers // ================== - + /// Creates an instance with the theme to be applied. /// /// - Parameters: @@ -76,7 +76,7 @@ public struct ODSThemeableView: View where Content: View { self.content = content self.navigationBarColors(for: theme) } - + public var body: some View { content() .accentColor(theme.componentColors.accent) @@ -86,3 +86,4 @@ public struct ODSThemeableView: View where Content: View { .toolBarColors(for: theme) } } + diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/TabBar+readSize.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/TabBar+readSize.swift new file mode 100644 index 00000000..b19d4a40 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/TabBar+readSize.swift @@ -0,0 +1,85 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +import SwiftUI + +extension View { + func readTabBarHeight( _ onChange: @escaping (CGFloat) -> Void) -> some View { + self.configureTabBar { controller, _ in + onChange(controller.tabBar.bounds.height) + } + } + + func configureTabBar(configurator: @escaping (UITabBarController, Bool) -> Void) -> some View { + modifier(TabBarConfigurationViewModifier(configurator: configurator)) + } +} + +struct TabBarConfigurationViewModifier: ViewModifier { + let configurator: (UITabBarController, Bool) -> Void + + func body(content: Content) -> some View { + content + .background(TabBarConfigurator(configurator: configurator)) + } +} + +struct TabBarConfigurator: UIViewControllerRepresentable { + let configurator: (UITabBarController, Bool) -> Void + + func makeUIViewController(context: Context) -> TabBarConfigurationViewController { + TabBarConfigurationViewController(configurator: configurator) + } + + func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { + } +} + +class TabBarConfigurationViewController: UIViewController { + let configurator: (UITabBarController, Bool) -> Void + + init(configurator: @escaping (UITabBarController, Bool) -> Void) { + self.configurator = configurator + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let tabBarController = self.tabBarController { + configurator(tabBarController, true) + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if let tabBarController = self.tabBarController { + configurator(tabBarController, false) + } + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcodeproj/project.pbxproj b/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcodeproj/project.pbxproj index c3132f50..9729412e 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcodeproj/project.pbxproj +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcodeproj/project.pbxproj @@ -19,7 +19,10 @@ 07387C6328F062D900D8721F /* ListComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07387C6228F062D900D8721F /* ListComponent.swift */; }; 07387C6928F0641400D8721F /* StandardListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07387C6828F0641400D8721F /* StandardListModel.swift */; }; 0752EC2828EDBB540029A7BE /* ChipsComponentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0752EC2728EDBB540029A7BE /* ChipsComponentModel.swift */; }; - 0752EC2A28EDD7A80029A7BE /* BottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0752EC2928EDD7A80029A7BE /* BottomSheet.swift */; }; + 07535BAA29D31A440012F298 /* BottomSheetStandardVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07535BA929D31A440012F298 /* BottomSheetStandardVariant.swift */; }; + 07535BAF29D713320012F298 /* BottomSheetExpandingVariantOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07535BAD29D713320012F298 /* BottomSheetExpandingVariantOptions.swift */; }; + 07535BB029D713320012F298 /* BottomSheetExpandingVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07535BAE29D713320012F298 /* BottomSheetExpandingVariant.swift */; }; + 07535BCF29DC57E60012F298 /* CustomizableVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07535BCE29DC57E60012F298 /* CustomizableVariant.swift */; }; 075462FA28F474CC002E2E40 /* ButtonsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075462F728F474CC002E2E40 /* ButtonsComponent.swift */; }; 075462FC28F474CC002E2E40 /* EmphasisAndFunctionnalVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075462F928F474CC002E2E40 /* EmphasisAndFunctionnalVariant.swift */; }; 075E5D6D29378735009A801B /* RecipesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075E5D6C29378735009A801B /* RecipesLoader.swift */; }; @@ -65,6 +68,7 @@ 07F141C0298BC213007C8575 /* CardHorizontalVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F141BF298BC213007C8575 /* CardHorizontalVariant.swift */; }; 07F141C2298C0644007C8575 /* RecipeBookModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F141C1298C0644007C8575 /* RecipeBookModel.swift */; }; 07F141C4298CF4CB007C8575 /* RecipesBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F141C3298CF4CB007C8575 /* RecipesBook.swift */; }; + 07F141C829928B96007C8575 /* BottomSheetComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F141C629928B96007C8575 /* BottomSheetComponent.swift */; }; 07F343862943458D0043335A /* ProgressIndicatorComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F343852943458C0043335A /* ProgressIndicatorComponent.swift */; }; C88E9E08333D56BAEA28A60E /* Pods_OrangeDesignSystemDemoTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E816C9278060A8072007BFD0 /* Pods_OrangeDesignSystemDemoTests.framework */; }; F90D9768280030A6006D29FC /* TypographyPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90D9765280030A6006D29FC /* TypographyPage.swift */; }; @@ -124,7 +128,10 @@ 07387C6228F062D900D8721F /* ListComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListComponent.swift; sourceTree = ""; }; 07387C6828F0641400D8721F /* StandardListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardListModel.swift; sourceTree = ""; }; 0752EC2728EDBB540029A7BE /* ChipsComponentModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipsComponentModel.swift; sourceTree = ""; }; - 0752EC2928EDD7A80029A7BE /* BottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheet.swift; sourceTree = ""; }; + 07535BA929D31A440012F298 /* BottomSheetStandardVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetStandardVariant.swift; sourceTree = ""; }; + 07535BAD29D713320012F298 /* BottomSheetExpandingVariantOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BottomSheetExpandingVariantOptions.swift; path = Expanding/BottomSheetExpandingVariantOptions.swift; sourceTree = ""; }; + 07535BAE29D713320012F298 /* BottomSheetExpandingVariant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BottomSheetExpandingVariant.swift; path = Expanding/BottomSheetExpandingVariant.swift; sourceTree = ""; }; + 07535BCE29DC57E60012F298 /* CustomizableVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizableVariant.swift; sourceTree = ""; }; 075462F728F474CC002E2E40 /* ButtonsComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonsComponent.swift; sourceTree = ""; }; 075462F928F474CC002E2E40 /* EmphasisAndFunctionnalVariant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmphasisAndFunctionnalVariant.swift; sourceTree = ""; }; 075E5D6C29378735009A801B /* RecipesLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipesLoader.swift; sourceTree = ""; }; @@ -167,6 +174,7 @@ 07F141BF298BC213007C8575 /* CardHorizontalVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardHorizontalVariant.swift; sourceTree = ""; }; 07F141C1298C0644007C8575 /* RecipeBookModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeBookModel.swift; sourceTree = ""; }; 07F141C3298CF4CB007C8575 /* RecipesBook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipesBook.swift; sourceTree = ""; }; + 07F141C629928B96007C8575 /* BottomSheetComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomSheetComponent.swift; sourceTree = ""; }; 07F343852943458C0043335A /* ProgressIndicatorComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressIndicatorComponent.swift; sourceTree = ""; }; 2ACFE972C59B1460F410852D /* Pods-OrangeDesignSystemDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OrangeDesignSystemDemo.debug.xcconfig"; path = "Target Support Files/Pods-OrangeDesignSystemDemo/Pods-OrangeDesignSystemDemo.debug.xcconfig"; sourceTree = ""; }; 759CBF84E701D1BE3D68D365 /* Pods_OrangeDesignSystemDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OrangeDesignSystemDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -323,6 +331,23 @@ path = Chips; sourceTree = ""; }; + 07535BAB29D31ACA0012F298 /* Extending */ = { + isa = PBXGroup; + children = ( + 07535BAE29D713320012F298 /* BottomSheetExpandingVariant.swift */, + 07535BAD29D713320012F298 /* BottomSheetExpandingVariantOptions.swift */, + ); + name = Extending; + sourceTree = ""; + }; + 07535BAC29D31AD60012F298 /* Standard */ = { + isa = PBXGroup; + children = ( + 07535BA929D31A440012F298 /* BottomSheetStandardVariant.swift */, + ); + path = Standard; + sourceTree = ""; + }; 075462F628F474CC002E2E40 /* Buttons */ = { isa = PBXGroup; children = ( @@ -364,6 +389,7 @@ 07C8233D28AE68B2003B2C3A /* Pages */ = { isa = PBXGroup; children = ( + 07F141C529928B96007C8575 /* BottomSheet */, 07D3AEDE296813DA00B36C3F /* ToolBar */, 07CC452A2923CF9C008BE71F /* Banners */, 075462F628F474CC002E2E40 /* Buttons */, @@ -394,8 +420,8 @@ isa = PBXGroup; children = ( 07C8235828AE690F003B2C3A /* ComponentPage.swift */, - 0752EC2928EDD7A80029A7BE /* BottomSheet.swift */, 07387C5C28F0068B00D8721F /* Component.swift */, + 07535BCE29DC57E60012F298 /* CustomizableVariant.swift */, ); name = Template; path = OrangeDesignSystemDemo/Views/Components/Template; @@ -441,6 +467,16 @@ path = Cards; sourceTree = ""; }; + 07F141C529928B96007C8575 /* BottomSheet */ = { + isa = PBXGroup; + children = ( + 07535BAC29D31AD60012F298 /* Standard */, + 07535BAB29D31ACA0012F298 /* Extending */, + 07F141C629928B96007C8575 /* BottomSheetComponent.swift */, + ); + path = BottomSheet; + sourceTree = ""; + }; 174EE48FFD62B367205CFF78 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -920,6 +956,7 @@ F90D978728005A4A006D29FC /* ComponentList.swift in Sources */, 07387C6928F0641400D8721F /* StandardListModel.swift in Sources */, 0789AB992934C35B00796B82 /* SliderComponent.swift in Sources */, + 07535BAA29D31A440012F298 /* BottomSheetStandardVariant.swift in Sources */, 07973C54287C7470004397D7 /* ODSColors+extension.swift in Sources */, F90D977428003104006D29FC /* ODSDemoAboutView.swift in Sources */, F96A3E19280451660086B9BF /* ColorDetail.swift in Sources */, @@ -937,11 +974,14 @@ F90D978528003299006D29FC /* AboutConfigDemo.swift in Sources */, F90D9768280030A6006D29FC /* TypographyPage.swift in Sources */, F96A3E17280451660086B9BF /* ColorsPage.swift in Sources */, + 07F141C829928B96007C8575 /* BottomSheetComponent.swift in Sources */, 07C8234F28AE68B2003B2C3A /* TextFieldComponent.swift in Sources */, 071D3EF128884D8200DFD1C9 /* SpacingsPage.swift in Sources */, + 07535BCF29DC57E60012F298 /* CustomizableVariant.swift in Sources */, 07C8236828AE823F003B2C3A /* ThemeSelectionView.swift in Sources */, F90D9768280030A6006D29FC /* TypographyPage.swift in Sources */, 07387C5F28F02F4F00D8721F /* SelectionList.swift in Sources */, + 07535BAF29D713320012F298 /* BottomSheetExpandingVariantOptions.swift in Sources */, F96A3E17280451660086B9BF /* ColorsPage.swift in Sources */, 07C8234F28AE68B2003B2C3A /* TextFieldComponent.swift in Sources */, 075E612C299AA988004CE0A6 /* ColorsGuideline.swift in Sources */, @@ -953,7 +993,6 @@ 07C8235028AE68B2003B2C3A /* ChipsComponent.swift in Sources */, 07081296293E29A7002E38BB /* ProgressBarVariant.swift in Sources */, 07000FB7292B7B0700CE537A /* NavigatinBarModifiers.swift in Sources */, - 0752EC2A28EDD7A80029A7BE /* BottomSheet.swift in Sources */, 07C52F2028D37A2B0067CFC0 /* CardExampleData.swift in Sources */, 075E612329968A56004CE0A6 /* SecureVariant.swift in Sources */, 072DE105296DCE3E00229FCF /* ToastView.swift in Sources */, @@ -964,6 +1003,7 @@ F96A3E18280451660086B9BF /* ColorUsage.swift in Sources */, 075E5D6D29378735009A801B /* RecipesLoader.swift in Sources */, F90D977528003104006D29FC /* ODSDemoAboutConfig.swift in Sources */, + 07535BB029D713320012F298 /* BottomSheetExpandingVariant.swift in Sources */, 07C8235928AE690F003B2C3A /* ComponentPage.swift in Sources */, 07F141C2298C0644007C8575 /* RecipeBookModel.swift in Sources */, 07387C6328F062D900D8721F /* ListComponent.swift in Sources */, @@ -1158,7 +1198,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.11.2; + MARKETING_VERSION = 0.12.0; PRODUCT_BUNDLE_IDENTIFIER = "soft.cocoa.ods-ios-demo.dev"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1193,7 +1233,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.11.2; + MARKETING_VERSION = 0.12.0; PRODUCT_BUNDLE_IDENTIFIER = "soft.cocoa.ods-ios-demo.dev"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..df102642 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "BottomSheet", + "repositoryURL": "https://github.com/lucaszischka/BottomSheet", + "state": { + "branch": null, + "revision": "4c9ef84552712e0117c37d4893270fdc28fb9288", + "version": "3.1.0" + } + } + ] + }, + "version": 1 +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIcCafe.imageset/Contents.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/Cafe.imageset/Contents.json similarity index 100% rename from OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIcCafe.imageset/Contents.json rename to OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/Cafe.imageset/Contents.json diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIcCafe.imageset/iconsCommunicationDIcCafe.svg b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/Cafe.imageset/iconsCommunicationDIcCafe.svg similarity index 100% rename from OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIcCafe.imageset/iconsCommunicationDIcCafe.svg rename to OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/Cafe.imageset/iconsCommunicationDIcCafe.svg diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIcCookingPot.imageset/Contents.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/CookingPot.imageset/Contents.json similarity index 100% rename from OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIcCookingPot.imageset/Contents.json rename to OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/CookingPot.imageset/Contents.json diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIcCookingPot.imageset/iconsCommunicationDIcCookingPot.svg b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/CookingPot.imageset/iconsCommunicationDIcCookingPot.svg similarity index 100% rename from OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIcCookingPot.imageset/iconsCommunicationDIcCookingPot.svg rename to OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/CookingPot.imageset/iconsCommunicationDIcCookingPot.svg diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIIcIceCream.imageset/Contents.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/IceCream.imageset/Contents.json similarity index 100% rename from OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIIcIceCream.imageset/Contents.json rename to OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/IceCream.imageset/Contents.json diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIIcIceCream.imageset/iconsCommunicationDIIcIceCream.svg b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/IceCream.imageset/iconsCommunicationDIIcIceCream.svg similarity index 100% rename from OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationDIIcIceCream.imageset/iconsCommunicationDIIcIceCream.svg rename to OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/IceCream.imageset/iconsCommunicationDIIcIceCream.svg diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationRUIcRestaurant.imageset/Contents.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/Restaurant.imageset/Contents.json similarity index 100% rename from OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationRUIcRestaurant.imageset/Contents.json rename to OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/Restaurant.imageset/Contents.json diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationRUIcRestaurant.imageset/iconsCommunicationRUIcRestaurant.svg b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/Restaurant.imageset/iconsCommunicationRUIcRestaurant.svg similarity index 100% rename from OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/iconsCommunicationRUIcRestaurant.imageset/iconsCommunicationRUIcRestaurant.svg rename to OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/Recipes/Restaurant.imageset/iconsCommunicationRUIcRestaurant.svg diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/AboutImage_generic.imageset/AboutImage_generic.png b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/AboutImage_generic.imageset/AboutImage_generic.png new file mode 100644 index 0000000000000000000000000000000000000000..eef8c253c9686e662a045ab44c83c96f02ea1f41 GIT binary patch literal 32183 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 F{{d{W{(k@f literal 0 HcmV?d00001 diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/AboutImage_generic.imageset/AboutImage_generic.svg b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/AboutImage_generic.imageset/AboutImage_generic.svg deleted file mode 100644 index 696bbd0f..00000000 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/AboutImage_generic.imageset/AboutImage_generic.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - Image - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/AboutImage_generic.imageset/Contents.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/AboutImage_generic.imageset/Contents.json index 4adf4f7d..b73e2676 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/AboutImage_generic.imageset/Contents.json +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/AboutImage_generic.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "AboutImage_generic.svg", + "filename" : "AboutImage_generic.png", "idiom" : "universal" } ], diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/BottomSheet_generic.imageset/BottomSheet_generic.png b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/BottomSheet_generic.imageset/BottomSheet_generic.png new file mode 100644 index 0000000000000000000000000000000000000000..4a053f8be4bbee690863cc94dbd006aa8a6c630b GIT binary patch literal 3383 zcmds4eNRyNf>UY`Z- z{^_y$ySTQMf1Kjy^-j-)8=Rsh^m~y~R2jWrmv1&hw}^E)xf>=|zxrd*aQm)vZ|?F9 z9n8$VBA;>9CG->SyJ#x7!CyEV9me+r92b8BpBYUBfBMz_CHP`0T0ALirO1vv$Ov0$ zxyRvz_|MSmnUToux&2GfGW8s@KiT#D;TocF=jWN5Yu^T{u@w0tv~wUtP^Q?q#nHRJ ztiTX=e!JFC7%kiShC`^}gu>})@u6=MDje0ZsPp4n+vvVg^#f6TLe{_x1ub9qsB1$D z>R;Cy>Om*UBDAggv$FG}4kMcTMI}w^pNL6c&w$bPrRvNz3+gh+w3D*)@~x(2<>eMB zB%12WNvd?*6(Vp@?0H!_!rD z=tDB$`({*3{;;6#D=0w~(yms|l8bPQo2R`76=sQp@1RFrix)PWS@{ zk;=gz<0V@JlK@j5_Ia*V$aFvY49e!x}KEqw!V+?8+7bSrrzs*j|iYlu19_oe0Y+uB26S_QrD(`CI4Y0437?S$fic~WqoGbN6 zt&X_(X@wrO=NKt&IpG7L3VSl8^r7KLme6{k za(xSks$K6W=f?dlOgj_QRn6H=bB1=7w-DzV&{*G;^bU{?Jr%h#dD_98tBph`ggcE# zMoYNAjH0^gwe%da6$nFk&f3f2*~waMPMCHh(Bz~{GXLBR93?(wP!(D+OH+*%&Dc>H z$_=0oj)v&s>Gq8t2zkbv-Wq$y;26E-mcPek*j)gGAEjK-yaU5XDO83&VfEZLe(f)q z_C8tY*`LWEwJ^!(Z?+$LogKXNlqjkmZP*V3v*GBs3(7L2Du7pR7wmrIE)<2O!9J8b zjT|$*AJ~&BQpHA6ML!bZX$&&M)83f)#n@h4iUii!06PV7OuF4J z9^Ov6oa0hKmh~9FgC@=>_#MDekMg)Wis}G7@_>{PZa{ZgB`LJcnPl>mwL9?EugFIkwF4vs zOwVCvo#^%I2iAGe|_ zTPzU*qA(Mdf~8Nf<8fZbX|hBf_tH0^&hL>v?Z*jaEBmFvyzvx!@ra4bFa6I5#dM53 zUgr0G%(g*R0EOpuTINw}aHf_REdd-_Q$6wbUt98QQ9WFqX6Vp--c^y)94xr|)GdJp0Qf;e%*{A5HL_Ym&)$U}({!Lo%IfH=MtWI;kLZ z%qNRA=nr!48h3j+mr{}bIs*~RvNy5792VZ;rpvjc#OIRc`n^H&2Tj@uJ^FrmW@Q;b z6yCmc7g!p?S`G5&fq>cMt7P8*n!dO@Lz+IaOE5*0-#LH5tjM`1XLs#>1;o!$X23HZ zV1>i&-eMT&o$W5{HMMLf=U3zZLjupxs{RJI=C-rZl6;e?#V>CKgFJU-U%a3=ZMj0< zn5868iIb)o%?EA^>Wt>Xt((A8I_^|r*3S#-l1_JEix%Q$iIX`Eetj}xo|333*%O}y z6kZihvAwkv*&&V8AEidy8|+48-PC>bm`t6aSUf!Mzzp`SAOID)`F3CuP@&1L;>)x8 z){D}EJYqw4M8ek+M7@GOYj7#1Zdkrf1m8npg5J;jTtJ<9@0y!t@%sI68aFA>{<>ml z250d0W`RsRH74ySCq}|vD_WAnnACZ1+0Y46@CC@qK5A~CB7RL0tvIM?B~3ws;$!mf zY`|Rc)!43i!z+7U1t@DPh4Eeww`xR^J)4%?y7tVZd()CNv)C%semO3yQe013%v$g_ z-*CYxvWP5(mgk;A1={PnN>zYAE7-fgf&=W|X1ivJ4`9Pfjir5Vrw$QI!7+>y8df;V z;Kje<$O)Y$!zoN|_4cj=UGEVOdq1}8>2i8#!IBcV`aU*D?Hn`64>IyU`S<657e|8U mr-|oBh#UN8aJ4--Hmx`yKKis{Tr589SowJSdKE7JGW8z|_wFqK literal 0 HcmV?d00001 diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/BottomSheet_generic.imageset/Contents.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/BottomSheet_generic.imageset/Contents.json new file mode 100644 index 00000000..5a4598f6 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Generic/BottomSheet_generic.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "BottomSheet_generic.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Orange/BottomSheet.imageset/Contents.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Orange/BottomSheet.imageset/Contents.json new file mode 100644 index 00000000..db609ce3 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Orange/BottomSheet.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "bottomSheet.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Orange/BottomSheet.imageset/bottomSheet.png b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Assets.xcassets/thumbs/Orange/BottomSheet.imageset/bottomSheet.png new file mode 100644 index 0000000000000000000000000000000000000000..4a053f8be4bbee690863cc94dbd006aa8a6c630b GIT binary patch literal 3383 zcmds4eNRyNf>UY`Z- z{^_y$ySTQMf1Kjy^-j-)8=Rsh^m~y~R2jWrmv1&hw}^E)xf>=|zxrd*aQm)vZ|?F9 z9n8$VBA;>9CG->SyJ#x7!CyEV9me+r92b8BpBYUBfBMz_CHP`0T0ALirO1vv$Ov0$ zxyRvz_|MSmnUToux&2GfGW8s@KiT#D;TocF=jWN5Yu^T{u@w0tv~wUtP^Q?q#nHRJ ztiTX=e!JFC7%kiShC`^}gu>})@u6=MDje0ZsPp4n+vvVg^#f6TLe{_x1ub9qsB1$D z>R;Cy>Om*UBDAggv$FG}4kMcTMI}w^pNL6c&w$bPrRvNz3+gh+w3D*)@~x(2<>eMB zB%12WNvd?*6(Vp@?0H!_!rD z=tDB$`({*3{;;6#D=0w~(yms|l8bPQo2R`76=sQp@1RFrix)PWS@{ zk;=gz<0V@JlK@j5_Ia*V$aFvY49e!x}KEqw!V+?8+7bSrrzs*j|iYlu19_oe0Y+uB26S_QrD(`CI4Y0437?S$fic~WqoGbN6 zt&X_(X@wrO=NKt&IpG7L3VSl8^r7KLme6{k za(xSks$K6W=f?dlOgj_QRn6H=bB1=7w-DzV&{*G;^bU{?Jr%h#dD_98tBph`ggcE# zMoYNAjH0^gwe%da6$nFk&f3f2*~waMPMCHh(Bz~{GXLBR93?(wP!(D+OH+*%&Dc>H z$_=0oj)v&s>Gq8t2zkbv-Wq$y;26E-mcPek*j)gGAEjK-yaU5XDO83&VfEZLe(f)q z_C8tY*`LWEwJ^!(Z?+$LogKXNlqjkmZP*V3v*GBs3(7L2Du7pR7wmrIE)<2O!9J8b zjT|$*AJ~&BQpHA6ML!bZX$&&M)83f)#n@h4iUii!06PV7OuF4J z9^Ov6oa0hKmh~9FgC@=>_#MDekMg)Wis}G7@_>{PZa{ZgB`LJcnPl>mwL9?EugFIkwF4vs zOwVCvo#^%I2iAGe|_ zTPzU*qA(Mdf~8Nf<8fZbX|hBf_tH0^&hL>v?Z*jaEBmFvyzvx!@ra4bFa6I5#dM53 zUgr0G%(g*R0EOpuTINw}aHf_REdd-_Q$6wbUt98QQ9WFqX6Vp--c^y)94xr|)GdJp0Qf;e%*{A5HL_Ym&)$U}({!Lo%IfH=MtWI;kLZ z%qNRA=nr!48h3j+mr{}bIs*~RvNy5792VZ;rpvjc#OIRc`n^H&2Tj@uJ^FrmW@Q;b z6yCmc7g!p?S`G5&fq>cMt7P8*n!dO@Lz+IaOE5*0-#LH5tjM`1XLs#>1;o!$X23HZ zV1>i&-eMT&o$W5{HMMLf=U3zZLjupxs{RJI=C-rZl6;e?#V>CKgFJU-U%a3=ZMj0< zn5868iIb)o%?EA^>Wt>Xt((A8I_^|r*3S#-l1_JEix%Q$iIX`Eetj}xo|333*%O}y z6kZihvAwkv*&&V8AEidy8|+48-PC>bm`t6aSUf!Mzzp`SAOID)`F3CuP@&1L;>)x8 z){D}EJYqw4M8ek+M7@GOYj7#1Zdkrf1m8npg5J;jTtJ<9@0y!t@%sI68aFA>{<>ml z250d0W`RsRH74ySCq}|vD_WAnnACZ1+0Y46@CC@qK5A~CB7RL0tvIM?B~3ws;$!mf zY`|Rc)!43i!z+7U1t@DPh4Eeww`xR^J)4%?y7tVZd()CNv)C%semO3yQe013%v$g_ z-*CYxvWP5(mgk;A1={PnN>zYAE7-fgf&=W|X1ivJ4`9Pfjir5Vrw$QI!7+>y8df;V z;Kje<$O)Y$!zoN|_4cj=UGEVOdq1}8>2i8#!IBcV`aU*D?Hn`64>IyU`S<657e|8U mr-|oBh#UN8aJ4--Hmx`yKKis{Tr589SowJSdKE7JGW8z|_wFqK literal 0 HcmV?d00001 diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/OrangeDesignSystemDemoApp.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/OrangeDesignSystemDemoApp.swift index 3509144c..50563e8c 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/OrangeDesignSystemDemoApp.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/OrangeDesignSystemDemoApp.swift @@ -27,7 +27,7 @@ import SwiftUI @main struct ods_ios_swiftUI_demoApp: App { @StateObject var themeProvider = ThemeProvider() - + var body: some Scene { WindowGroup { ODSThemeableView(theme: themeProvider.currentTheme) { diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Themes/ThemeSelectionView.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Themes/ThemeSelectionView.swift index 692e507f..3b4ba22b 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Themes/ThemeSelectionView.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Themes/ThemeSelectionView.swift @@ -141,6 +141,7 @@ struct HotSwhitchIndicatorModifier: ViewModifier { self.hotSwitchWarningIndicator = hotSwitchWarningIndicator } + @ViewBuilder func body(content: Content) -> some View { content .alert("Warning", isPresented: $hotSwitchWarningIndicator.showAlert) { diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/About/ODSDemoAboutConfig.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/About/ODSDemoAboutConfig.swift index 62cebd0a..137b32fd 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/About/ODSDemoAboutConfig.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/About/ODSDemoAboutConfig.swift @@ -25,19 +25,26 @@ import OrangeDesignSystem import SwiftUI public final class ODSDemoAboutConfig: NSObject { + + // ======================= + // MARK: Stored Properties + // ======================= public static let instance = ODSDemoAboutConfig() - let applicationInformation: ApplicationInformation + // ================= + // MARK: Initializer + // ================= + override private init() { applicationInformation = ApplicationInformation( - name: "Orange Design System", + name: "Orange Design System Demo", version: Bundle.main.marketingVersion, buildNumber: Bundle.main.buildNumber, buildType: Bundle.main.buildType, description: "In this app you'll find implemented code examples of the guidelines, components and modules, for the themes of the Orange Design System.", - imageHeader: Image("AboutImage", bundle: Bundle.main)) + imageHeader: ThemeProvider().imageFromResources("AboutImage")) } public func configure() { diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/ComponentList.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/ComponentList.swift index 54b0da72..40dd64e3 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/ComponentList.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/ComponentList.swift @@ -46,6 +46,7 @@ struct ComponentsList: View { // Remark: Components are automatically displayed sorted by their name let components: [Component] = [ BannerComponent(), + BottomSheetComponent(), ButtonComponent(), CardComponent(), ChipsComponent(), diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Banners/BannerComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Banners/BannerComponent.swift index 7900a447..5198c754 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Banners/BannerComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Banners/BannerComponent.swift @@ -62,17 +62,15 @@ struct BannerVariant: View { // ========== var body: some View { - ZStack { - + CustomizableVariant { BannerVariantContent(model: model) - - BottomSheet { - BannerVariantOptions(model: model) - } + } options: { + + BannerVariantOptions(model: model) } } } - + struct BannerVariantContent: View { // ======================= diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/BottomSheetComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/BottomSheetComponent.swift new file mode 100644 index 00000000..bd92b74f --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/BottomSheetComponent.swift @@ -0,0 +1,56 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +import OrangeDesignSystem +import SwiftUI +import BottomSheet + +struct BottomSheetComponent: Component { + let title: String + let imageName: String + let description: String + let variants: AnyView + + init() { + title = "Sheets: Bottom" + imageName = "BottomSheet" + description = "By default, a sheet is modal, presenting a focused experience that prevents users from interacting with the parent view, until they dismiss the sheet. A modal sheet is useful for requesting a specific information or enabling a simple task." + variants = AnyView(BottomSheetVariants()) + } +} + +struct BottomSheetVariants: View { + var body: some View { + VariantEntryItem(text: "Expanding", technicalElement: ".odsBottomSheetExpanding()") { + ExpandingBottomSheetVariantHome(model: BottomSheetVariantModel()) + .navigationTitle("Expanding") + + } + + VariantEntryItem(text: "Standard", technicalElement: ".odsBottomSheetStandard()") { + StandardBottomSheetVariant() + .navigationTitle("Standard") + } + } +} + diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariant.swift new file mode 100644 index 00000000..8a03e61c --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariant.swift @@ -0,0 +1,206 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +import SwiftUI +import OrangeDesignSystem + +struct BottomSheetVariant: View { + + // ====================== + // MARK: Store properties + // ====================== + + @ObservedObject var model: BottomSheetVariantModel + + // ========== + // MARK: Body + // ========== + + var body: some View { + PageContent(model: model) + .modifier(BottomSheetModifier(model: model)) + + } +} + +fileprivate struct BottomSheetModifier: ViewModifier { + + // ====================== + // MARK: Store properties + // ====================== + + @ObservedObject var model: BottomSheetVariantModel + + // ========== + // MARK: Body + // ========== + @ViewBuilder + func body(content: Content) -> some View { + if model.showSubtitle { + content + .odsBottomSheetExpanding(title: "Recipes", + subtitle: "French products", + bottomSheetSize: $model.bottomSheetSize, + content: bottomSheetContent) + } else { + if model.showIcon { + content + .odsBottomSheetExpanding(title: "Recipes", + icon: Image("Heart_19371"), + bottomSheetSize: $model.bottomSheetSize, + content: bottomSheetContent) + } else { + content + .odsBottomSheetExpanding(title: "Recipes", + bottomSheetSize: $model.bottomSheetSize, + content: bottomSheetContent) + } + } + } + + private func bottomSheetContent() -> some View { + BottonSheetContent(model: model) + .background(Color(UIColor.systemBackground)) + + } +} + +fileprivate struct BottonSheetContent: View { + + // ====================== + // MARK: Store properties + // ====================== + + @ObservedObject var model: BottomSheetVariantModel + + // ========== + // MARK: Body + // ========== + + var body: some View { + if model.contentType == .tutorial { + tutorialPage() + } else { + examplePage() + } + } + + // ============= + // MARK: Helpers + // ============= + @ViewBuilder + private func tutorialPage() -> some View { + TutorialText(message: model.tutorialTextOnBottomSheetContent) + } + + private func examplePage() -> some View { +// List { + ForEach(RecipeBook.shared.recipes, id: \.title) { recipe in + let listItemModel = + ODSListStandardItemModel(title: recipe.title, leadingIcon: .icon(Image(recipe.iconName))) + + ODSListStandardItem(model: listItemModel) + .padding(.horizontal, ODSSpacing.s) + .listRowSeparator(Visibility.visible) + .listRowInsets(EdgeInsets()) + .onTapGesture { + model.selectedRecipe = recipe + } + } +// } +// .listStyle(.plain) + } +} + + +fileprivate struct PageContent: View { + + // ====================== + // MARK: Store properties + // ====================== + + @ObservedObject var model: BottomSheetVariantModel + + // ========== + // MARK: Body + // ========== + + var body: some View { + ScrollView { + if model.contentType == .example { + exemplePage() + } else { + tutorialPage() + } + } + } + + // ==================== + // MARK: Computed value + // ==================== + + @ViewBuilder + private func exemplePage() -> some View { + if let recipe = model.selectedRecipe { + let cardModel = + ODSCardVerticalImageFirstModel(title: recipe.title, + subtitle: recipe.subtitle, + imageSource: .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)), + supportingText: recipe.description) + ODSCardVerticalImageFirst(model: cardModel) { + ODSButton(text: "Start preparing", emphasis: .highest, action: {}) + } + .padding(.horizontal, ODSSpacing.s) + } else { + EmptyView() + } + } + + @ViewBuilder + private func tutorialPage() -> some View { + TutorialText(message: model.tutorialTextOnPageContent) + } +} + +struct TutorialText: View { + + // ====================== + // MARK: Store properties + // ====================== + + let message: String? + + // ========== + // MARK: Body + // ========== + + var body: some View { + if let message = message { + Text(message) + .fixedSize(horizontal: false, vertical: true) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.all, ODSSpacing.m) + } + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift new file mode 100644 index 00000000..66c234d7 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift @@ -0,0 +1,245 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +import SwiftUI +import OrangeDesignSystem + +class BottomSheetVariantModel: ObservableObject { + + // ====================== + // MARK: Store properties + // ====================== + + @Published var bottomSheetSize: ODSBottomSheetSize + @Published var contentType: ContentType + @Published var showSubtitle: Bool { + didSet { if showSubtitle { showIcon = false } } + } + @Published var showIcon: Bool { + didSet { if showIcon { showSubtitle = false } } + } + + @Published var selectedRecipe: Recipe? + + + // ================= + // MARK: Initializer + // ================= + + init() { + self.bottomSheetSize = .medium + self.showSubtitle = false + self.showIcon = false + self.contentType = .tutorial + self.selectedRecipe = RecipeBook.shared.recipes[0] + } + + // ============== + // MARK: Tutorial + // ============== + + var tutorialTextOnPageContent: String? { + switch bottomSheetSize { + case .hidden, .large: + return nil + case .small: + return + """ + To open the bottom sheet :\n + Drag the component up + """ + case .medium: + return + """ + To open or close the bottom sheet :\n + Drag the handle up or down\n + Scroll the content\n + Tap the dimming area + """ + } + } + + var tutorialTextOnBottomSheetContent: String? { + switch bottomSheetSize { + case .hidden, .small, .medium: + return nil + case .large: + return + """ + To close the bottom sheet :\n + Drag the handle down\n + Scroll the content\n + Tap the dimming area + """ + } + } +} + +struct ExpandingBottomSheetVariantHome: View { + + // ====================== + // MARK: Store properties + // ====================== + + @ObservedObject private var model: BottomSheetVariantModel + @State private var showBottomSheet = false + + // ================= + // MARK: Initializer + // ================= + + init(model: BottomSheetVariantModel) { + self.model = model + } + + // ========== + // MARK: Body + // ========== + + var body: some View { + ScrollView { + VStack(spacing: ODSSpacing.m) { + Text("Customize the bottom sheet before opening sheet to see it.") + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, ODSSpacing.m) + + ExpandingBottomSheetVariantOptions(model: model) + + ODSButton(text: "See the component", emphasis: .highest, variableWidth: false) { + showBottomSheet = true + } + .multilineTextAlignment(.center) + .padding(.horizontal, ODSSpacing.m) + .padding(.top, ODSSpacing.m) + + Spacer() + + // First solution: Show Variant in navigation +// NavigationLink( +// destination: +// BottomSheetVariant(model: model) +// .navigationBarTitle("Sheet: Bottom tutorial", displayMode: .inline), +// isActive: $showBottomSheet, +// label: { EmptyView() } +// ) + } + // Second solution: Show Variant in full screen + .fullScreenCover(isPresented: $showBottomSheet) { + NavigationView { + BottomSheetVariant(model: model) + .navigationBarTitle("Sheet: Bottom \(model.contentType.rawValue)", displayMode: .inline) + .navigationbarMenuForThemeSelection() + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button { + showBottomSheet = false + } label: { + Text("Close") + } + } + } + } + } + .padding(.vertical, ODSSpacing.m) + } + } +} + + +struct ExpandingBottomSheetVariantOptions: View { + + // ====================== + // MARK: Store properties + // ====================== + + @ObservedObject var model: BottomSheetVariantModel + + // ========== + // MARK: Body + // ========== + + var body: some View { + VStack(spacing: ODSSpacing.m) { + Group { + ODSChipPicker(title: "Detent", + selection: $model.bottomSheetSize, + chips: ODSBottomSheetSize.chips) + + ODSChipPicker(title: "Content", + selection: $model.contentType, + chips: ContentType.chips) + + Toggle("Subtitle", isOn: $model.showSubtitle) + .padding(.horizontal, ODSSpacing.m) + .disabled(model.showIcon) + + Toggle("Icon", isOn: $model.showIcon) + .padding(.horizontal, ODSSpacing.m) + .disabled(model.showSubtitle) + } + } + .odsFont(.bodyRegular) + } +} + + +// MARK: - Internal type and extension + +enum ContentType: String, CaseIterable { + case tutorial + case example + + var chip: ODSChip { + ODSChip(self, text: self.rawValue.capitalized) + } + + static var chips: [ODSChip] { + Self.allCases.map { $0.chip } + } +} + +extension ODSBottomSheetSize { + var description: String { + switch self { + case .small: + return "Small" + case .medium: + return "Medium" + case .large: + return "Large" + case .hidden: + return "Hidden" + } + } + + var chip: ODSChip { + ODSChip(self, text: self.description) + } + + static var chips: [ODSChip] { + Self.allCases + .filter { $0 != .hidden } + .map { $0.chip } + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Standard/BottomSheetStandardVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Standard/BottomSheetStandardVariant.swift new file mode 100644 index 00000000..a583103c --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/Standard/BottomSheetStandardVariant.swift @@ -0,0 +1,107 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +import OrangeDesignSystem +import SwiftUI + +struct StandardBottomSheetVariant: View { + + // ======================= + // MARK: Stored properties + // ======================= + + @State var selectedRecipe: Recipe? = RecipesListSelection.receipes.first + @State var isOpen: Bool = false + + // ========== + // MARK: Body + // ========== + + var body: some View { + pageContent + .odsBottomSheetStandard(isOpen: $isOpen, title: "Recipes", icon: Image(systemName: "chevron.down")) { + RecipesListSelection(selectedRecipe: $selectedRecipe) + } + } + + // ============ + // MARK: Helper + // ============ + + @ViewBuilder + private var pageContent: some View { + if let recipe = selectedRecipe { + let cardModel = ODSCardVerticalImageFirstModel(title: recipe.title, + subtitle: recipe.subtitle, + imageSource: .asyncImage(recipe.url, Image("ods_empty", bundle: Bundle.ods)), + supportingText: recipe.description) + + ScrollView { + ODSCardVerticalImageFirst(model: cardModel).padding(.horizontal, ODSSpacing.s) + } + } else { + EmptyView() + } + } +} + +struct RecipesListSelection: View { + + // ======================= + // MARK: Stored properties + // ======================= + + private let selectedRecipe: Binding + private let listItemModels: [ODSListStandardItemModel] + fileprivate static let receipes: [Recipe] = Array(RecipeBook.shared.recipes.prefix(4)) + + // ================= + // MARK: Initializer + // ================= + + init(selectedRecipe: Binding) { + self.selectedRecipe = selectedRecipe + self.selectedRecipe.wrappedValue = Self.receipes.first + self.listItemModels = Self.receipes.map { recipe in + ODSListStandardItemModel(title: recipe.title, leadingIcon: .icon(Image(recipe.iconName))) + } + } + + // ========== + // MARK: Body + // ========== + + var body: some View { + VStack(spacing: 0) { + ForEach(self.listItemModels, id: \.title) { listItemModel in + ODSListStandardItem(model: listItemModel) + .padding(.horizontal, ODSSpacing.s) + .listRowSeparator(Visibility.visible) + .listRowInsets(EdgeInsets()) + .onTapGesture { + selectedRecipe.wrappedValue = Self.receipes.first { $0.title == listItemModel.title } + } + } + } + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Buttons/ButtonsComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Buttons/ButtonsComponent.swift index 594f0929..263102b1 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Buttons/ButtonsComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Buttons/ButtonsComponent.swift @@ -87,7 +87,7 @@ struct CommonButtonVariant: View where Variant: View { // ========== var body: some View { - ZStack { + CustomizableVariant { ScrollView { VStack(spacing: ODSSpacing.m) { contentView(model) @@ -96,11 +96,10 @@ struct CommonButtonVariant: View where Variant: View { .padding(.horizontal, ODSSpacing.m) } .padding(.bottom, 55) - - BottomSheet(showContent: false) { - EmphasisAndFunctionalVariantOptions(model: model) - } - } - .background(Color("componentBackground2")) + .background(Color("componentBackground2")) + } options: { + EmphasisAndFunctionalVariantOptions(model: model) + } } } + diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Buttons/IconVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Buttons/IconVariant.swift index f80528c6..b3de8c5f 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Buttons/IconVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Buttons/IconVariant.swift @@ -39,40 +39,42 @@ struct IconVariant: View { // ========== var body: some View { - ZStack { - ScrollView { - VStack(spacing: ODSSpacing.m) { - Text("Plain buttons are the most ubiquitous compoent found troughout applications. Consisting a icon, they are the most simple button style.") - .odsFont(.bodyRegular) - .frame(maxWidth: .infinity, alignment: .leading) - - VariantsTitle().frame(maxWidth: .infinity, alignment: .leading) + CustomizableVariant { + variant + } options: { + IconVariantOptions(model: model) + } + } + + var variant: some View { + ScrollView { + VStack(spacing: ODSSpacing.m) { + Text("Plain buttons are the most ubiquitous compoent found troughout applications. Consisting a icon, they are the most simple button style.") + .odsFont(.bodyRegular) + .frame(maxWidth: .infinity, alignment: .leading) + + VariantsTitle().frame(maxWidth: .infinity, alignment: .leading) + + VStack(alignment: .center, spacing: ODSSpacing.l) { + VStack(alignment: .center, spacing: ODSSpacing.s) { + Text("Icon (add)").odsFont(.headline).frame(maxWidth: .infinity, alignment: .leading) + + ODSIconButton(image: Image("Add")) {} + .disabled(model.showDisabled) + } - VStack(alignment: .center, spacing: ODSSpacing.l) { - VStack(alignment: .center, spacing: ODSSpacing.s) { - Text("Icon (add)").odsFont(.headline).frame(maxWidth: .infinity, alignment: .leading) - - ODSIconButton(image: Image("Add")) {} - .disabled(model.showDisabled) - } - - VStack(alignment: .center, spacing: ODSSpacing.s) { - Text("Icon (info)").odsFont(.headline).frame(maxWidth: .infinity, alignment: .leading) - - ODSIconButton(image: Image(systemName: "info.circle")) {} - .disabled(model.showDisabled) - } + VStack(alignment: .center, spacing: ODSSpacing.s) { + Text("Icon (info)").odsFont(.headline).frame(maxWidth: .infinity, alignment: .leading) + + ODSIconButton(image: Image(systemName: "info.circle")) {} + .disabled(model.showDisabled) } } - .padding(.top, ODSSpacing.m) - .padding(.horizontal, ODSSpacing.m) - } - .padding(.bottom, 55) - - BottomSheet(showContent: false) { - IconVariantOptions(model: model) } + .padding(.top, ODSSpacing.m) + .padding(.horizontal, ODSSpacing.m) } + .padding(.bottom, 55) .background(Color("componentBackground2")) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardHorizontalVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardHorizontalVariant.swift index 5cd723d3..a1509515 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardHorizontalVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardHorizontalVariant.swift @@ -122,7 +122,7 @@ struct CardHorizontalVariant: View { // ========== var body: some View { - ZStack { + CustomizableVariant { ScrollView { ODSCardHorizontal(model: model.cardModel) { if let text = model.button1Text { @@ -146,10 +146,8 @@ struct CardHorizontalVariant: View { .alert(model.alertText, isPresented: $model.showAlert) { Button("close", role: .cancel) {} } - - BottomSheet(showContent: false) { - CardHorizontalVariantOptions(model: model) - } + } options: { + CardHorizontalVariantOptions(model: model) } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardSmallVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardSmallVariant.swift index 3996a22c..4bb18d3d 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardSmallVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardSmallVariant.swift @@ -64,7 +64,7 @@ struct CardSmallVariant: View { // ========== var body: some View { - ZStack { + CustomizableVariant { ScrollView { LazyVGrid(columns: columns, spacing: ODSSpacing.none) { ForEach(model.cardSmallModels) { model in @@ -77,13 +77,11 @@ struct CardSmallVariant: View { } .padding(.horizontal, ODSSpacing.m) .padding(.top, ODSSpacing.m) - .alert("Card container clicked", isPresented: $showAlert) { - Button("close", role: .cancel) {} - } - - BottomSheet { - CardSmallVariantOptions(model: model) - } + } options: { + CardSmallVariantOptions(model: model) + } + .alert("Card container clicked", isPresented: $showAlert) { + Button("close", role: .cancel) {} } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardVerticalHeaderFirstVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardVerticalHeaderFirstVariant.swift index 6ca57d1e..b6817d76 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardVerticalHeaderFirstVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardVerticalHeaderFirstVariant.swift @@ -95,7 +95,7 @@ struct CardVerticalHeaderFirstVariant: View { // ========== var body: some View { - ZStack { + CustomizableVariant { ScrollView { ODSCardVerticalHeaderFirst(model: model.cardModel) { if let text = model.button1Text { @@ -119,10 +119,8 @@ struct CardVerticalHeaderFirstVariant: View { .alert(model.alertText, isPresented: $model.showAlert) { Button("close", role: .cancel) {} } - - BottomSheet(showContent: false) { - CardVerticalHeaderFirstVariantOptions(model: model) - } + } options: { + CardVerticalHeaderFirstVariantOptions(model: model) } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardVerticalImageFirstVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardVerticalImageFirstVariant.swift index c5a83498..8f10d2ff 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardVerticalImageFirstVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Cards/CardVerticalImageFirstVariant.swift @@ -90,7 +90,7 @@ struct CardVerticalImageFirstVariant: View { // ========== var body: some View { - ZStack { + CustomizableVariant { // Card demonstrator ScrollView { ODSCardVerticalImageFirst(model: model.cardModel) { @@ -115,10 +115,8 @@ struct CardVerticalImageFirstVariant: View { .alert(model.alertText, isPresented: $model.showAlert) { Button("close", role: .cancel) {} } - - BottomSheet(showContent: false) { - CardVerticalImageFirstVariantOptions(model: model) - } + } options: { + CardVerticalImageFirstVariantOptions(model: model) } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Lists/SelectionVariant/SelectionList.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Lists/SelectionVariant/SelectionList.swift index df726e1f..bc9b37c2 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Lists/SelectionVariant/SelectionList.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Lists/SelectionVariant/SelectionList.swift @@ -38,13 +38,10 @@ struct SelectionListVariant: View { // ========== var body: some View { - ZStack { - + CustomizableVariant { SelectionListVariantInner(model: model) - - BottomSheet { + } options: { SelectionListVariantOptions(model: model) - } } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Lists/StandardVariant/StandardList.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Lists/StandardVariant/StandardList.swift index 25b1896b..a61ca8c7 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Lists/StandardVariant/StandardList.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Lists/StandardVariant/StandardList.swift @@ -37,13 +37,10 @@ struct StandardListVariant: View { // ========== var body: some View { - ZStack { - + CustomizableVariant { StandardListVariantInner(model: model) - - BottomSheet { - StandardListVariantOptions(model: model) - } + } options: { + StandardListVariantOptions(model: model) } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/NavigationBar/NavigationBarComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/NavigationBar/NavigationBarComponent.swift index cf58fa37..0cd6ec72 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/NavigationBar/NavigationBarComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/NavigationBar/NavigationBarComponent.swift @@ -63,12 +63,10 @@ struct NavigationBarVariant: View { // ========== var body: some View { - ZStack { + CustomizableVariant { NavigationBarVariantContent(model: model) - - BottomSheet { - NavigationBarVariantOptions(model: model) - } + } options: { + NavigationBarVariantOptions(model: model) } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/ProgressIndicator/ActivityIndicatorVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/ProgressIndicator/ActivityIndicatorVariant.swift index aeed2f63..6a061dbd 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/ProgressIndicator/ActivityIndicatorVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/ProgressIndicator/ActivityIndicatorVariant.swift @@ -37,7 +37,7 @@ struct ActivityIndicatorVariant: View { // ========== var body: some View { - ZStack { + CustomizableVariant { VStack { ProgressView { if model.showLabel { @@ -47,9 +47,7 @@ struct ActivityIndicatorVariant: View { Spacer() } .padding(.all, ODSSpacing.m) - } - - BottomSheet { + } options: { ActivityIndicatorVariantOptions(model: model) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/ProgressIndicator/ProgressBarVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/ProgressIndicator/ProgressBarVariant.swift index f2b75974..f4e643c1 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/ProgressIndicator/ProgressBarVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/ProgressIndicator/ProgressBarVariant.swift @@ -40,40 +40,46 @@ struct ProgressBarVariant: View { // ========== var body: some View { - ZStack { - VStack { - ProgressView(value: secondsElapsed, total: maxSeconds) { - if model.showLabel { - Label(title: {Text("Downloading...")}, icon: { - if model.showIconInLabel { - Image(systemName: "tray.and.arrow.down") - } - }) - } - } currentValueLabel: { - if model.showCurrentValue { - let percent = String(format: "%.0f", secondsElapsed) - Text("\(percent) %") - .frame(maxWidth: .infinity, alignment: .trailing) - } + CustomizableVariant { + variant + } options: { + ProgressBarVariantOptions(model: model) + } + } + + // ==================== + // MARK: Private helper + // ==================== + + var variant: some View { + VStack { + ProgressView(value: secondsElapsed, total: maxSeconds) { + if model.showLabel { + Label(title: {Text("Downloading...")}, icon: { + if model.showIconInLabel { + Image(systemName: "tray.and.arrow.down") + } + }) } - .tint(theme.componentColors.accent) - .onReceive(timer) { _ in - if secondsElapsed < maxSeconds { - secondsElapsed += 1 - } else { - secondsElapsed = 0 - } + } currentValueLabel: { + if model.showCurrentValue { + let percent = String(format: "%.0f", secondsElapsed) + Text("\(percent) %") + .frame(maxWidth: .infinity, alignment: .trailing) } - - Spacer() } - .padding(.all, ODSSpacing.m) - - BottomSheet() { - ProgressBarVariantOptions(model: model) + .tint(theme.componentColors.accent) + .onReceive(timer) { _ in + if secondsElapsed < maxSeconds { + secondsElapsed += 1 + } else { + secondsElapsed = 0 + } } + + Spacer() } + .padding(.all, ODSSpacing.m) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Sliders/SlidersVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Sliders/SlidersVariant.swift index 203ec743..71f46b88 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Sliders/SlidersVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/Sliders/SlidersVariant.swift @@ -31,57 +31,66 @@ struct SliderVariant: View { // ====================== @ObservedObject var model: SliderVariantModel - @State private var value = 50.0 - let range = 0 ... 100.0 + @State private var value = 5.0 + private let range = 0 ... 10.0 // ========== // MARK: Body // ========== var body: some View { - ZStack { - ScrollView { - VStack { - if model.showValue { - Text(String(format: "%.0f", value)) - .odsFont(.bodyRegular) - .frame(maxWidth: .infinity, alignment: .leading) - .accessibilityHidden(true) + CustomizableVariant { + variant + } options: { + SliderVariantOptions(model: model) + } + } + + + // ===================== + // MARK: Private Helpers + // ===================== + var variant: some View { + ScrollView { + VStack { + if model.showValue { + Text(String(format: "%.2f", value)) + .odsFont(.bodyRegular) + .frame(maxWidth: .infinity, alignment: .leading) + .accessibilityHidden(true) + } + + if model.stepped { + ODSSlider(value: $value, in: range, step: 0.5) { + Text("Volume") + } minimumValueLabel: { + SliderLabel(show: model.showSideIcons, systemName: "speaker.wave.1.fill") + } maximumValueLabel: { + SliderLabel(show: model.showSideIcons, systemName: "speaker.wave.3.fill") + } onEditingChanged: { isEditing in + print("isEdition: \(isEditing)") } - - ODSSlider(value: $value, range: range, step: step) { + } else { + ODSSlider(value: $value, in: range) { + Text("Volume") + } minimumValueLabel: { SliderLabel(show: model.showSideIcons, systemName: "speaker.wave.1.fill") - } maximumLabelView: { + } maximumValueLabel: { SliderLabel(show: model.showSideIcons, systemName: "speaker.wave.3.fill") + } onEditingChanged: { isEditing in + print("isEdition: \(isEditing)") } - .accessibilityLabel(Text("Volume")) } - .padding(.horizontal, ODSSpacing.m) - .padding(.top, ODSSpacing.m) - } - - BottomSheet(showContent: false) { - SliderVariantOptions(model: model) } + .padding(.horizontal, ODSSpacing.m) + .padding(.top, ODSSpacing.m) } } - // ===================== - // MARK: Private Helpers - // ===================== - - private var step: Double { - return model.stepped ? 5.0 : 1.0 - } - - struct SliderLabel: View { - let show: Bool - let systemName: String - - var body: some View { - if show { - Image(systemName: systemName).accessibilityHidden(true) - } + @ViewBuilder + func SliderLabel(show: Bool, systemName: String) -> some View { + if show { + Image(systemName: systemName).accessibilityHidden(true) } } } @@ -123,7 +132,7 @@ struct SliderVariantOptions: View { VStack(spacing: ODSSpacing.m) { Toggle("Side icons", isOn: $model.showSideIcons) Toggle("Display value", isOn: $model.showValue) - Toggle("Stepped", isOn: $model.stepped) + Toggle("Stepped (0.5)", isOn: $model.stepped) } .odsFont(.bodyRegular) .padding(.vertical, ODSSpacing.m) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/TabBar/TabBarVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/TabBar/TabBarVariant.swift index 778356e7..1a1eb3ce 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/TabBar/TabBarVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/TabBar/TabBarVariant.swift @@ -51,7 +51,7 @@ struct TabBarVariant: View { // ========== var body: some View { - ZStack { + CustomizableVariant { GeometryReader { reader in VStack(alignment: .center, spacing: 0) { VStack { @@ -72,13 +72,11 @@ struct TabBarVariant: View { .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in spacerHeight = Self.computeSpacerHeight() } - - BottomSheet { - TabBarVariantOptions(model: model) - } + } options: { + TabBarVariantOptions(model: model) } } - + // ============= // MARK: Helpers // ============= diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/TextFields/CapitalizedTextInputsVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/TextFields/CapitalizedTextInputsVariant.swift index 6df98cde..df77bbc0 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/TextFields/CapitalizedTextInputsVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/TextFields/CapitalizedTextInputsVariant.swift @@ -53,7 +53,7 @@ private struct CapitalizedTextInputsVariant: View { // ========== var body: some View { - ZStack { + CustomizableVariant { VStack { textField .textInputAutocapitalization(model.selectedCapitalizationType.textInputAutocapitalization) @@ -68,10 +68,8 @@ private struct CapitalizedTextInputsVariant: View { Spacer() } - - BottomSheet(showContent: false) { - CapitalizedTextInputsVariantOptions(model: model) - } + } options: { + CapitalizedTextInputsVariantOptions(model: model) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Template/BottomSheet.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Template/BottomSheet.swift deleted file mode 100644 index 5ff4c94b..00000000 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Template/BottomSheet.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// MIT License -// Copyright (c) 2021 Orange -// 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: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// - -import SwiftUI -import OrangeDesignSystem - -// MARK: Button sheet with header and content -struct BottomSheet: View where ContentView: View { - @State var showContent: Bool = true - let contentView: () -> ContentView - - init(showContent: Bool = true, @ViewBuilder contentView: @escaping () -> ContentView) { - self.showContent = showContent - self.contentView = contentView - } - - var body: some View { - VStack(spacing: ODSSpacing.none) { - Spacer() - - VStack(spacing: ODSSpacing.none) { - BottomSheedHeader(showContent: $showContent) - .background(Color(.systemGray6)) - - if showContent { - contentView() - } - } - .background(Color(UIColor.systemBackground)) - } - .cornerRadius(10) - .shadow(radius: 8) - } -} - -struct BottomSheedHeader: View { - - @Binding var showContent: Bool - - var body: some View { - VStack(spacing: ODSSpacing.none) { - RoundedRectangle(cornerRadius: 4) - .frame(width: 55, height: 4, alignment: .center) - .padding(.top, ODSSpacing.s) - .padding(.bottom, ODSSpacing.xs) - - Button { - showContent.toggle() - } label: { - VStack(spacing: ODSSpacing.none) { - HStack(spacing: ODSSpacing.m) { - let imageName = showContent ? "chevron.down" : "chevron.up" - - Image(systemName: imageName) - .foregroundColor(.primary) - .accessibility(hidden: true) - - Text("Settings") - .odsFont(.headline) - .foregroundColor(.primary) - Spacer() - } - .padding(.all, ODSSpacing.s) - - Divider() - } - } - } - } -} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Template/CustomizableVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Template/CustomizableVariant.swift new file mode 100644 index 00000000..9cf88426 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Template/CustomizableVariant.swift @@ -0,0 +1,62 @@ +// +// MIT License +// Copyright (c) 2021 Orange +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +import SwiftUI +import OrangeDesignSystem + +struct CustomizableVariant: View where Variant: View, Options: View { + + // ======================= + // MARK: Stored Properties + // ======================= + + @State var isOpen = false + let variant: () -> Variant + let options: () -> Options + + // ================= + // MARK: Initializer + // ================= + + init(@ViewBuilder variant: @escaping () -> Variant, + @ViewBuilder options: @escaping () -> Options) { + self.variant = variant + self.options = options + } + + // ========== + // MARK: Body + // ========== + + var body: some View { + variant() + .task { + withAnimation(Animation.linear.delay(0.5)) { + self.isOpen = true + } + } + .odsBottomSheetStandard(isOpen: $isOpen, title: "Customize", + icon: Image(systemName: "chevron.down"), annimateIcon: true, + content: self.options) + } +} diff --git a/OrangeDesignSystemDemo/fastlane/Fastfile b/OrangeDesignSystemDemo/fastlane/Fastfile index 3387695c..b3c807be 100644 --- a/OrangeDesignSystemDemo/fastlane/Fastfile +++ b/OrangeDesignSystemDemo/fastlane/Fastfile @@ -43,7 +43,7 @@ platform :ios do # BUILD DEBUG APP # ------------------------------------------------------------ desc "BUILD DEBUG APP" - lane :build do + lane :buildDebugApp do cocoapods( clean_install: true ) @@ -71,40 +71,42 @@ platform :ios do set_info_plist_value(path: "#{Dir.pwd}/../OrangeDesignSystemDemo/Info.plist", key: "ODSBuildType", value: "This is a QUALIF version") - build_and_upload + build_and_upload(upload: true) end # ------------------------------------------------------------ # BUILD & UPLOAD TO TESTFLIGHT PROD APP # ------------------------------------------------------------ - desc "BUILD & UPLOAD TO TESTFLIGHT PROD APP" - lane :prod do + desc "BUILD & UPLOAD TO TESTFLIGHT (if set in options: upload) PROD APP" + lane :prod do |options| puts "This is a dumb 'puts' to ensure the 'Appfile' is read!" - build_and_upload + build_and_upload(options) end - # ----------------------------------------------------------------------- # PRIVATE LANE BUILD & UPLOAD (DEV / QUALIF / PROD is set by main lane) # ----------------------------------------------------------------------- - desc "PRIVATE LANE BUILD & UPLOAD (DEV / QUALIF / PROD is set by main lane)" - private_lane :build_and_upload do - TESTFLIGHT_GROUPS = ENV['TESTFLIGHT_GROUPS'] + private_lane :build_and_upload do |options| + build + + if options[:upload] + upload + else + puts ">> Upload to testflight no requested" + end + end + # ----------------------------------------------------------------------- + # PRIVATE LANE BUILD (DEV / QUALIF / PROD is set by main lane) + # ----------------------------------------------------------------------- + desc "PRIVATE LANE BUILD (DEV / QUALIF / PROD is set by main lane)" + private_lane :build do update_app_identifier( xcodeproj: "#{ODS_PROJECT}", plist_path: "#{ODS_SCHEME}/Info.plist", app_identifier: CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier) ) - api_key = app_store_connect_api_key( - key_id: APPLE_KEY_ID, - issuer_id: APPLE_ISSUER_ID, - key_content: APPLE_KEY_CONTENT, - duration: 500, - in_house: false - ) - increment cocoapods( @@ -123,8 +125,24 @@ platform :ios do export_method: 'app-store', xcargs: '-allowProvisioningUpdates' ) - - + end + + # ----------------------------------------------------------------------- + # PRIVATE LANE UPLOAD TO TESTFLIGHT (DEV / QUALIF / PROD is set by main lane) + # ----------------------------------------------------------------------- + desc "PRIVATE LANE UPLOAD TO TESTFLIGHT" + private_lane :upload do + + api_key = app_store_connect_api_key( + key_id: APPLE_KEY_ID, + issuer_id: APPLE_ISSUER_ID, + key_content: APPLE_KEY_CONTENT, + duration: 500, + in_house: false + ) + + TESTFLIGHT_GROUPS = ENV['TESTFLIGHT_GROUPS'] + version = get_app_version puts version @@ -145,6 +163,10 @@ platform :ios do ) end + # ------- + # Helpers + # ------- + # Get version set in the Xcode project def get_app_version version = get_version_number( xcodeproj: ODS_PROJECT, @@ -153,7 +175,8 @@ platform :ios do return version end - + # Read release note in section associated to the current version + # If empty, try within the Unreleased section def read_current_release_notes version = get_app_version diff --git a/OrangeDesignSystemDemo/fastlane/README.md b/OrangeDesignSystemDemo/fastlane/README.md index 59a5f9ba..3b360cc0 100644 --- a/OrangeDesignSystemDemo/fastlane/README.md +++ b/OrangeDesignSystemDemo/fastlane/README.md @@ -31,10 +31,10 @@ UPDATE BUILD NUMBER WITH TIMESTAMP READ AND SET NEXT RELEASE NOTE IN CHANLOG -### ios build +### ios buildDebugApp ```sh -[bundle exec] fastlane ios build +[bundle exec] fastlane ios buildDebugApp ``` BUILD DEBUG APP @@ -53,7 +53,7 @@ BUILD & UPLOAD TO TESTFLIGHT QUALIF APP [bundle exec] fastlane ios prod ``` -BUILD & UPLOAD TO TESTFLIGHT PROD APP +BUILD & UPLOAD TO TESTFLIGHT (if set in options: upload) PROD APP ---- diff --git a/OrangeTheme/Sources/OrangeTheme/OrangeTheme.swift b/OrangeTheme/Sources/OrangeTheme/OrangeTheme.swift index 08c5a978..910a83bb 100644 --- a/OrangeTheme/Sources/OrangeTheme/OrangeTheme.swift +++ b/OrangeTheme/Sources/OrangeTheme/OrangeTheme.swift @@ -207,6 +207,9 @@ public struct OrangeThemeFactory { theme.componentColors.functionalInfo = OrangeColors.functionalInfo.colorDecription.color theme.componentColors.functionalAlert = OrangeColors.functionalAlert.colorDecription.color + // Bottom sheet + theme.componentColors.bottomSheetHeaderBackground = OrangeColors.componentBackground.colorDecription.color + theme.font = { style in switch style { case .largeTitle: diff --git a/Package.swift b/Package.swift index 949397e8..6c64cc9e 100644 --- a/Package.swift +++ b/Package.swift @@ -24,14 +24,14 @@ let package = Package( dependencies: [ // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), - + .package(url: "https://github.com/lucaszischka/BottomSheet", .exact("3.1.0")) ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "OrangeDesignSystem", - dependencies: [], + dependencies: ["BottomSheet"], path: "OrangeDesignSystem/Sources"), .target( name: "OrangeTheme", diff --git a/THIRD-PARTY.md b/THIRD-PARTY.md index 5307cb50..1658d973 100644 --- a/THIRD-PARTY.md +++ b/THIRD-PARTY.md @@ -19,3 +19,6 @@ You may download the source code on the following website: https://github.com/re Parma is distributed under the terms and conditions ot the MIT License (http://opensource.org/licenses/MIT) You may download the source code on the following website: https://github.com/dasautoooo/Parma +### BottomSheet +BottomSheet is distributed under the terms and conditions ot the MIT License (http://opensource.org/licenses/MIT) +You may download the source code on the following website: https://github.com/lucaszischka/BottomSheet diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index f2c45188..7334e3ea 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -78,6 +78,7 @@ PLATFORMS arm64-darwin-21 x86_64-darwin-19 x86_64-darwin-20 + x86_64-darwin-21 DEPENDENCIES jekyll (~> 4.2.1) diff --git a/docs/_data/data_menus.yml b/docs/_data/data_menus.yml index 31e5fe9a..625f03d6 100644 --- a/docs/_data/data_menus.yml +++ b/docs/_data/data_menus.yml @@ -36,6 +36,8 @@ toc2: url: components/progressIndicator_docs - page: Slider url: components/slider_docs + - page: Sheets - Bottom + url: components/sheetsBottom_docs - page: Text fields url: components/textInput_docs diff --git a/docs/components/sheetsBottom.md b/docs/components/sheetsBottom.md new file mode 100644 index 00000000..2f8ddc00 --- /dev/null +++ b/docs/components/sheetsBottom.md @@ -0,0 +1,86 @@ +--- +layout: detail +title: Bottom sheets +description: Bottom Sheets are surfaces anchored to the bottom of the screen that present users supplement content. +--- + +--- + +**Page Summary** + +* [Specifications references](#specifications-references) +* [Accessibility](#accessibility) +* [Variants](#variants) + * [Standard](#standard) + * [Expanding](#expanding) + +--- + +## Specifications references + +- [Design System Manager - Bottom sheets](https://system.design.orange.com/0c1af118d/p/3347ca-sheets-bottom/b/83b619) + +## Accessibility + +Please follow [accessibility criteria for development](https://a11y-guidelines.orange.com/en/mobile/ios/) + +## Variants + +Bottom sheets are surfaces anchored to the bottom of the screen that present users supplemental content. +It is useful for requesting a specific information or enabling a simple task related to the current context +of the current view or more globaly the application context. + +### Standard + +The standard bottom sheet must be used only with a "simple, basic" content. If a more complex content (scrollable) must be added prefer the Expanding variant. + +It defines two states: +- **closed**: The content is hidden +- **opened**: The content is visible (above the main view) + +A taps on the header, opens or closes the bottom sheet. + +```swift +struct BottomSheetPresentation: View { + @State private var isOpen = false + + var body: some View { + VStack { + // Main content goes here. + Text("Bottom sheet is \(isOpen ? "Opened": "Closed")") + } + .odsBottomSheetStandard(isOpen: $isOpen, title: "Customize") { + // Bottom sheet content goes here + } + } +} +``` + +### Expanding + +The type of bottom must be used if the content is more complex and perhaps need to be scrollable. + +It defines three size: +- **small**: (closed) The content is hidden, only the header is visible +- **medium**: (parcially opened) The content is parcially visible (half screen above the main view) but not scrollable +- **large**: (opened) The content is visible and scrollable + +User can resize by tapping on dimming area (close), drag the content, or tap on the header to cycle through the available sizes. + +```swift + struct BottomSheetPresentation: View { + @State private var bottomSheetSize: ODSBottomSheetSize = .large + var body: some View { + VStack { + // Main content goes here. + Text("Bottom sheet size \(bottomSheetSize.rawValue)") + } + .odsBottomSheetExpanding(title: "Customize", bottomSheetSize: $bottomSheetSize) { + // Bottom sheet content goes here + } + } + } +``` + +**Remark**: In order to compute the resizing when user scrolls the content, the bottom sheet automatically adds the provided content is a scrollView. + diff --git a/docs/components/sheetsBottom_docs.md b/docs/components/sheetsBottom_docs.md new file mode 100644 index 00000000..42eeca14 --- /dev/null +++ b/docs/components/sheetsBottom_docs.md @@ -0,0 +1,4 @@ +--- +layout: main +content_page: sheetsBottom.md +--- diff --git a/docs/components/slider.md b/docs/components/slider.md index f8b2d8f0..a3a08968 100644 --- a/docs/components/slider.md +++ b/docs/components/slider.md @@ -30,17 +30,7 @@ Please follow [accessibility criteria for development](https://a11y-guidelines.o As the `ODSSlider` is based on the native `Slider`, Voice Over is able to vocalize However, if you want to set a description you need to add it using `.accessibilityLabel` on the `ODSSlider`. -We recommand to not set information on `minimumValueLabel` and `maximumValueLabel` view using `.accessibilityHidden(true)`. You can do it like this: - -```swift -ODSSlider(value: $value, - range: range, - step: step) { - Text("0").accessibilityHidden(true) -} maximumLabelView: { - Text("100").accessibilityHidden(true) -} -``` +We recommand to not set information on `minimumValueLabel` and `maximumValueLabel` view using `.accessibilityHidden(true)` ## Variants @@ -54,52 +44,65 @@ Unlabelled sliders allow users to make easy selections that do not require any d struct UnlabeledSlider: View { @State private var value = 50.0 - let range = 0 ... 100.0 var body: some View { - Text("Unlabeled slider") - .odsFont(.title2) - VStack(alignment: .center) { - ODSSlider(value: $value, range: range) - } - .padding(.horizontal, ODSSpacing.s) + ODSSlider(value: $value, in: 0 ... 100) } } ``` ### Labeled slider (with images) +We recommand to not set information on `minimumValueLabel` and `maximumValueLabel` view using `.accessibilityHidden(true)`. You can do it like this: + ```swift -ODSSlider(value: $value, in: 0 ... 100) { - Image(systemName: "speaker.wave.1.fill") -} maximumValueLabel: { - Image(systemName: "speaker.wave.3.fill") +struct LabeledSlider: View { + + @State private var value = 50.0 + + var body: some View { + ODSSlider(value: $value, in: 0 ... 100) { + Text("Volume") + } minimumValueLabel: { + Image(systemName: "speaker.wave.1.fill").accessibilityHidden(true) + } maximumValueLabel: { + Image(systemName: "speaker.wave.3.fill").accessibilityHidden(true) + } + } } ``` ### Labeled slider (with text) +We recommand to not set information on `minimumValueLabel` and `maximumValueLabel` view using `.accessibilityHidden(true)`. You can do it like this: + ```swift ODSSlider(value: $value, in: 0 ... 100) { - Text(" 0") + Text("Volume") +} minimumValueLabel: { + Text("0").accessibilityHidden(true) } maximumValueLabel: { - Text("100") + Text("100").accessibilityHidden(true) } ``` ### Stepped slider (with text and value display) +We recommand to not set information on `minimumValueLabel` and `maximumValueLabel` view using `.accessibilityHidden(true)`. You can do it like this: + ```swift -Text("Stepped slider").odsFont(.title2) -Text("Value : \(Int(value))").odsFont(.bodyRegular) -VStack(alignment: .center) { - ODSSlider( - value: $value, - in: 0 ... 100, - step: 10) { - Text(" 0") - } maximumValueLabel: { - Text("100") +struct SteppedSlider: View { + + @State private var value = 50.0 + + var body: some View { + ODSSlider(value: $value, in: 0 ... 100.0, step: 0.5) { + Text("Volume") + } minimumValueLabel: { + Image(systemName: "speaker.wave.1.fill").accessibilityHidden(true) + } maximumValueLabel: { + Image(systemName: "speaker.wave.3.fill").accessibilityHidden(true) + } } } ```