diff --git a/CHANGELOG.md b/CHANGELOG.md index ecb72303..92b687f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,31 +3,48 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [0.16.0]\(https://github.com/Orange-OpenSource/ods-ios/compare/0.16.0...0.15.0) - 2024-01-15 + +- [SDK/DemoApp] Options are overlapped in Setup page of the about module. This needs update in Chips Pickers (Bug [#577] (https://github.com/Orange-OpenSource/ods-ios/issues/577)) +- [SDK] Size of filter chips is not inconsistent in selected and unselected states (Bug [#594](https://github.com/Orange-OpenSource/ods-ios/issues/594)) +- [DemoApp/SDK] Thumbnail of card vertical header first not showing (Bug [#499](https://github.com/Orange-OpenSource/ods-ios/issues/499)) +- [DemoApp] Back button renamed after switching from lanscape to portrait mode (Bug [#562](https://github.com/Orange-OpenSource/ods-ios/issues/562)) +- [DemoApp] Udpate accessibility label on Functional and Emphasis buttons in demo app ([#546](https://github.com/Orange-OpenSource/ods-ios/issues/546)) +- [SDK/DemoApp] Refactor chips component API ([#262](https://github.com/Orange-OpenSource/ods-ios/issues/262)) +- [SDK] Update guidelines typography names ([#542](https://github.com/Orange-OpenSource/ods-ios/issues/542)) +- [DemoApp] Refactor variant page and entry ([#553](https://github.com/Orange-OpenSource/ods-ios/issues/553)) +- [SDK] Accessibility - Group texts in cards for VoiceOver (Bug [#547](https://github.com/Orange-OpenSource/ods-ios/issues/547)) +- [DemoApp] No title on Component - Lists - Standard and Selection screens (Bug [#545](https://github.com/Orange-OpenSource/ods-ios/issues/545)) +- [Tooling] Add script phase to check if AppNews.json files are conform to JSON format +- [DemoApp/SDK] Refactor About module (with view model, errors management and unit tests) +- [DemoApp/SDK] Change wording by replacing ... by elipsis code +- [DemoApp] Fix corrupted AppNews.json file + ## [0.15.0](https://github.com/Orange-OpenSource/ods-ios/compare/0.15.0...0.14.0) - 2023-11-14 -- [DemoApp/SDK] Fix compilation issues (auto signing for release, remove ios 17 support) [#529](https://github.com/Orange-OpenSource/ods-ios/issues/529)) -- [DemoApp] Review the naming of the wording keys for components [#523](https://github.com/Orange-OpenSource/ods-ios/issues/523)) -- [DemoApp] Show List style and Header and Footer of Sections [#416](https://github.com/Orange-OpenSource/ods-ios/issues/416)) -- [Tooling] Add doctor script to check if project preconditions are filled and update README[#516](https://github.com/Orange-OpenSource/ods-ios/issues/516) -- [DemoApp/SDK/Tooling] Add new Swiftlint rules and remove some warnings for cleaner source code [#514](https://github.com/Orange-OpenSource/ods-ios/issues/514) -- [DemoApp] Remove dead code from app with Periphery [#511](https://github.com/Orange-OpenSource/ods-ios/issues/511) -- [DemoApp/SDK/Tooling] Update source files headers to be compliant with SPDX format [#497](https://github.com/Orange-OpenSource/ods-ios/issues/497) -- [Tooling] Run tests plan in CI/CD chain [#506](https://github.com/Orange-OpenSource/ods-ios/issues/506) -- [Tooling] Add Mattermost notifications for build and upload lanes [#503](https://github.com/Orange-OpenSource/ods-ios/issues/503) -- [Tooling] Downgrade version of activesupport (Cocoapods issue, CI/CD troubleshooting) [#501](https://github.com/Orange-OpenSource/ods-ios/issues/501) -- [SDK] Internationalization support [#466](https://github.com/Orange-OpenSource/ods-ios/issues/466)) -- [Tooling] Fix security issues with activesupport transitive dependency (cocoapods gem) [#495](https://github.com/Orange-OpenSource/ods-ios/issues/495) -- [DemoApp/SDK] Update ListItem api to use SwiftUI elements [#462](https://github.com/Orange-OpenSource/ods-ios/issues/462)) -- [DemoApp/SDK] Add accessibility statement in About module [#119](https://github.com/Orange-OpenSource/ods-ios/issues/119)) -- [SDK] Update CardSmall api to use SwiftUI elements [#485](https://github.com/Orange-OpenSource/ods-ios/issues/485)) +- [DemoApp/SDK] Fix compilation issues (auto signing for release, remove ios 17 support) ([#529](https://github.com/Orange-OpenSource/ods-ios/issues/529)) +- [DemoApp] Review the naming of the wording keys for components ([#523](https://github.com/Orange-OpenSource/ods-ios/issues/523)) +- [DemoApp] Show List style and Header and Footer of Sections ([#416](https://github.com/Orange-OpenSource/ods-ios/issues/416)) +- [Tooling] Add doctor script to check if project preconditions are filled and update README ([#516](https://github.com/Orange-OpenSource/ods-ios/issues/516)) +- [DemoApp/SDK/Tooling] Add new Swiftlint rules and remove some warnings for cleaner source code ([#514](https://github.com/Orange-OpenSource/ods-ios/issues/514)) +- [DemoApp] Remove dead code from app with Periphery ([#511](https://github.com/Orange-OpenSource/ods-ios/issues/511)) +- [DemoApp/SDK/Tooling] Update source files headers to be compliant with SPDX format ([#497](https://github.com/Orange-OpenSource/ods-ios/issues/497)) +- [Tooling] Run tests plan in CI/CD chain ([#506](https://github.com/Orange-OpenSource/ods-ios/issues/506)) +- [Tooling] Add Mattermost notifications for build and upload lanes ([#503](https://github.com/Orange-OpenSource/ods-ios/issues/503)) +- [Tooling] Downgrade version of activesupport (Cocoapods issue, CI/CD troubleshooting) ([#501](https://github.com/Orange-OpenSource/ods-ios/issues/501)) +- [SDK] Internationalization support ([#466](https://github.com/Orange-OpenSource/ods-ios/issues/466)) +- [Tooling] Fix security issues with activesupport transitive dependency (cocoapods gem) ([#495](https://github.com/Orange-OpenSource/ods-ios/issues/495) +- [DemoApp/SDK] Update ListItem api to use SwiftUI elements ([#462](https://github.com/Orange-OpenSource/ods-ios/issues/462)) +- [DemoApp/SDK] Add accessibility statement in About module ([#119](https://github.com/Orange-OpenSource/ods-ios/issues/119)) +- [SDK] Update CardSmall api to use SwiftUI elements ([#485](https://github.com/Orange-OpenSource/ods-ios/issues/485)) ## [0.14.0](https://github.com/Orange-OpenSource/ods-ios/compare/0.14.0...0.13.1) - 2023-10-09 -- [SDK] Update CardVerticalImageFirst api to use SwiftUI elements [#481](https://github.com/Orange-OpenSource/ods-ios/issues/481)) -- [SDK] Update CardVerticalHeaderFirst api to use SwiftUI elements [#479](https://github.com/Orange-OpenSource/ods-ios/issues/479)) -- [SDK] Update CardHorizontal api to use SwiftUI elements [#477](https://github.com/Orange-OpenSource/ods-ios/issues/477)) -- [SDK] Update Button api to use SwiftUI elements and use a buttonStyle [#471](https://github.com/Orange-OpenSource/ods-ios/issues/471)) -- [SDK] Update banner api to use SwiftUI elements [#473](https://github.com/Orange-OpenSource/ods-ios/issues/473)) +- [SDK] Update CardVerticalImageFirst api to use SwiftUI elements ([#481](https://github.com/Orange-OpenSource/ods-ios/issues/481)) +- [SDK] Update CardVerticalHeaderFirst api to use SwiftUI elements ([#479](https://github.com/Orange-OpenSource/ods-ios/issues/479)) +- [SDK] Update CardHorizontal api to use SwiftUI elements ([#477](https://github.com/Orange-OpenSource/ods-ios/issues/477)) +- [SDK] Update Button api to use SwiftUI elements and use a buttonStyle ([#471](https://github.com/Orange-OpenSource/ods-ios/issues/471)) +- [SDK] Update banner api to use SwiftUI elements ([#473](https://github.com/Orange-OpenSource/ods-ios/issues/473)) - [SDK] Accessibility Voice over - Application name is a header in the about screen (Bug [#468](https://github.com/Orange-OpenSource/ods-ios/issues/468)) - [DemoApp/SDK] Update the button emphasis scale naming ([#464](https://github.com/Orange-OpenSource/ods-ios/issues/464)) - [DemoApp] Update configuration to display by default buttons in variable width and without icon ([#459](https://github.com/Orange-OpenSource/ods-ios/issues/459)) diff --git a/DEVELOP.md b/DEVELOP.md index 1dec2f15..c549d83f 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -30,3 +30,58 @@ Execute the commands below to generate and run the documentation: If you encounter errors during installation and your platform is not listed in the `PLATFORMS` section of `Gemfile.lock`, you can optionally run `bundle platform` to retrieve your platform, then `bundle lock --add-platform ` to install specific dependencies for your platform. Finally, open your browser and go to http://127.0.0.1:4000/ods-ios/ + +## Build phases + +The project contains several custom build phases so as to automatize several steps: + +1. _SwiftFormat (headers of sources)_ format the source code files headers with a template +2. _SwiftLint_ will run the linter on the sources +3. _Check JSON files format_ will run a Shell command to check wether or not the AppNews JSON files are compliant JSON files or not + +## Targets + +The Xcode project contains three targets: + +1. _OrangeDesignSystemDemo_ for the application +2. _OoangeDesignSystemDemoTests_ for the unit tests targeting the library +3. _Periphery (dead code finder)_ to look for dead code in the source code + +## JSON file validations + +Some JSON files are used in the application like the _AppNews.json_. +You can check its integrity and if all the fields are well defined by using its JSON schema: + +```json +// Can be stored in a schema.json file for further tests +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/Orange-OpenSource/ods-ios/schema/AppNews", + "title": "App News", + "description": "The catalog of app news with date, versions and news", + "items": { + "type": "object", + "properties": { + "version": { + "description": "The version of the app with these news, matching the releases", + "type": "string" + }, + "date": { + "description": "The date in yyyy-MM-dd format", + "type": "string" + }, + "news": { + "description": "The main news of the app", + "type": "string" + } + } + }, + "required": [ "version", "date", "news" ] +} +``` + +Then you can run the following command bellow to check if the _AppNews.json_ file matches the specification of the schema: + +```shell +check-jsonschema --schemafile schema.json AppNews.json +``` diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Banner/ODSBanner.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Banner/ODSBanner.swift index 6c3bc1bd..6fd0ad16 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Banner/ODSBanner.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Banner/ODSBanner.swift @@ -96,7 +96,7 @@ public struct ODSBanner: View { } text - .odsFont(.subhead) + .odsFont(.bodyS) .frame(maxWidth: .infinity, alignment: .leading) } .padding(.top, ODSSpacing.m) diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/BottomSheedHeader.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/BottomSheedHeader.swift index 69215621..cec48558 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/BottomSheedHeader.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/BottomSheedHeader.swift @@ -36,18 +36,18 @@ struct BottomSheedHeader: View { icon? .foregroundColor(.primary) .accessibility(hidden: true) - .odsFont(.headline) + .odsFont(.headlineS) .animation(.linear, value: applyRotation) .rotationEffect(.degrees(applyRotation ? 180 : 0)) VStack(alignment: .leading, spacing: ODSSpacing.none) { Text(title) - .odsFont(.headline) + .odsFont(.headlineS) .frame(maxWidth: .infinity, alignment: .leading) if let subtitle = self.subtitle { Text(subtitle) - .odsFont(.subhead) + .odsFont(.bodyS) .frame(maxWidth: .infinity, alignment: .leading) } } @@ -92,28 +92,28 @@ struct HeaderPreviewProvider_Previews: PreviewProvider { VStack(spacing: 50) { VStack { Text("Title and Subtile") - .odsFont(.title2) + .odsFont(.titleM) .frame(maxWidth: .infinity, alignment: .leading) BottomSheedHeader(title: "Title", subtitle: "Subtitle", icon: nil, applyRotation: false) } VStack { Text("Title and icon (without rotation)") - .odsFont(.title2) + .odsFont(.titleM) .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) + .odsFont(.titleM) .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) + .odsFont(.titleM) .frame(maxWidth: .infinity, alignment: .leading) AnimatinoExample() } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetExpandingModifier.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetExpandingModifier.swift index dd3726a1..6df943cb 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetExpandingModifier.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetExpandingModifier.swift @@ -49,7 +49,7 @@ struct ODSBottomSheetExpandingModifier: ViewModifier where ContentV content .bottomSheet( bottomSheetPosition: $bottomSheetPosition, - switchablePositions: ODSBottomSheetSize.allCases.map { $0.position }, + switchablePositions: ODSBottomSheetSize.positions, headerContent: { BottomSheedHeader(title: title, subtitle: subtitle, icon: icon, applyRotation: false) .onTapGesture { diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetSize+extension.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetSize+extension.swift index 13094a54..9fb198e7 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetSize+extension.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/Internal/ODSBottomSheetSize+extension.swift @@ -8,7 +8,11 @@ import BottomSheet -extension ODSBottomSheetSize { +extension ODSBottomSheetSize: CaseIterable { + public static var allCases: [ODSBottomSheetSize] = [ + .hidden, .small, .medium, .large, + ] + var position: BottomSheetPosition { switch self { case .hidden: @@ -22,7 +26,11 @@ extension ODSBottomSheetSize { } } - public init(from position: BottomSheetPosition) { + static var positions: [BottomSheetPosition] { + Self.allCases.map { $0.position } + } + + init(from position: BottomSheetPosition) { switch position { case .hidden: self = .hidden diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/ODSBottomSheetExpanding.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/ODSBottomSheetExpanding.swift index 9489e9ee..e370237f 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/ODSBottomSheetExpanding.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/BottomSheet/ODSBottomSheetExpanding.swift @@ -8,8 +8,7 @@ import SwiftUI -// swiftlint:disbale comment_spacing multiline_parameters_brackets vertical_parameter_alignment -public enum ODSBottomSheetSize: String, CaseIterable { +public enum ODSBottomSheetSize { 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 diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonContent.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonContent.swift index 14196c88..55870755 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonContent.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSButtonContent.swift @@ -38,7 +38,7 @@ struct ODSButtonContent: View { ODSIcon(image, size: 17) } - text.odsFont(.bodyBold) + text.odsFont(.bodyLBold) } } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSEmphasisButton+Style.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSEmphasisButton+Style.swift index 9df80592..5868fe21 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSEmphasisButton+Style.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSEmphasisButton+Style.swift @@ -41,7 +41,7 @@ private struct ODSEmphasisButtonStyle: ButtonStyle { backgroundColor: backgroundColor, borderColor: borderColor, fullWidth: fullWidth) - .odsFont(.bodyBold) + .odsFont(.bodyLBold) } // ====================== diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSFunctionalButton+Style.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSFunctionalButton+Style.swift index 4b04e893..09c82123 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSFunctionalButton+Style.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Buttons/Internal/ODSFunctionalButton+Style.swift @@ -41,7 +41,7 @@ private struct ODSFunctionalButtonStyleV2: ButtonStyle { backgroundColor: backgroundColor, borderColor: borderColor, fullWidth: fullWidth) - .odsFont(.bodyBold) + .odsFont(.bodyLBold) } // ====================== diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardHorizontal.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardHorizontal.swift index 15b3015d..96bf16f1 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardHorizontal.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardHorizontal.swift @@ -65,13 +65,13 @@ public struct ODSCardHorizontal: View { /// Initialization with one button. /// /// - Parameters: - /// - title: The title to be displayed in the card. - /// - imageSource: The image to be displayed in the card. - /// - imagePosition: The side where image is placed. - /// - subtitle: Optional subtitle to be displayed in the card. - /// - text: Optional text description to be displayed in the card. The text + /// - title: Title displayed into the card. + /// - imageSource: Image from source [ODSImage.Source] displayed into the card. + /// - imagePosition: Side where image is placed. + /// - subtitle: Optional subtitle displayed into the card. + /// - text: Optional text description displayed into the card. The text /// displaying is limited to two lines (truncated tail). - /// - button: The optional first (leading) button. + /// - button: Optional first (leading) button. /// - dividerEnabled: Optional divider added at the top of the buttons area. /// public init( @@ -97,14 +97,14 @@ public struct ODSCardHorizontal: View { /// Initialization with two buttons. /// /// - Parameters: - /// - title: The title to be displayed in the card. - /// - imageSource: The image to be displayed in the card. - /// - imagePosition: The side where image is placed. - /// - subtitle: Optional subtitle to be displayed in the card. - /// - text: Optional text description to be displayed in the card. The text + /// - title: Title displayed into the card. + /// - imageSource: Image from source [ODSImage.Source] displayed into the card. + /// - imagePosition: Side where image is placed. + /// - subtitle: Optional subtitle displayed into the card. + /// - text: Optional text description displayed into the card. The text /// displaying is limited to two lines (truncated tail). - /// - firstButton: The optional first (leading) button. - /// - secondButton: The optional second (trailing) button. + /// - firstButton: Optional first (leading) button. + /// - secondButton: Optional second (trailing) button. /// - dividerEnabled: Optional divider added at the top of the buttons area. /// public init( @@ -142,7 +142,7 @@ public struct ODSCardHorizontal: View { VStack(alignment: .leading, spacing: ODSSpacing.xs) { title - .odsFont(.bodyBold) + .odsFont(.bodyLBold) .frame(maxWidth: .infinity, alignment: .leading) subtitle? @@ -152,6 +152,7 @@ public struct ODSCardHorizontal: View { .lineLimit(2) .frame(maxWidth: .infinity, alignment: .leading) } + .accessibilityElement(children: .combine) .foregroundColor(.primary) .padding(.all, ODSSpacing.m) diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardSmall.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardSmall.swift index 5861ec30..5c3ff5e2 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardSmall.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardSmall.swift @@ -27,9 +27,9 @@ public struct ODSCardSmall: View { /// Initialization. /// /// - Parameters: - /// - title: The title to be displayed in the card. - /// - imageSource: The source of the image. - /// - subtitle: The optional subtitle. + /// - title: Title displayed into the card. + /// - imageSource: Image from source [ODSImage.Source] displayed into the card. + /// - subtitle: Optional subtitle displayed into the card. /// public init(title: Text, imageSource: ODSImage.Source, subtitle: Text? = nil) { self.title = title @@ -52,14 +52,15 @@ public struct ODSCardSmall: View { VStack(alignment: .leading, spacing: ODSSpacing.xs) { title .lineLimit(1) - .odsFont(.bodyBold) + .odsFont(.bodyLBold) .frame(maxWidth: .infinity, alignment: .leading) subtitle? .lineLimit(1) - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .frame(maxWidth: .infinity, alignment: .leading) } + .accessibilityElement(children: .combine) .padding(.all, ODSSpacing.m) } .foregroundColor(.primary) @@ -79,7 +80,7 @@ struct SmallCardView_Previews: PreviewProvider { VStack(alignment: .leading, spacing: ODSSpacing.none) { Text("Card in Vertical grid") - .odsFont(.title1) + .odsFont(.titleL) .frame(width: .infinity, alignment: .leading) .padding(.bottom, ODSSpacing.m) @@ -105,7 +106,7 @@ struct SmallCardView_Previews: PreviewProvider { .padding(.bottom, ODSSpacing.m) Text("Card in content view") - .odsFont(.title1) + .odsFont(.titleL) .frame(width: .infinity, alignment: .leading) .padding(.bottom, ODSSpacing.m) diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalHeaderFirst.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalHeaderFirst.swift index 09daa6a0..c0bda012 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalHeaderFirst.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalHeaderFirst.swift @@ -21,7 +21,7 @@ public struct ODSCardVerticalHeaderFirst: View { private let title: Text private let subtitle: Text? - private let thumbnail: Image? + private let thumbnailSource: ODSImage.Source? private let imageSource: ODSImage.Source private let text: Text? private let firstButton: (() -> Button)? @@ -34,22 +34,22 @@ public struct ODSCardVerticalHeaderFirst: View { /// Initialization without button. /// /// - Parameters: - /// - title: The title to be displayed in the card. - /// - imageSource: The image to be displayed in the card. - /// - subtitle: Optional subtitle to be displayed in the card. - /// - thumbnail: The optional thumbnail: avatar, logo or icon. - /// - text: Optional text description to be displayed in the card. + /// - title: Title displayed into the header of the card. + /// - imageSource: Image from source [ODSImage.Source] displayed into the card + /// - subtitle: Optional subtitle displayed into the header of the card. + /// - thumbnailSource: Optional thumbnail (avatar, logo or icon) from source [ODSImage.Source] next to the title. + /// - text: Optional text description displayed into the card. /// public init( title: Text, imageSource: ODSImage.Source, subtitle: Text?, - thumbnail: Image?, + thumbnailSource: ODSImage.Source?, text: Text? = nil) { self.title = title self.subtitle = subtitle - self.thumbnail = thumbnail + self.thumbnailSource = thumbnailSource self.imageSource = imageSource self.text = text firstButton = nil @@ -59,24 +59,24 @@ public struct ODSCardVerticalHeaderFirst: View { /// Initialization with one button. /// /// - Parameters: - /// - title: The title to be displayed in the card. - /// - imageSource: The image to be displayed in the card. - /// - subtitle: Optional subtitle to be displayed in the card. - /// - thumbnail: The optional thumbnail: avatar, logo or icon. - /// - text: Optional text description to be displayed in the card. + /// - title: Title displayed into the header of the card. + /// - imageSource: Image from source [ODSImage.Source] displayed into the card. + /// - subtitle: Optional subtitle displayed into the header of the card. + /// - thumbnailSource: Optional thumbnail (avatar, logo or icon) from source [ODSImage.Source] next to the title. + /// - text: Optional text description displayed into the card. /// - button: The optional button. /// public init( title: Text, imageSource: ODSImage.Source, subtitle: Text?, - thumbnail: Image?, + thumbnailSource: ODSImage.Source?, text: Text? = nil, @ViewBuilder button: @escaping () -> Button) { self.title = title self.subtitle = subtitle - self.thumbnail = thumbnail + self.thumbnailSource = thumbnailSource self.imageSource = imageSource self.text = text firstButton = button @@ -86,11 +86,11 @@ public struct ODSCardVerticalHeaderFirst: View { /// Initialization with two buttons. /// /// - Parameters: - /// - title: The title to be displayed in the card. - /// - imageSource: The image to be displayed in the card. - /// - subtitle: Optional subtitle to be displayed in the card. - /// - thumbnail: The optional thumbnail: avatar, logo or icon. - /// - text: Optional text description to be displayed in the card. + /// - title: Title displayed into the header of the card. + /// - imageSource: Image from source [ODSImage.Source] displayed into the card. + /// - subtitle: Optional subtitle displayed into the header of the card. + /// - thumbnailSource: Optional thumbnail (avatar, logo or icon) from source [ODSImage.Source] next to the title. + /// - text: Optional text description displayed into the card. /// - firstButton: The optional first (leading) button. /// - secondButton: The optional second (trailing) button. /// @@ -98,7 +98,7 @@ public struct ODSCardVerticalHeaderFirst: View { title: Text, imageSource: ODSImage.Source, subtitle: Text? = nil, - thumbnail: Image? = nil, + thumbnailSource: ODSImage.Source? = nil, text: Text? = nil, dividerEnabled: Bool = true, @ViewBuilder firstButton: @escaping () -> Button, @@ -106,7 +106,7 @@ public struct ODSCardVerticalHeaderFirst: View { { self.title = title self.subtitle = subtitle - self.thumbnail = thumbnail + self.thumbnailSource = thumbnailSource self.imageSource = imageSource self.text = text self.firstButton = firstButton @@ -120,21 +120,22 @@ public struct ODSCardVerticalHeaderFirst: View { public var body: some View { VStack(alignment: .leading, spacing: ODSSpacing.none) { HStack(alignment: .center, spacing: ODSSpacing.s) { - thumbnail? - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 44.0, height: 44.0, alignment: .center) - .clipShape(Circle()) - .accessibilityHidden(true) + if let thumbnailSource = thumbnailSource { + ODSImage(source: thumbnailSource) + .frame(width: 44.0, height: 44.0, alignment: .center) + .clipShape(Circle()) + .accessibilityHidden(true) + } VStack(alignment: .leading, spacing: ODSSpacing.none) { title - .odsFont(.bodyBold) + .odsFont(.bodyLBold) .frame(maxWidth: .infinity, alignment: .leading) subtitle? - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) } + .accessibilityElement(children: .combine) } .multilineTextAlignment(.leading) .foregroundColor(.primary) @@ -148,7 +149,7 @@ public struct ODSCardVerticalHeaderFirst: View { VStack(alignment: .leading, spacing: ODSSpacing.none) { text? - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .multilineTextAlignment(.leading) .padding(.horizontal, ODSSpacing.m) .padding(.top, ODSSpacing.s) @@ -164,13 +165,6 @@ public struct ODSCardVerticalHeaderFirst: View { // MARK: Private Helpers // ===================== - private var image: some View { - ODSImage(source: imageSource) - .accessibilityHidden(true) - .frame(width: 128) - .clipped() - } - @ViewBuilder private func buttons() -> some View { if let firstButton = firstButton { @@ -215,7 +209,7 @@ struct ODSCardVerticalHeaderFirst_Previews: PreviewProvider { title: Text(ODSCCardPreviewData.title), imageSource: .image(ODSCCardPreviewData.image), subtitle: Text(ODSCCardPreviewData.subtitle), - thumbnail: ODSCCardPreviewData.thumbnail, + thumbnailSource: .image(ODSCCardPreviewData.thumbnail), text: Text(ODSCCardPreviewData.supportingText)) { Button("Button 1") { diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalImageFirst.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalImageFirst.swift index c92c3fa3..e4aa9bcb 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalImageFirst.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Cards/ODSCardVerticalImageFirst.swift @@ -28,10 +28,10 @@ public struct ODSCardVerticalImageFirst: View { /// Initialization without button. /// /// - Parameters: - /// - title: The title. - /// - imageSource: The source of the image. - /// - subtitle: Optional subtitle. - /// - text: Optional text description. + /// - title: Title displayed into the card. + /// - imageSource: Image from source [ODSImage.Source] displayed into the card. + /// - subtitle: Optional subtitle displayed into the card. + /// - text: Optional text description displayed into the card. /// public init( title: Text, @@ -50,10 +50,10 @@ public struct ODSCardVerticalImageFirst: View { /// Initialization with one button. /// /// - Parameters: - /// - title: The title. - /// - imageSource: The source of the image. - /// - subtitle: Optional subtitle. - /// - text: Optional text description. + /// - title: Title displayed into the card. + /// - imageSource: Image from source [ODSImage.Source] displayed into the card. + /// - subtitle: Optional subtitle displayed into the card. + /// - text: Optional text description displayed into the card. /// - button: The button with text only (lowest emphasis). /// public init( @@ -74,10 +74,10 @@ public struct ODSCardVerticalImageFirst: View { /// Initialization with two buttons. /// /// - Parameters: - /// - title: The title. - /// - imageSource: The source of the image. - /// - subtitle: Optional subtitle. - /// - text: Optional text description. + /// - title: Title displayed into the card. + /// - imageSource: Image from source [ODSImage.Source] displayed into the card. + /// - subtitle: Optional subtitle displayed into the card. + /// - text: Optional text description displayed into the card. /// - firstButton: The first (leading) button text. /// - secondButton: The second (trailing) button text. /// @@ -111,7 +111,7 @@ public struct ODSCardVerticalImageFirst: View { VStack(alignment: .leading, spacing: ODSSpacing.xs) { title - .odsFont(.bodyBold) + .odsFont(.bodyLBold) .frame(maxWidth: .infinity, alignment: .leading) subtitle? @@ -120,6 +120,7 @@ public struct ODSCardVerticalImageFirst: View { text? .frame(maxWidth: .infinity, alignment: .leading) } + .accessibilityElement(children: .combine) .foregroundColor(.primary) .padding(.horizontal, ODSSpacing.m) .padding(.top, ODSSpacing.m) diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/Internal/Chip.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/Internal/Chip.swift new file mode 100644 index 00000000..21dff919 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/Internal/Chip.swift @@ -0,0 +1,119 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import SwiftUI + +struct Chip: View where Leading: View, Text: View { + + // ======================= + // MARK: Stored properties + // ======================= + + private let isSelected: Bool + private let action: () -> Void + private let removeAction: (() -> Void)? + @ViewBuilder private let text: () -> Text + @ViewBuilder private let leading: () -> Leading + + @ScaledMetric(relativeTo: .body) private var leadingHeight = 24 + @ScaledMetric(relativeTo: .body) private var frameHeight = 32 + @ScaledMetric(relativeTo: .body) private var labelPadding = ODSSpacing.xs + + // ================= + // MARK: Intializers + // ================= + + init(isSelected: Bool, + action: @escaping () -> Void, + removeAction: (() -> Void)? = nil, + @ViewBuilder text: @escaping () -> Text, + @ViewBuilder leading: @escaping () -> Leading) + { + self.isSelected = isSelected + self.action = action + self.removeAction = removeAction + self.text = text + self.leading = leading + } + + init(isSelected: Bool, + action: @escaping () -> Void, + removeAction: (() -> Void)? = nil, + @ViewBuilder text: @escaping () -> Text) where Leading == EmptyView + { + self.isSelected = isSelected + self.action = action + self.removeAction = removeAction + self.text = text + leading = { EmptyView() } + } + + // ========== + // MARK: Body + // ========== + + var body: some View { + Button { + action() + } label: { + HStack(spacing: ODSSpacing.none) { + + leading() + .frame(width: leadingHeight, height: leadingHeight) + .clipShape(Circle()) + + text() + .odsFont(.bodyS) + .multilineTextAlignment(.center) + .frame(minHeight: leadingHeight) + + removeButton + .frame(width: leadingHeight, height: leadingHeight) + } + .padding(.all, labelPadding) + .modifier(ChipContentModifier(isSelected: isSelected)) + .frame(height: frameHeight) + } + } + + @ViewBuilder + var removeButton: some View { + if let removeAction = removeAction { + Button(action: removeAction) { + Image("ic_close", bundle: Bundle.ods) + .resizable() + .renderingMode(.template) + .aspectRatio(contentMode: .fit) + .foregroundColor(.primary) + } + } + } +} + +struct ChipContentModifier: ViewModifier { + + @Environment(\.theme) private var theme + @Environment(\.isEnabled) private var isEnabled + private let lineWidth: CGFloat = 1.0 + let isSelected: Bool + + func body(content: Content) -> some View { + if isSelected { + content + .foregroundColor(.black) + .background(theme.componentColors.accent, in: Capsule()) + .opacity(isEnabled ? 1 : 0.5) + } else { + content + .foregroundColor(.primary) + .overlay(Capsule().stroke(Color.primary, lineWidth: lineWidth)) + .padding(.all, lineWidth) + .opacity(isEnabled ? 1 : 0.5) + } + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSActionChip.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSActionChip.swift new file mode 100644 index 00000000..1ef456bc --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSActionChip.swift @@ -0,0 +1,54 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import SwiftUI + +/// ODS Chips. +/// +/// Chips are small components containing a number of elements that represent a calendar event or contact. +/// +public struct ODSActionChip: View { + + // ======================= + // MARK: Stored properties + // ======================= + + private let text: Text + private let leadingIcon: Image + private let action: () -> Void + + // ================== + // MARK: Initializers + // ================== + + /// Create the chip. + /// + /// - Parameters: + /// - text: Text to be displayed into the chip. + /// - leadingIcon: Optional leading icon to be displayed at the start of the chip, preceding the content text. + /// - action: Callback action invoked when chip is clicked. + /// + public init(text: Text, leadingIcon: Image, action: @escaping () -> Void) { + self.text = text + self.leadingIcon = leadingIcon + self.action = action + } + + // ========== + // MARK: Body + // ========== + + public var body: some View { + Chip(isSelected: false, action: action) { + text.padding(.horizontal, ODSSpacing.s) + } leading: { + ODSImage(source: .image(leadingIcon.renderingMode(.template))) + .padding(.leading, ODSSpacing.xs) + } + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSChips.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSChips.swift deleted file mode 100644 index 4e695059..00000000 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSChips.swift +++ /dev/null @@ -1,439 +0,0 @@ -// -// Software Name: Orange Design System (iOS) -// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA -// SPDX-License-Identifier: MIT -// -// This software is distributed under the MIT license. -// - -import SwiftUI - -/// A thumbnail can be added on the right side of a chip. -/// - Icon is a simple image with only one color -/// - Avatar is the a more complex image like contact photo. -/// -public enum ODSChipThumbnail { - case icon(Image) - case iconSystem(name: String) - case avatar(Image) -} - -/// ODS Chips. -/// -/// Chips are small components containing a number of elements that represent a calendar event or contact. -/// -public typealias ODSChip = ODSChipPicker.ODSChipModel where Value: Hashable - -/// Create a picker by providing the selection type with a binding to get selected element(s). -/// An additonnal title can be added above the Picker. -/// -public struct ODSChipPicker: View where Value: Hashable { - - /// The chip element description. - public struct ODSChipModel { - - let value: Value - let text: String - let thumbnail: ODSChipThumbnail? - let disabled: Bool - let removable: Bool - - /// Create a chip model that describes the chip contents. - /// - /// - Parameters: - /// - value: The value of the chip - /// - text: Text of the chip - /// - thumbnail: Optional leading thumbnail - /// - disabled: When disabled, chip will not respond to user input. - /// - removable: A cross to the chip and provides a remove action (remove chip from list). - /// - public init(_ value: Value, text: String, thumbnail: ODSChipThumbnail? = nil, disabled: Bool = false, removable: Bool = false) - { - self.value = value - self.text = text - self.thumbnail = thumbnail - self.disabled = disabled - self.removable = removable - } - } - - /// Creates a picker manages a single selection that allows no element to be selected. - /// - /// - Parameters: - /// - title: Optional title above the picker - /// - selection: A binding to a property that determines the - /// currently-selected option. - /// - chips: All chips describing elements to be displayed. - /// - public init(title: String? = nil, selection: Binding, chips: [ODSChipModel]) { - self.title = title - self.chips = chips - - singleSelectionZero = selection - singleSelectionOne = nil - multipleSelection = nil - } - - /// Creates a picker manages a single selection that avoid to get no element selected (at least one). - /// - /// - Parameters: - /// - title: Optional title above the picker - /// - selection: A binding to a property that determines the - /// currently-selected option. - /// - chips: All chips describing elements to be displayed. - /// - public init(title: String? = nil, selection: Binding, chips: [ODSChipModel]) { - self.title = title - self.chips = chips - - singleSelectionZero = nil - singleSelectionOne = selection - multipleSelection = nil - } - - /// Creates a picker manages multiple selections. - /// - /// - Parameters: - /// - title: Optional title above the picker - /// - selection: A binding to a property that determines the - /// currently-selected option. - /// - allowZeroSelection: If set to true mens that no chip can be selected, otherwise almost one chip is always selected. - /// - chips: All chips describing elements to be displayed. - /// - public init(title: String? = nil, selection: Binding<[Value]>, allowZeroSelection: Bool = false, chips: [ODSChipModel]) { - self.title = title - self.chips = chips - - singleSelectionOne = nil - singleSelectionZero = nil - multipleSelection = (selection, allowZeroSelection) - } - - typealias SingleSelectionZero = Binding - typealias SingleSelectionOne = Binding - typealias MultipleSelection = (Binding<[Value]>, Bool) - - let title: String? - let chips: [ODSChipModel] - let singleSelectionZero: SingleSelectionZero? - let singleSelectionOne: SingleSelectionOne? - let multipleSelection: MultipleSelection? - - @State var textHeight: CGFloat = 30.0 - - public var body: some View { - VStack(alignment: .leading, spacing: ODSSpacing.s) { - if let title = title { - Text(title).odsFont(.headline) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.leading, ODSSpacing.m) - } - - ScrollView(.horizontal, showsIndicators: false) { - VStack(spacing: ODSSpacing.none) { - - HStack(spacing: ODSSpacing.s) { - ForEach(chips, id: \.value) { chip in - Button { - handleSelection(for: chip) - } label: { - HStack(alignment: .center, spacing: ODSSpacing.none) { - if let thumbnail = chip.thumbnail { - ChipThumbnail(selected: isSelected(chip), - thumbnail: thumbnail, - height: textHeight) - } - - Text(chip.text) - .odsFont(.subhead) - .tint(isSelected(chip) ? .black : .primary) - .padding(.vertical, 6) - .padding(.leading, textLeadingPadding(for: chip)) - .padding(.trailing, chip.removable ? ODSSpacing.s : ODSSpacing.m) - .readSize { size in - textHeight = size.height - } - - if chip.removable { - ChipRemoveLabel(height: textHeight, selected: isSelected(chip)) - .highPriorityGesture(TapGesture().onEnded {}) - } - } - } - .background(background(for: chip)) - .clipShape(Capsule()) - .disabled(chip.disabled) - } - } - .padding(.trailing, ODSSpacing.s) - .padding(.leading, ODSSpacing.m) - } - } - } - } - - func textLeadingPadding(for chip: ODSChipModel) -> CGFloat { - switch chip.thumbnail { - case .icon: return ODSSpacing.s - case .avatar: return isSelected(chip) ? ODSSpacing.s : ODSSpacing.s - 2 - case .iconSystem: return ODSSpacing.s - case .none: return ODSSpacing.m - } - } - - @ViewBuilder func background(for chip: ODSChipModel) -> some View { - if isSelected(chip) { - Capsule().foregroundColor(Color.accentColor) - } else { - Capsule().stroke(lineWidth: 1) - } - } - - func isSelected(_ chip: ODSChipModel) -> Bool { - - if let singleSelectionZero = singleSelectionZero { - return chip.value == singleSelectionZero.wrappedValue - } - - if let singleSelectionOne = singleSelectionOne { - return chip.value == singleSelectionOne.wrappedValue - } - - if let multipleSelection = multipleSelection { - return multipleSelection.0.wrappedValue.contains(where: { chip.value == $0 }) - } - - return false - } - - func handleSelection(for chip: ODSChipModel) { - if let singleSelectionZero = singleSelectionZero { - handle(singleSelectionZero, for: chip) - } else { - if let singleSelectionOne = singleSelectionOne { - handle(singleSelectionOne, for: chip) - } else { - if let multipleSelection = multipleSelection { - handle(multipleSelection, for: chip) - } - } - } - } - - func handle(_ singleSelection: SingleSelectionZero, for chip: ODSChipModel) { - if singleSelection.wrappedValue == chip.value { - singleSelection.wrappedValue = nil - } else { - singleSelection.wrappedValue = chip.value - } - } - - func handle(_ singleSelection: SingleSelectionOne, for chip: ODSChipModel) { - if singleSelection.wrappedValue == chip.value { - } else { - singleSelection.wrappedValue = chip.value - } - } - - func handle(_ multipleSelection: MultipleSelection, for chip: ODSChipModel) { - if let index = multipleSelection.0.wrappedValue.firstIndex(where: { $0 == chip.value }) { - if multipleSelection.0.count != 1 || multipleSelection.1 { - multipleSelection.0.wrappedValue.remove(at: index) - } - } else { - multipleSelection.0.wrappedValue.append(chip.value) - } - } -} - -struct ChipThumbnail: View { - let selected: Bool - let thumbnail: ODSChipThumbnail - let height: CGFloat - - var body: some View { - switch thumbnail { - case let .avatar(image): - if selected { - ChipSelectedAvatar(height: height) - } else { - image - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: height - 6, height: height - 6, alignment: .center) - .clipShape(Circle()) - .padding(.leading, ODSSpacing.xs) - } - case let .icon(image): - image - .resizable() - .renderingMode(.template) - .aspectRatio(contentMode: .fill) - .tint(selected ? .black : .primary) - .frame(width: height - 9, height: height - 9, alignment: .center) - .padding(.leading, ODSSpacing.s) - - case let .iconSystem(name): - Image(systemName: name) - .tint(selected ? .black : .primary) - .padding(.leading, ODSSpacing.s) - } - } -} - -struct ChipSelectedAvatar: View { - let height: CGFloat - - var body: some View { - Image("iconsFunctionalUiEMIcFormTick", bundle: Bundle.ods) - .resizable() - .renderingMode(.template) - .aspectRatio(contentMode: .fit) - .tint(Color.accentColor) - .background(Color.black) - .frame(width: height - 6, height: height - 6, alignment: .center) - .clipShape(Circle()) - .padding(.leading, ODSSpacing.xs) - } -} - -struct ChipRemoveLabel: View { - - let height: CGFloat - let selected: Bool - - var body: some View { - Image("Close", bundle: Bundle.ods) - .resizable() - .renderingMode(.template) - .aspectRatio(contentMode: .fit) - .tint(selected ? .black : .primary) - .frame(width: height - 11, height: height - 11, alignment: .center) - .padding(.trailing, ODSSpacing.s) - } -} - -#if DEBUG -struct ODSChips_Previews: PreviewProvider { - - enum ChipsTest: Int, CaseIterable { - case title1 = 1 - case title2 - case removable1 - case removabele2 - case disabled - - var odsChip: ODSChip { - switch self { - case .title1: - return ODSChip(self, text: "Title 1") - case .title2: - return ODSChip(self, text: "Title 2", thumbnail: .iconSystem(name: "heart")) - case .removable1: - return ODSChip(self, text: "Removable 1", removable: true) - case .removabele2: - return ODSChip(self, text: "Removable 2", thumbnail: .iconSystem(name: "heart"), removable: true) - case .disabled: - return ODSChip(self, text: "Disabled", disabled: true) - } - } - } - - struct ODSChipPickerTest: View { - - @State var selectedOneChip: ChipsTest - @State var selectedZeroChip: ChipsTest? - @State var selectedMultipleChipsZero: [ChipsTest] = [] - @State var selectedMultipleChipsOne: [ChipsTest] = [] - - let chips: [ODSChip] - - init(chipsTest: [ChipsTest], defaultSelectedChip: ChipsTest) { - chips = chipsTest.map { $0.odsChip } - selectedOneChip = defaultSelectedChip - selectedMultipleChipsOne = [defaultSelectedChip] - } - - var selectedMultipleChipsZeroText: String { - let text = selectedMultipleChipsZero.reduce(into: "") { result, chip in - result = "\(result)\(chip.odsChip.text)," - } - - if text.isEmpty { - return "No Chip selected" - } - - return text - } - - var selectedMultipleChipsOneText: String { - return selectedMultipleChipsOne.reduce(into: "") { result, chip in - result = "\(result)\(chip.odsChip.text)," - } - } - - var body: some View { - VStack { - VStack(spacing: ODSSpacing.s) { - VStack(spacing: ODSSpacing.s) { - ODSChipPicker(title: "Single selection with zero allowed", - selection: $selectedZeroChip, - chips: chips) - Text("Selected Chip : \(selectedZeroChip?.odsChip.text ?? "None")") - .padding(.horizontal, ODSSpacing.m) - .frame(maxWidth: .infinity, alignment: .leading) - } - .padding(.horizontal, ODSSpacing.none) - .padding(.bottom, ODSSpacing.l) - .background(.green) - - VStack(spacing: ODSSpacing.s) { - ODSChipPicker(title: "Single selection with at least one", - selection: $selectedOneChip, - chips: chips) - Text("Selected Chip : \(selectedOneChip.odsChip.text)") - .padding(.horizontal, ODSSpacing.m) - .frame(maxWidth: .infinity, alignment: .leading) - } - .padding(.horizontal, ODSSpacing.none) - .padding(.bottom, ODSSpacing.l) - .background(.green) - - VStack(spacing: ODSSpacing.s) { - ODSChipPicker(title: "Multiple selection with zero allowed", - selection: $selectedMultipleChipsZero, - allowZeroSelection: true, - chips: chips) - - Text("Selected Chip : \(self.selectedMultipleChipsZeroText)") - .padding(.horizontal, ODSSpacing.m) - .frame(maxWidth: .infinity, alignment: .leading) - } - .padding(.horizontal, ODSSpacing.none) - .padding(.bottom, ODSSpacing.l) - .background(.green) - - VStack(spacing: ODSSpacing.s) { - ODSChipPicker(title: "Multiple selection with at least one", - selection: $selectedMultipleChipsOne, - allowZeroSelection: false, - chips: chips) - - Text("Selected Chip : \(self.selectedMultipleChipsOneText)") - .padding(.horizontal, ODSSpacing.m) - .frame(maxWidth: .infinity, alignment: .leading) - } - .padding(.horizontal, ODSSpacing.none) - .padding(.bottom, ODSSpacing.l) - .background(.green) - } - } - } - } - - static var previews: some View { - ODSChipPickerTest(chipsTest: ChipsTest.allCases, - defaultSelectedChip: ChipsTest.title1) - } -} -#endif diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSChoiceChip.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSChoiceChip.swift new file mode 100644 index 00000000..96a9b2df --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSChoiceChip.swift @@ -0,0 +1,78 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import SwiftUI + +/// ODS Chips. +/// +/// Chips are small components containing a number of elements that represent a calendar event or contact. +/// +/// ODSChoiceChip represents a single choice from a set. Choice chips contain text describing an associated value. +/// +/// - remark: The associated view __ODSChoiceChipView__ is used by +/// __ODsChocieChipPicker__ to wrap chips in stacked or carousel and +/// propose a picker to catch the choice from a set of options. +/// +public final class ODSChoiceChip where Value: Hashable { + + // ======================= + // MARK: Stored properties + // ======================= + + let text: Text + let value: Value + + // ================= + // MARK: Intializers + // ================= + + /// Initialize the chip. + /// + /// - Parameters: + /// - text: Text to be displayed into the chip. + /// - value: The value associated to the chip + /// + public init(text: Text, value: Value) { + self.text = text + self.value = value + } +} + +/// +/// The view representing the choice chip. +/// +public struct ODSChoiceChipView: View where Value: Hashable { + + // ======================= + // MARK: Stored properties + // ======================= + + private let chip: ODSChoiceChip + private let selected: Bool + private let action: () -> Void + + // ================= + // MARK: Intializers + // ================= + + public init(chip: ODSChoiceChip, selected: Bool, action: @escaping () -> Void) { + self.chip = chip + self.selected = selected + self.action = action + } + + // ========== + // MARK: Body + // ========== + + public var body: some View { + Chip(isSelected: selected, action: action) { + chip.text.padding(.horizontal, ODSSpacing.s) + } + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSFilterChip.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSFilterChip.swift new file mode 100644 index 00000000..b27632e0 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSFilterChip.swift @@ -0,0 +1,125 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import SwiftUI + +/// ODS Chips. +/// +/// Chips are small components containing a number of elements that represent a calendar event or contact. +/// This chip offers a layout used for filtering. +/// +/// - remark: The associated view __ODSFilterChipView__ is used by +/// __ODSFilterChipPicker__ to wrap chips in stacked or carousel and +/// propose a picker to apply a filter on elements easily. +/// +public final class ODSFilterChip where Value: Hashable { + + // ======================= + // MARK: Stored properties + // ======================= + + let text: Text + let leading: ODSImage.Source? + let value: Value + var disabled: Bool + + // ================= + // MARK: Initializer + // ================= + + /// Initialize the chip. + /// + /// - Parameters: + /// - text: Text to be displayed into the chip. + /// - leading: Optional leading avatar to be displayed in a circle shape at the start of the chip, preceding the content text. + /// - value: The value associated to the chip + /// - disabled: When disabled, chip will not respond to user input. + /// + public init(text: Text, leading: ODSImage.Source? = nil, value: Value, disabled: Bool = false) { + self.text = text + self.leading = leading + self.value = value + self.disabled = disabled + } +} + +/// +/// The view representing the filter chip. +/// +public struct ODSFilterChipView: View where Value: Hashable { + + // ======================= + // MARK: Stored properties + // ======================= + + @Environment(\.theme) private var theme + private let model: ODSFilterChip + private let selected: Bool + private let action: () -> Void + + // ================== + // MARK: Initializers + // ================== + + /// Initialize the view. + /// + /// - Parameters: + /// - model: Model containing chip elements for layout. + /// - selected: Controls the selected state of the chip. When `true`, the chip is highlighted. + /// - action: The action when chip is clicked. + /// + public init(chip: ODSFilterChip, selected: Bool, action: @escaping () -> Void) { + model = chip + self.selected = selected + self.action = action + } + + // ========== + // MARK: Body + // ========== + + public var body: some View { + Chip(isSelected: selected, action: action) { + model.text + .padding(.trailing, ODSSpacing.s) + .padding(.leading, leadingTextPadding) + } leading: { + if selected { + selectedLeading + } else { + avatar + } + } + .disabled(model.disabled) + } + + // ============= + // MARK: Helpers + // ============= + + @ViewBuilder + private var avatar: some View { + if let leading = model.leading { + ODSImage(source: leading) + } + } + + @ViewBuilder + private var selectedLeading: some View { + Image("iconsFunctionalUiEMIcFormTick", bundle: Bundle.ods) + .resizable() + .renderingMode(.template) + .aspectRatio(contentMode: .fit) + .background(model.leading == nil ? .clear : .black) + .foregroundColor(model.leading == nil ? .black : theme.componentColors.accent) + } + + private var leadingTextPadding: CGFloat { + model.leading == nil && selected ? ODSSpacing.xs : ODSSpacing.s + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSInputChip.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSInputChip.swift new file mode 100644 index 00000000..44a26a95 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/ODSInputChip.swift @@ -0,0 +1,63 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import SwiftUI + +/// ODS Chips. +/// +/// Chips are small components containing a number of elements that represent a calendar event or contact. +/// +public struct ODSInputChip: View { + + // ======================= + // MARK: Stored properties + // ======================= + + @Environment(\.theme) private var theme + private let text: Text + private let leading: ODSImage.Source? + private let action: () -> Void + private let removeAction: (() -> Void)? + + // ================== + // MARK: Initializers + // ================== + + /// Initialize the chip. + /// + /// - Parameters: + /// - text: Text to be displayed into the chip. + /// - leadingAvatar: Optional leading avatar to be displayed in a circle shape at the start of the chip, preceding the content text. + /// - action: The action when chip is clicked. + /// - removeAction: The action when cross is clicked. + /// + public init(text: Text, + leading: ODSImage.Source? = nil, + action: @escaping () -> Void, + removeAction: (() -> Void)? = nil) + { + self.text = text + self.leading = leading + self.action = action + self.removeAction = removeAction + } + + // ========== + // MARK: Body + // ========== + + public var body: some View { + Chip(isSelected: false, action: action, removeAction: removeAction) { + text.padding(.horizontal, ODSSpacing.s) + } leading: { + if let leading = leading { + ODSImage(source: leading) + } + } + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/Pickers/ODSChipPickerPlacement.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/Pickers/ODSChipPickerPlacement.swift new file mode 100644 index 00000000..5d96dbec --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/Pickers/ODSChipPickerPlacement.swift @@ -0,0 +1,14 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import Foundation + +public enum ODSChipPickerPlacement { + case carousel + case stacked +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/Pickers/ODSChoiceChipPicker.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/Pickers/ODSChoiceChipPicker.swift new file mode 100644 index 00000000..1ab4bde0 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/Pickers/ODSChoiceChipPicker.swift @@ -0,0 +1,142 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import Flow +import SwiftUI + +/// Create a picker by providing the selection type with a binding to get selected element(s). +/// +/// An additonnal title can be added above the Picker. +/// +public struct ODSChoiceChipPicker: View where Value: Hashable { + + // ======================= + // MARK: Stored Properties + // ======================= + + private let title: Text? + private let chips: [ODSChoiceChip] + private var selection: Binding + private let placement: ODSChipPickerPlacement + + // ================= + // MARK: Initializer + // ================= + + /// Creates a picker which manages a multiple selection that allows no element to be selected. + /// + /// - Parameters: + /// - title: Optional title above the picker + /// - selection: A binding to a property that determines the currently-selected option. + /// - chips: All chips describing elements to be displayed. + /// - placement: Define the placement of the chips in the picker. By default, chips are placed in a carousel. + /// + public init(title: Text? = nil, chips: [ODSChoiceChip], selection: Binding, placement: ODSChipPickerPlacement = .carousel) { + self.title = title + self.chips = chips + self.selection = selection + self.placement = placement + } + + // ========== + // MARK: Body + // ========== + + public var body: some View { + VStack(alignment: .leading, spacing: ODSSpacing.s) { + title? + .odsFont(.headlineS) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, ODSSpacing.m) + + switch placement { + case .carousel: + carouselContent + case .stacked: + stackedContent + } + } + } + + // ===================== + // MARK: Private Helpers + // ===================== + + private func content(for chip: ODSChoiceChip) -> some View { + ODSChoiceChipView(chip: chip, selected: selection.wrappedValue == chip.value) { + selection.wrappedValue = chip.value + } + } + + @ViewBuilder + private var carouselContent: some View { + VStack { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: ODSSpacing.s) { + ForEach(chips, id: \.value) { chip in + content(for: chip) + } + } + .padding(.leading, ODSSpacing.m) + } + } + } + + @State private var finalSize: CGSize = .zero + + @ViewBuilder + private var stackedContent: some View { + if #available(iOS 16.0, *) { + HFlow(alignment: .top, spacing: ODSSpacing.s) { + ForEach(chips, id: \.value) { chip in + content(for: chip) + } + } + .padding(.horizontal, ODSSpacing.m) + } else { + var width = CGFloat.zero + var height = CGFloat.zero + VStack { + GeometryReader { geo in + ZStack(alignment: .topLeading) { + ForEach(chips, id: \.value) { chip in + content(for: chip) + .padding(.top, ODSSpacing.xs) + .padding(.trailing, ODSSpacing.xs) + .alignmentGuide(.leading) { dimension in + if abs(width - dimension.width) > geo.size.width { + width = 0 + height -= dimension.height + } + let result = width + if chip.value == chips.last!.value { + width = 0 + } else { + width -= dimension.width + } + return result + } + .alignmentGuide(.top) { _ in + let result = height + if chip.value == chips.last!.value { + height = 0 + } + return result + } + } + } + .readSize { size in + finalSize = size + } + } + } + .frame(height: finalSize.height) + .padding(.horizontal, ODSSpacing.m) + } + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/Pickers/ODSFilterChipPicker.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/Pickers/ODSFilterChipPicker.swift new file mode 100644 index 00000000..f08181d4 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Chips/Pickers/ODSFilterChipPicker.swift @@ -0,0 +1,144 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import Flow +import SwiftUI + +public struct ODSFilterChipPicker: View where Value: Hashable { + + // ======================= + // MARK: Stored Properties + // ======================= + + private let title: Text? + private let chips: [ODSFilterChip] + private var selection: Binding<[Value]> + private let placement: ODSChipPickerPlacement + + // ================= + // MARK: Initialzier + // ================= + + /// Creates a picker which manages a multiple selection that allows no element to be selected. + /// + /// - Parameters: + /// - title: Optional title above the picker + /// - selection: A binding to a property that determines the + /// currently-selected options. + /// - chips: All chips describing elements to be displayed. + /// - placement: Define the placement of the chips in the picker. By default, chips are placed in a stacked. + /// + public init(title: Text? = nil, chips: [ODSFilterChip], selection: Binding<[Value]>, placement: ODSChipPickerPlacement = .stacked) { + self.title = title + self.chips = chips + self.selection = selection + self.placement = placement + } + + // ========== + // MARK: Body + // ========== + + public var body: some View { + VStack(alignment: .leading, spacing: ODSSpacing.s) { + title? + .odsFont(.headlineS) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, ODSSpacing.m) + + switch placement { + case .carousel: + carouselContent + case .stacked: + stackedContent + } + } + } + + // ===================== + // MARK: private Helpers + // ===================== + + private func content(for chip: ODSFilterChip) -> some View { + let index = selection.wrappedValue.firstIndex(of: chip.value) + return ODSFilterChipView(chip: chip, selected: index != nil) { + if let index = index { + selection.wrappedValue.remove(at: index) + } else { + selection.wrappedValue.append(chip.value) + } + } + } + + @ViewBuilder + private var carouselContent: some View { + VStack { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: ODSSpacing.s) { + ForEach(chips, id: \.value) { chip in + content(for: chip) + } + } + .padding(.leading, ODSSpacing.m) + } + } + } + + @State private var finalSize: CGSize = .zero + + @ViewBuilder + private var stackedContent: some View { + if #available(iOS 16.0, *) { + HFlow(alignment: .top, spacing: ODSSpacing.s) { + ForEach(chips, id: \.value) { chip in + content(for: chip) + } + } + .padding(.horizontal, ODSSpacing.m) + } else { + var width = CGFloat.zero + var height = CGFloat.zero + VStack { + GeometryReader { geo in + ZStack(alignment: .topLeading) { + ForEach(chips, id: \.value) { chip in + content(for: chip) + .padding(.top, ODSSpacing.xs) + .padding(.trailing, ODSSpacing.xs) + .alignmentGuide(.leading) { dimension in + if abs(width - dimension.width) > geo.size.width { + width = 0 + height -= dimension.height + } + let result = width + if chip.value == chips.last!.value { + width = 0 + } else { + width -= dimension.width + } + return result + } + .alignmentGuide(.top) { _ in + let result = height + if chip.value == chips.last!.value { + height = 0 + } + return result + } + } + } + .readSize { size in + finalSize = size + } + } + } + .frame(height: finalSize.height) + .padding(.horizontal, ODSSpacing.m) + } + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Image/ODSImage.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Image/ODSImage.swift index 96319ca2..bff6d9e5 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Image/ODSImage.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Image/ODSImage.swift @@ -25,6 +25,10 @@ public struct ODSImage: View { public enum Source { case image(Image) case asyncImage(URL, Image) + + public init(url: URL, placeholder: Image = Image("ods_empty", bundle: Bundle.ods)) { + self = .asyncImage(url, placeholder) + } } let source: Source @@ -49,7 +53,7 @@ public struct ODSImage: View { case let .image(image): image .resizable() - .aspectRatio(contentMode: .fit) + .aspectRatio(contentMode: .fill) } } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Lists/Internal/TrailingView.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Lists/Internal/TrailingView.swift index 56626a90..0b6905b3 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Lists/Internal/TrailingView.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Lists/Internal/TrailingView.swift @@ -26,13 +26,13 @@ struct TrailingView: View { switch element { case let .textOnly(text): text - .odsFont(.subhead) + .odsFont(.bodyS) .foregroundColor(Color(UIColor.systemGray3)) case let .iButton(action, text): HStack { text - .odsFont(.subhead) + .odsFont(.bodyS) .foregroundColor(Color(UIColor.systemGray3)) ODSIconButton(image: Image(systemName: "info.circle"), action: action) diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Lists/ODSListItem.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Lists/ODSListItem.swift index e4415bc8..2371e060 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Lists/ODSListItem.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/Lists/ODSListItem.swift @@ -267,13 +267,13 @@ public struct ODSListItem: View { VStack(alignment: .leading, spacing: ODSSpacing.none) { title - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .foregroundColor(.primary) .frame(maxWidth: .infinity, alignment: .leading) .multilineTextAlignment(.leading) subtitle? - .odsFont(.footnote) + .odsFont(.labelL) .foregroundColor(.primary) .multilineTextAlignment(.leading) } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/TextField/ODSTextFieldStyle.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/TextField/ODSTextFieldStyle.swift index 9328b7db..d420f714 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/TextField/ODSTextFieldStyle.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Components/TextField/ODSTextFieldStyle.swift @@ -43,7 +43,7 @@ private struct ODSTextFieldStyle: ViewModifier { content .accentColor(theme.componentColors.accent) .padding(.all, ODSSpacing.s) - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .background(Color(.tertiarySystemFill), in: RoundedRectangle(cornerRadius: 8.0)) } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Guidelines/Fonts/ODSFontsStyle.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Guidelines/Fonts/ODSFontsStyle.swift index 617be264..d41d9460 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Guidelines/Fonts/ODSFontsStyle.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Guidelines/Fonts/ODSFontsStyle.swift @@ -14,17 +14,17 @@ import SwiftUI /// the `ODSTheme`. /// public enum ODSFontStyle: String, CaseIterable { - case largeTitle - case title1 - case title2 - case title3 - case headline - case bodyRegular - case bodyBold - case callout - case subhead - case footnote - case caption1Regular - case caption1Bold - case caption2 + case headlineL + case headlineS + case titleL + case titleM + case titleS + case bodyLBold + case bodyLRegular + case bodyM + case bodyS + case labelL + case labelMBold + case labelMRegular + case labelS } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Configuration/Items/AppNews/ODSAboutAppNewsItem.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Configuration/Items/AppNews/ODSAboutAppNewsItem.swift index ae933696..464448a6 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Configuration/Items/AppNews/ODSAboutAppNewsItem.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Configuration/Items/AppNews/ODSAboutAppNewsItem.swift @@ -39,6 +39,6 @@ public struct ODSAboutAppNewsItemConfig: ODSAboutListItemConfig { self.priority = priority title = "modules.about.app_news.title".🌐 icon = Image("ic_taskList", bundle: Bundle.ods) - target = .destination(AnyView(AppNewsList(fromFile: path))) + target = .destination(AnyView(AppNewsList(viewModel: AppNewsListViewModel(fromFile: path)))) } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Configuration/Items/ODSAboutListItemConfiguration.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Configuration/Items/ODSAboutListItemConfiguration.swift index abc85dfe..88aa6f9c 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Configuration/Items/ODSAboutListItemConfiguration.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Configuration/Items/ODSAboutListItemConfiguration.swift @@ -38,7 +38,7 @@ extension ODSAboutListItemPriority { public enum ODSAboutListItemTarget { /// Means the items is a navigation link that opens the provided view. case destination(AnyView) - /// Mmeans the item is like a button that runs action when it is tapped. + /// Means the item is like a button that runs action when it is tapped. case action(() -> Void) } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/ApplicationInformation/ApplicationInformation.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/ApplicationInformation/ApplicationInformation.swift index db795987..fbdba8ee 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/ApplicationInformation/ApplicationInformation.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/ApplicationInformation/ApplicationInformation.swift @@ -25,7 +25,7 @@ struct AboutApplicationInformation: View { var body: some View { VStack(alignment: .leading, spacing: ODSSpacing.m) { Text(LocalizedStringKey(applicationInformation.name)) - .odsFont(.largeTitle) + .odsFont(.headlineL) .fixedSize(horizontal: false, vertical: true) .accessibilityAddTraits(.isHeader) diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AboutReleaseDescription.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AboutReleaseDescription.swift new file mode 100644 index 00000000..e02990b8 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AboutReleaseDescription.swift @@ -0,0 +1,19 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import Foundation + +// ================================= +// MARK: - About Release Description +// ================================= + +struct AboutReleaseDescription: Decodable { + let version: String + let date: Date + let news: String +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AboutReleaseDescriptionModel.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AboutReleaseDescriptionModel.swift deleted file mode 100644 index f281a343..00000000 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AboutReleaseDescriptionModel.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// Software Name: Orange Design System (iOS) -// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA -// SPDX-License-Identifier: MIT -// -// This software is distributed under the MIT license. -// - -import Foundation - -struct AboutReleaseDescription: Decodable { - let version: String - let date: Date - let news: String -} - -class AboutReleaseDescriptionsLoader { - - // ================= - // MARK: Initializer - // ================= - - init() {} - - enum Error: Swift.Error { - case resourceNotFound - case noJsonData - } - - // ==================== - // MARK: Private Helper - // ==================== - - func load(from applicationNewsPath: String) throws -> [AboutReleaseDescription] { - - guard FileManager().fileExists(atPath: applicationNewsPath) else { - throw Error.resourceNotFound - } - - guard let jsonData = try String(contentsOfFile: applicationNewsPath).data(using: .utf8) else { - throw Error.noJsonData - } - - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd" // Format of date in the AppNews JSON file - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .formatted(dateFormatter) - return try decoder.decode([AboutReleaseDescription].self, from: jsonData) - } -} - -extension AboutReleaseDescription { - static func load() throws -> [AboutReleaseDescription] { - return try AboutReleaseDescriptionsLoader().load(from: "AppNews") - } -} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AppNewsViewModel.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AppNewsViewModel.swift new file mode 100644 index 00000000..fa58e9b0 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AppNewsViewModel.swift @@ -0,0 +1,82 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import Foundation +import SwiftUI + +// ================================ +// MARK: - App News List View Model +// ================================ + +final class AppNewsListViewModel: ObservableObject { + + /// The state containing or not the release descriptions to use based on the locale + @Published var releaseDescriptions: LoadingState<[AboutReleaseDescription], AppNewsListViewModel.Error> + /// Path to the app news file to display + private let newsFilePath: String + + // ================= + // MARK: Initializer + // ================= + + init(fromFile newsFilePath: String) { + releaseDescriptions = .loading + self.newsFilePath = newsFilePath + } + + // ============= + // MARK: Service + // ============= + + /// Errors which may appear while processing the app news files + enum Error: Swift.Error { + case resourceNotFound + case rawParsingFailure + case jsonParsingFailure + } + + /// Loads from the local `newsFilePath` the content of the target file, then decodes as JSON and converts and stores as + /// `[AboutReleaseDescription]` available through the _releaseDescriptions_ published property. + /// + /// It expects to have an array of objects with the following attributes (see DEVELOP.md file for further details): + ///
+    ///     {
+    ///         "version": "Some x.y.z version",
+    ///         "date": "date in yyyy-MM-dd format",,
+    ///         "news": "News descriptions with /n symbol for new lines"
+    ///     }
+    /// 
+ func load() { + + // If file missing, nothing to do more + guard FileManager().fileExists(atPath: newsFilePath) else { + ODSLogger.error("Resource not found for AppNews") + releaseDescriptions = .error(.resourceNotFound) + return + } + + // Read file, update cache, update state + guard let rawData = try? String(contentsOfFile: newsFilePath).data(using: .utf8) else { + ODSLogger.error("Failed to parse UTF8 raw data") + releaseDescriptions = .error(.rawParsingFailure) + return + } + + let dateFormatter = DateFormatter.formatter(for: "yyyy-MM-dd") // Format of date in the AppNews JSON file + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .formatted(dateFormatter) + guard let decodedReleaseDescriptions = try? decoder.decode([AboutReleaseDescription].self, from: rawData) else { + ODSLogger.error("Parsing error for AppNews") + releaseDescriptions = .error(.jsonParsingFailure) + return + } + + ODSLogger.debug("Successfully loaded AppNews resource") + releaseDescriptions = .loaded(decodedReleaseDescriptions) + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AppNewsList.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AppNewsViews.swift similarity index 56% rename from OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AppNewsList.swift rename to OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AppNewsViews.swift index c48c469f..4db48962 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AppNewsList.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Modules/About/Internal/LitsItems/AppNews/AppNewsViews.swift @@ -18,14 +18,14 @@ struct AppNewsList: View { // MARK: Stored Properties // ======================= - let releaseDescriptions: [AboutReleaseDescription] + @ObservedObject private var viewModel: AppNewsListViewModel // ================= // MARK: Initializer // ================= - init(fromFile path: String) { - releaseDescriptions = (try? AboutReleaseDescriptionsLoader().load(from: path)) ?? [] + init(viewModel: AppNewsListViewModel) { + self.viewModel = viewModel } // ========== @@ -34,24 +34,45 @@ struct AppNewsList: View { var body: some View { ScrollView { - if releaseDescriptions.isEmpty { - Text(°°"modules.about.app_news.no_news") - } else { - ForEach(releaseDescriptions, id: \.version) { releaseDescription in - AboutReleaaseDescriptionEntry(releaseDescription: releaseDescription) - .padding(.horizontal, ODSSpacing.m) - Divider() - } + switch viewModel.releaseDescriptions { + case .loading: + loadingView() + case let .loaded(releaseDescription) where !releaseDescription.isEmpty: + loadedView(releaseDescription) + default: // case .error, case .loaded(let releaseDescription) where releaseDescription.isEmpty: + errorView() } + }.task { + viewModel.load() } } + + // =========== + // MARK: Views + // =========== + + private func loadingView() -> some View { + Text(°°"shared.loading") + } + + private func loadedView(_ data: [AboutReleaseDescription]) -> some View { + ForEach(data, id: \.version) { item in + AboutReleaseDescriptionEntry(releaseDescription: item) + .padding(.horizontal, ODSSpacing.m) + Divider() + } + } + + private func errorView() -> some View { + Text(°°"modules.about.app_news.no_news") + } } // ======================================= // MARK: - About Release Description Entry // ======================================= -private struct AboutReleaaseDescriptionEntry: View { +private struct AboutReleaseDescriptionEntry: View { // ======================= // MARK: Stored Properties @@ -59,6 +80,13 @@ private struct AboutReleaaseDescriptionEntry: View { let releaseDescription: AboutReleaseDescription + private var formatedDate: String { + DateFormatter.localizedFormat(date: releaseDescription.date, + for: Bundle.main, + dateStyle: .short, + timeStyle: .none) + } + // ========== // MARK: Body // ========== @@ -66,26 +94,15 @@ private struct AboutReleaaseDescriptionEntry: View { var body: some View { VStack(alignment: .leading, spacing: ODSSpacing.s) { HStack { - Text(releaseDescription.version).odsFont(.headline) + Text(releaseDescription.version).odsFont(.headlineS) Spacer() - Text(formatedDate).odsFont(.caption1Regular) + Text(formatedDate).odsFont(.labelMRegular) } Text(releaseDescription.news) - .odsFont(.callout) + .odsFont(.bodyM) .fixedSize(horizontal: false, vertical: true) } .padding(.vertical, ODSSpacing.s) } - - // ==================== - // MARK: Private Helper - // ==================== - - private var formatedDate: String { - DateFormatter.localizedFormat(date: releaseDescription.date, - for: Bundle.main, - dateStyle: .short, - timeStyle: .none) - } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Resources/Icons.xcassets/ic_close.imageset/Contents.json b/OrangeDesignSystem/Sources/OrangeDesignSystem/Resources/Icons.xcassets/ic_close.imageset/Contents.json new file mode 100644 index 00000000..81be9f7e --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Resources/Icons.xcassets/ic_close.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "form-cross.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Resources/Icons.xcassets/ic_close.imageset/form-cross.svg b/OrangeDesignSystem/Sources/OrangeDesignSystem/Resources/Icons.xcassets/ic_close.imageset/form-cross.svg new file mode 100644 index 00000000..6ab90a76 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Resources/Icons.xcassets/ic_close.imageset/form-cross.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/ODSTheme.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/ODSTheme.swift index 9a1a8df6..f0fc5e41 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/ODSTheme.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Theme/ODSTheme.swift @@ -36,31 +36,31 @@ public struct ODSTheme: Identifiable, Hashable { componentColors = ODSComponentColors() font = { style in switch style { - case .largeTitle: + case .headlineL: return Font.largeTitle.bold() - case .title1: + case .titleL: return Font.title.bold() - case .title2: + case .titleM: return Font.title2.bold() - case .title3: + case .titleS: return Font.title3.bold() - case .headline: + case .headlineS: return Font.headline.bold() - case .bodyRegular: - return Font.body - case .bodyBold: + case .bodyLBold: return Font.body.bold() - case .callout: + case .bodyLRegular: + return Font.body + case .bodyM: return Font.callout - case .subhead: + case .bodyS: return Font.subheadline.bold() - case .footnote: + case .labelL: return Font.footnote - case .caption1Regular: - return Font.caption - case .caption1Bold: + case .labelMBold: return Font.caption.bold() - case .caption2: + case .labelMRegular: + return Font.caption + case .labelS: return Font.caption2 } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/DateFormatter+extension.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/DateFormatter+extension.swift index a4b723a7..7d0889e3 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/DateFormatter+extension.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/DateFormatter+extension.swift @@ -39,9 +39,8 @@ extension DateFormatter { return dateFormatter.string(from: date) } - /// Creates if not yet done or jsut returns a `DateFormatter` using the given parameters. + /// Creates if not yet done or just returns a `DateFormatter` using the given parameters. /// Because the initialization of such objects could be costly the object once created is stored locally. - /// See https://sarunw.com/posts/how-expensive-is-dateformatter/ /// - Parameters: /// - locale: The locale to use to format some date /// - dateStyle: The date to apply for the format @@ -55,7 +54,22 @@ extension DateFormatter { dateFormatter.locale = locale dateFormatter.dateStyle = dateStyle dateFormatter.timeStyle = timeStyle - DateFormatterCache.shared.store(formatter: dateFormatter) + DateFormatterCache.shared.store(formatter: dateFormatter, using: .localeDateTimeCache) + return dateFormatter + } + } + + /// Creates if not yet done or just returns a `DateFormatter` using the given parameters. + /// Because the initialization of such objects could be costly the object once created is stored locally. + /// - Parameter dateFormat: The date format to apply to the date formatter + /// - Returns: A freshly created new `DateFormatter` or another objected stored previsouly when created before + static func formatter(for dateFormat: String) -> DateFormatter { + if let dateFormatter = DateFormatterCache.shared.formatter(for: dateFormat) { + return dateFormatter + } else { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = dateFormat + DateFormatterCache.shared.store(formatter: dateFormatter, using: .dateFormatCache) return dateFormatter } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/DateFormatterCache.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/DateFormatterCache.swift index 6d8471f2..322361cb 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/DateFormatterCache.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/DateFormatterCache.swift @@ -9,19 +9,30 @@ import Foundation /// Because the instanciations of `DateFormatter` can be costly, helps to store such objects according to their configuration +/// See https://sarunw.com/posts/how-expensive-is-dateformatter/ final class DateFormatterCache { - /// The cache of formatters depending to some configuration they must have - private var cache: Cache + // MARK: - Properties + + /// A cache of formatters depending to some configuration they must have (locale, date style and time style) + private var localeDateTimeCache: Cache + + /// A cache of formatters depending to some configuration they must have (date format) + private var dateFormatCache: Cache + + // MARK: - Singleton definition /// The singleton static let shared = DateFormatterCache() private init() { - cache = Cache() + localeDateTimeCache = Cache() + dateFormatCache = Cache() } - struct Configuration: Hashable { + // MARK: - Locale Date Time Configuration + + struct LocaleDateTimeConfiguration: Hashable { let locale: Locale let dateStyle: DateFormatter.Style let timeStyle: DateFormatter.Style @@ -29,19 +40,48 @@ final class DateFormatterCache { /// - Returns: The `DateFormatter` which has the given arguments in configuration, or `nil` of none func formatter(for locale: Locale, dateStyle: DateFormatter.Style, timeStyle: DateFormatter.Style) -> DateFormatter? { - cache[Configuration(locale: locale, dateStyle: dateStyle, timeStyle: timeStyle)] + localeDateTimeCache[LocaleDateTimeConfiguration(locale: locale, dateStyle: dateStyle, timeStyle: timeStyle)] + } + + // MARK: - Date Format Confguration + + struct DateFormatConfiguration: Hashable { + let dateFormat: String } - /// Stores in the cahe the given `DateFormatter` and overrides the prvious value if it exists - /// - Parameter formatter: The `DateFormatter` to store according to its locale, date style and time style - func store(formatter: DateFormatter) { - cache[Configuration(locale: formatter.locale, - dateStyle: formatter.dateStyle, - timeStyle: formatter.timeStyle)] = formatter + /// - Returns: The `DateFormatter` which has the given argument in configuration, or `nil` of none + func formatter(for dateFormat: String) -> DateFormatter? { + dateFormatCache[DateFormatConfiguration(dateFormat: dateFormat)] } - /// Deletes all the entries store din the cache + // MARK: - Store + + /// To specifiy the type of cache the date formatters must be stored + enum CacheType { + case localeDateTimeCache + case dateFormatCache + } + + /// Stores in the cache the given `DateFormatter` and overrides the previous value if it exists + /// - Parameters: + /// - formatter: The `DateFormatter` to store using the specified cache type + /// - cacheType: The type of cache to use defining the configuration of the date formatter as key + func store(formatter: DateFormatter, using cacheType: CacheType) { + switch cacheType { + case .localeDateTimeCache: + localeDateTimeCache[LocaleDateTimeConfiguration(locale: formatter.locale, + dateStyle: formatter.dateStyle, + timeStyle: formatter.timeStyle)] = formatter + case .dateFormatCache: + dateFormatCache[DateFormatConfiguration(dateFormat: formatter.dateFormat)] = formatter + } + } + + // MARK: - Flush + + /// Deletes all the entries stored in the caches func flush() { - cache.removeAllValues() + localeDateTimeCache.removeAllValues() + dateFormatCache.removeAllValues() } } diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/LoadingState.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/LoadingState.swift new file mode 100644 index 00000000..f1f08e02 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/LoadingState.swift @@ -0,0 +1,26 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import Foundation + +/// Enumeration which allows to deal with loading states of views so as to display or not a skeleton, get data or error. +/// Should be used when the view is loading data or waiting for a request response and view model results for example. +/// - Parameters: +/// - Payload: Genreric type of data (picked from backend request for example) +/// - Error: Some error instead of a payload +enum LoadingState { + + /// Waiting for some data + case loading + + /// Data have been loaded + case loaded(_ payload: Payload) + + /// And error occured whiile loading data + case error(_ error: Error) +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/ODSBundle+extension.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/ODSBundle+extension.swift index 9e9f5613..60d73b3f 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/ODSBundle+extension.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/ODSBundle+extension.swift @@ -20,5 +20,5 @@ extension Bundle { #if SWIFT_PACKAGE #else -class ODSBundleResource {} +final class ODSBundleResource {} #endif diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/ODSLogger.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/ODSLogger.swift new file mode 100644 index 00000000..465dc8c0 --- /dev/null +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/ODSLogger.swift @@ -0,0 +1,40 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import Foundation +import os + +/// Simple struct for logs in _Xcode_ outputs +struct ODSLogger { + + private init() {} + + private static let logger = Logger() + private static let bullet: String = "💫" + private static let prefix: String = "\(bullet) OrangeDesignSystem" + + static func debug(_ message: String) { + logger.debug("\(prefix):debug: 🪲 \(message)") + } + + static func log(_ message: String) { + logger.log("\(prefix): \(message)") + } + + static func info(_ message: String) { + logger.info("\(prefix):info: ℹ️ \(message)") + } + + static func warning(_ message: String) { + logger.warning("\(prefix):warning: ⚠️ \(message)") + } + + static func error(_ message: String) { + logger.error("\(prefix):error: 💥 \(message)") + } +} diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/ShareSheet.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/ShareSheet.swift index efb82b90..2596011b 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/ShareSheet.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/ShareSheet.swift @@ -26,7 +26,7 @@ enum ShareSheet { } } -private class ShareItem: NSObject, UIActivityItemSource { +private final class ShareItem: NSObject, UIActivityItemSource { let content: String let mailSubject: String diff --git a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/TabBar+readSize.swift b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/TabBar+readSize.swift index 715f2140..264ae54a 100644 --- a/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/TabBar+readSize.swift +++ b/OrangeDesignSystem/Sources/OrangeDesignSystem/Utils/TabBar+readSize.swift @@ -39,7 +39,7 @@ struct TabBarConfigurator: UIViewControllerRepresentable { func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {} } -class TabBarConfigurationViewController: UIViewController { +final class TabBarConfigurationViewController: UIViewController { // ======================= // MARK: Stored Properties diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcodeproj/project.pbxproj b/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcodeproj/project.pbxproj index c0231824..b427456e 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcodeproj/project.pbxproj +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 072D9EF82B0CA96A00763EAE /* ChoiceChipsVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 072D9EF72B0CA96A00763EAE /* ChoiceChipsVariant.swift */; }; 075326852AD45610003D8832 /* GridOfSmallCards.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075326832AD45610003D8832 /* GridOfSmallCards.swift */; }; 075326862AD45610003D8832 /* ListOfVerticalImageFirstCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075326842AD45610003D8832 /* ListOfVerticalImageFirstCard.swift */; }; 077B673C2AE1500F0008C32B /* LeadingOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077B673B2AE1500F0008C32B /* LeadingOption.swift */; }; @@ -45,7 +46,6 @@ 077C385C2A9DDC79003D6B51 /* ProgressIndicatorComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077C38082A9DDC79003D6B51 /* ProgressIndicatorComponent.swift */; }; 077C385D2A9DDC79003D6B51 /* ActivityIndicatorVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077C38092A9DDC79003D6B51 /* ActivityIndicatorVariant.swift */; }; 077C385E2A9DDC79003D6B51 /* ChipsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077C380B2A9DDC79003D6B51 /* ChipsComponent.swift */; }; - 077C385F2A9DDC79003D6B51 /* ChipsComponentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077C380C2A9DDC79003D6B51 /* ChipsComponentModel.swift */; }; 077C38602A9DDC79003D6B51 /* CardVerticalHeaderFirstVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077C380E2A9DDC79003D6B51 /* CardVerticalHeaderFirstVariant.swift */; }; 077C38612A9DDC79003D6B51 /* CardExampleData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077C380F2A9DDC79003D6B51 /* CardExampleData.swift */; }; 077C38622A9DDC79003D6B51 /* CardVerticalImageFirstVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077C38102A9DDC79003D6B51 /* CardVerticalImageFirstVariant.swift */; }; @@ -97,14 +97,18 @@ 077C389E2A9DEEDC003D6B51 /* ThemeablePreviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077C38962A9DEEDC003D6B51 /* ThemeablePreviews.swift */; }; 077C38A22AA0DD68003D6B51 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = FDDAB0F62809AB2100ACE5F4 /* CHANGELOG.md */; }; 077C38A52AA215BC003D6B51 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 077C37CB2A9DD643003D6B51 /* Assets.xcassets */; }; + 0786F7422B1649F100299A75 /* FilterChipVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0786F7412B1649F100299A75 /* FilterChipVariant.swift */; }; + 078AB0552B30A7AA00C592AE /* Variants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 078AB0542B30A7AA00C592AE /* Variants.swift */; }; 079DDC0C2AE6A18D0073A542 /* Recipe+id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 079DDC0B2AE6A18D0073A542 /* Recipe+id.swift */; }; 07AA3D4E28AE8B160001B75E /* Pods_OrangeDesignSystemDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 07AA3D4D28AE8B160001B75E /* Pods_OrangeDesignSystemDemo.framework */; }; + 07B1A09C2B1E191F00ABF0A1 /* InputChipsVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B1A09B2B1E191F00ABF0A1 /* InputChipsVariant.swift */; }; + 07B543E72B289B2100A2B6ED /* ListItemSelectionVariantModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B543E62B289B2000A2B6ED /* ListItemSelectionVariantModel.swift */; }; 07B94DEA2AD9239700AAD1A5 /* ListItemStandardVariantOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B94DE82AD9239700AAD1A5 /* ListItemStandardVariantOptions.swift */; }; - 07B94DEB2AD9239700AAD1A5 /* ListItemVariantStandardModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B94DE92AD9239700AAD1A5 /* ListItemVariantStandardModel.swift */; }; + 07B94DEB2AD9239700AAD1A5 /* ListItemStandardVariantModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B94DE92AD9239700AAD1A5 /* ListItemStandardVariantModel.swift */; }; 07B94E0B2AD98D7900AAD1A5 /* ListItemStandardVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B94E0A2AD98D7900AAD1A5 /* ListItemStandardVariant.swift */; }; 07B94E0D2AD9902A00AAD1A5 /* ListItemSelectionVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B94E082AD9892C00AAD1A5 /* ListItemSelectionVariant.swift */; }; - 07B94E0E2AD9904900AAD1A5 /* ListItemVaraintSelectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B94E0C2AD98DAA00AAD1A5 /* ListItemVaraintSelectionModel.swift */; }; 07B94E0F2AD9908300AAD1A5 /* ListItemSelectionVariantOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B94E092AD9892C00AAD1A5 /* ListItemSelectionVariantOptions.swift */; }; + 07BDEA932B150263005574A2 /* ActionChipsVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07BDEA922B150263005574A2 /* ActionChipsVariant.swift */; }; 07C3C7FC2AE7E4E000833957 /* ListModuleDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07C3C7FB2AE7E4E000833957 /* ListModuleDataModel.swift */; }; 07C3C7FE2AE7E56100833957 /* ListModuleOptionsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07C3C7FD2AE7E56100833957 /* ListModuleOptionsModel.swift */; }; 07E4C85C2AE7BC2D00FB4DAC /* ListModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E4C85B2AE7BC2D00FB4DAC /* ListModule.swift */; }; @@ -118,6 +122,11 @@ 51BFDAB52AF9206E00FB8139 /* AccessibilityStatement.xml in Resources */ = {isa = PBXBuildFile; fileRef = 51BFDAB32AF9206E00FB8139 /* AccessibilityStatement.xml */; }; 51C776932AE1512200FD0AED /* DateFormatterCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C776922AE1512200FD0AED /* DateFormatterCacheTests.swift */; }; 51C776952AE1551600FD0AED /* CacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C776942AE1551600FD0AED /* CacheTests.swift */; }; + 51EE08832B0F5B31003A254A /* AppNewsListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EE08822B0F5B31003A254A /* AppNewsListViewModelTests.swift */; }; + 51EE08892B0F6074003A254A /* FileWithoutJson.json in Resources */ = {isa = PBXBuildFile; fileRef = 51EE08882B0F6074003A254A /* FileWithoutJson.json */; }; + 51EE088C2B0F6985003A254A /* NotStringFile.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 51EE088B2B0F6985003A254A /* NotStringFile.jpg */; }; + 51EE088E2B0F6A5B003A254A /* AppNewsMock.json in Resources */ = {isa = PBXBuildFile; fileRef = 51EE088D2B0F6A5B003A254A /* AppNewsMock.json */; }; + 51EE08902B0F6FDD003A254A /* AppNewsMock_notCompliant_badDateFormat.json in Resources */ = {isa = PBXBuildFile; fileRef = 51EE088F2B0F6FDD003A254A /* AppNewsMock_notCompliant_badDateFormat.json */; }; 51F2E6D52AE6AC2B00377A07 /* StringLocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F2E6D42AE6AC2B00377A07 /* StringLocalizationTests.swift */; }; 51F90B1B2ADED756007AAA45 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 513876902ADD624B00AE53DF /* Localizable.strings */; }; C88E9E08333D56BAEA28A60E /* Pods_OrangeDesignSystemDemoTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E816C9278060A8072007BFD0 /* Pods_OrangeDesignSystemDemoTests.framework */; }; @@ -143,6 +152,7 @@ 07210C462A8FACAA00507988 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 07210C482A8FAD1500507988 /* Fastfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Fastfile; path = fastlane/Fastfile; sourceTree = ""; }; 07210C492A8FAD1600507988 /* Appfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Appfile; path = fastlane/Appfile; sourceTree = ""; }; + 072D9EF72B0CA96A00763EAE /* ChoiceChipsVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChoiceChipsVariant.swift; sourceTree = ""; }; 075326832AD45610003D8832 /* GridOfSmallCards.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridOfSmallCards.swift; sourceTree = ""; }; 075326842AD45610003D8832 /* ListOfVerticalImageFirstCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOfVerticalImageFirstCard.swift; sourceTree = ""; }; 077B673B2AE1500F0008C32B /* LeadingOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeadingOption.swift; sourceTree = ""; }; @@ -174,7 +184,6 @@ 077C38082A9DDC79003D6B51 /* ProgressIndicatorComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressIndicatorComponent.swift; sourceTree = ""; }; 077C38092A9DDC79003D6B51 /* ActivityIndicatorVariant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorVariant.swift; sourceTree = ""; }; 077C380B2A9DDC79003D6B51 /* ChipsComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChipsComponent.swift; sourceTree = ""; }; - 077C380C2A9DDC79003D6B51 /* ChipsComponentModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChipsComponentModel.swift; sourceTree = ""; }; 077C380E2A9DDC79003D6B51 /* CardVerticalHeaderFirstVariant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardVerticalHeaderFirstVariant.swift; sourceTree = ""; }; 077C380F2A9DDC79003D6B51 /* CardExampleData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardExampleData.swift; sourceTree = ""; }; 077C38102A9DDC79003D6B51 /* CardVerticalImageFirstVariant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardVerticalImageFirstVariant.swift; sourceTree = ""; }; @@ -224,16 +233,20 @@ 077C38932A9DEEDC003D6B51 /* RecipesLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecipesLoader.swift; sourceTree = ""; }; 077C38952A9DEEDC003D6B51 /* ThemeSelectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeSelectionView.swift; sourceTree = ""; }; 077C38962A9DEEDC003D6B51 /* ThemeablePreviews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeablePreviews.swift; sourceTree = ""; }; + 0786F7412B1649F100299A75 /* FilterChipVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterChipVariant.swift; sourceTree = ""; }; + 078AB0542B30A7AA00C592AE /* Variants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Variants.swift; sourceTree = ""; }; 079DDC0B2AE6A18D0073A542 /* Recipe+id.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Recipe+id.swift"; sourceTree = ""; }; 07AA3D4D28AE8B160001B75E /* Pods_OrangeDesignSystemDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_OrangeDesignSystemDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 07B030592AB0B454001764E7 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 07B0305A2AB0B4A9001764E7 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = CONTRIBUTING.md; path = ../CONTRIBUTING.md; sourceTree = ""; }; + 07B1A09B2B1E191F00ABF0A1 /* InputChipsVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputChipsVariant.swift; sourceTree = ""; }; + 07B543E62B289B2000A2B6ED /* ListItemSelectionVariantModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListItemSelectionVariantModel.swift; sourceTree = ""; }; 07B94DE82AD9239700AAD1A5 /* ListItemStandardVariantOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListItemStandardVariantOptions.swift; sourceTree = ""; }; - 07B94DE92AD9239700AAD1A5 /* ListItemVariantStandardModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListItemVariantStandardModel.swift; sourceTree = ""; }; + 07B94DE92AD9239700AAD1A5 /* ListItemStandardVariantModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListItemStandardVariantModel.swift; sourceTree = ""; }; 07B94E082AD9892C00AAD1A5 /* ListItemSelectionVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemSelectionVariant.swift; sourceTree = ""; }; 07B94E092AD9892C00AAD1A5 /* ListItemSelectionVariantOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemSelectionVariantOptions.swift; sourceTree = ""; }; 07B94E0A2AD98D7900AAD1A5 /* ListItemStandardVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemStandardVariant.swift; sourceTree = ""; }; - 07B94E0C2AD98DAA00AAD1A5 /* ListItemVaraintSelectionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemVaraintSelectionModel.swift; sourceTree = ""; }; + 07BDEA922B150263005574A2 /* ActionChipsVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionChipsVariant.swift; sourceTree = ""; }; 07C3C7FB2AE7E4E000833957 /* ListModuleDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListModuleDataModel.swift; sourceTree = ""; }; 07C3C7FD2AE7E56100833957 /* ListModuleOptionsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListModuleOptionsModel.swift; sourceTree = ""; }; 07E4C85B2AE7BC2D00FB4DAC /* ListModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListModule.swift; sourceTree = ""; }; @@ -247,6 +260,11 @@ 51BFDAB42AF9206E00FB8139 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = fr; path = OrangeDesignSystemDemo/Resources/fr.lproj/AccessibilityStatement.xml; sourceTree = ""; }; 51C776922AE1512200FD0AED /* DateFormatterCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DateFormatterCacheTests.swift; path = OrangeDesignSystemDemoTests/DateFormatterCacheTests.swift; sourceTree = ""; }; 51C776942AE1551600FD0AED /* CacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CacheTests.swift; path = OrangeDesignSystemDemoTests/CacheTests.swift; sourceTree = ""; }; + 51EE08822B0F5B31003A254A /* AppNewsListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppNewsListViewModelTests.swift; path = OrangeDesignSystemDemoTests/AppNewsListViewModelTests.swift; sourceTree = ""; }; + 51EE08882B0F6074003A254A /* FileWithoutJson.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = FileWithoutJson.json; sourceTree = ""; }; + 51EE088B2B0F6985003A254A /* NotStringFile.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = NotStringFile.jpg; sourceTree = ""; }; + 51EE088D2B0F6A5B003A254A /* AppNewsMock.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = AppNewsMock.json; sourceTree = ""; }; + 51EE088F2B0F6FDD003A254A /* AppNewsMock_notCompliant_badDateFormat.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = AppNewsMock_notCompliant_badDateFormat.json; sourceTree = ""; }; 51F058B62AE8083500B1F690 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = OrangeDesignSystemDemo/Resources/Base.lproj/Localizable.strings; sourceTree = ""; }; 51F2E6D42AE6AC2B00377A07 /* StringLocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StringLocalizationTests.swift; path = OrangeDesignSystemDemoTests/StringLocalizationTests.swift; sourceTree = ""; }; 759CBF84E701D1BE3D68D365 /* Pods_OrangeDesignSystemDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OrangeDesignSystemDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -395,6 +413,7 @@ 077C37EF2A9DDC78003D6B51 /* CustomizableVariant.swift */, 077C37F02A9DDC78003D6B51 /* ComponentPage.swift */, 077C37F12A9DDC78003D6B51 /* Component.swift */, + 078AB0542B30A7AA00C592AE /* Variants.swift */, ); path = Template; sourceTree = ""; @@ -461,7 +480,10 @@ isa = PBXGroup; children = ( 077C380B2A9DDC79003D6B51 /* ChipsComponent.swift */, - 077C380C2A9DDC79003D6B51 /* ChipsComponentModel.swift */, + 072D9EF72B0CA96A00763EAE /* ChoiceChipsVariant.swift */, + 07BDEA922B150263005574A2 /* ActionChipsVariant.swift */, + 0786F7412B1649F100299A75 /* FilterChipVariant.swift */, + 07B1A09B2B1E191F00ABF0A1 /* InputChipsVariant.swift */, ); path = Chips; sourceTree = ""; @@ -713,9 +735,9 @@ 07B94E062AD9890900AAD1A5 /* StandardVariant */ = { isa = PBXGroup; children = ( - 07B94DE82AD9239700AAD1A5 /* ListItemStandardVariantOptions.swift */, 07B94E0A2AD98D7900AAD1A5 /* ListItemStandardVariant.swift */, - 07B94DE92AD9239700AAD1A5 /* ListItemVariantStandardModel.swift */, + 07B94DE92AD9239700AAD1A5 /* ListItemStandardVariantModel.swift */, + 07B94DE82AD9239700AAD1A5 /* ListItemStandardVariantOptions.swift */, ); path = StandardVariant; sourceTree = ""; @@ -723,9 +745,9 @@ 07B94E072AD9891E00AAD1A5 /* SelectionVariant */ = { isa = PBXGroup; children = ( - 07B94E092AD9892C00AAD1A5 /* ListItemSelectionVariantOptions.swift */, 07B94E082AD9892C00AAD1A5 /* ListItemSelectionVariant.swift */, - 07B94E0C2AD98DAA00AAD1A5 /* ListItemVaraintSelectionModel.swift */, + 07B543E62B289B2000A2B6ED /* ListItemSelectionVariantModel.swift */, + 07B94E092AD9892C00AAD1A5 /* ListItemSelectionVariantOptions.swift */, ); path = SelectionVariant; sourceTree = ""; @@ -762,14 +784,28 @@ 51C776912AE14F6900FD0AED /* Utils */ = { isa = PBXGroup; children = ( + 51EE088A2B0F632D003A254A /* Data */, F99FF08A2767B226006236A0 /* DateFormatterTests.swift */, 51C776922AE1512200FD0AED /* DateFormatterCacheTests.swift */, 51C776942AE1551600FD0AED /* CacheTests.swift */, 51F2E6D42AE6AC2B00377A07 /* StringLocalizationTests.swift */, + 51EE08822B0F5B31003A254A /* AppNewsListViewModelTests.swift */, ); name = Utils; sourceTree = ""; }; + 51EE088A2B0F632D003A254A /* Data */ = { + isa = PBXGroup; + children = ( + 51EE088B2B0F6985003A254A /* NotStringFile.jpg */, + 51EE08882B0F6074003A254A /* FileWithoutJson.json */, + 51EE088D2B0F6A5B003A254A /* AppNewsMock.json */, + 51EE088F2B0F6FDD003A254A /* AppNewsMock_notCompliant_badDateFormat.json */, + ); + name = Data; + path = OrangeDesignSystemDemoTests/Data; + sourceTree = ""; + }; 5DEE13EE0B42A31B2EDDB804 /* Pods */ = { isa = PBXGroup; children = ( @@ -846,6 +882,7 @@ EB0AA52D275652AE0012E192 /* Frameworks */, EB0AA52E275652AE0012E192 /* Resources */, 59A545B45A1BFD1A7DC83D74 /* [CP] Embed Pods Frameworks */, + 51EE08912B0F7464003A254A /* Check JSON files format */, ); buildRules = ( ); @@ -948,6 +985,10 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 51EE08902B0F6FDD003A254A /* AppNewsMock_notCompliant_badDateFormat.json in Resources */, + 51EE08892B0F6074003A254A /* FileWithoutJson.json in Resources */, + 51EE088C2B0F6985003A254A /* NotStringFile.jpg in Resources */, + 51EE088E2B0F6A5B003A254A /* AppNewsMock.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -994,6 +1035,24 @@ shellPath = /bin/sh; shellScript = "# Periphery action to look for dead code\nperiphery scan --strict --workspace OrangeDesignSystemDemo.xcworkspace --schemes OrangeDesignSystemDemo --targets OrangeDesignSystemDemo --format xcode\n"; }; + 51EE08912B0F7464003A254A /* Check JSON files format */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Check JSON files format"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# ------------------------------\n# Check if jq available\n# ------------------------------\n\nif which \"jq\" >/dev/null; then\n # Check if AppNews.json file is a conform JSON file and throw error if not to prevent to build and ship with corrupted file\n AppNewsFileFolder=\"${PROJECT_DIR}/OrangeDesignSystemDemo/Resources/\"\n \n AppNewsFileBase=\"$AppNewsFileFolder/Base.lproj/AppNews.json\"\n if cat \"$AppNewsFileBase\" | jq >/dev/null 2>&1; then\n echo \"AppNews.json (Base) seems good\"\n else\n echo \"error: AppNews.json (Base) is not conform to JSON format\"\n exit 1\n fi\n \n AppNewsFileBase=\"$AppNewsFileFolder/en.lproj/AppNews.json\"\n if cat \"$AppNewsFileBase\" | jq >/dev/null 2>&1; then\n echo \"AppNews.json (en) seems good\"\n else\n echo \"error: AppNews.json (en) is not conform to JSON format\"\n exit 1\n fi\n \n AppNewsFileBase=\"$AppNewsFileFolder/fr.lproj/AppNews.json\"\n if cat \"$AppNewsFileBase\" | jq >/dev/null 2>&1; then\n echo \"AppNews.json (fr) seems good\"\n else\n echo \"error: AppNews.json (fr) is not conform to JSON format\"\n exit 1\n fi\n# ------------------------- \n# jq not available\n# -------------------------\nelse\n echo \"warning: jq not installed. To install run `brew install jq`\" \nfi\n\n"; + }; 59A545B45A1BFD1A7DC83D74 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1082,12 +1141,13 @@ 075326852AD45610003D8832 /* GridOfSmallCards.swift in Sources */, 077C384F2A9DDC79003D6B51 /* IconVariant.swift in Sources */, 077C38522A9DDC79003D6B51 /* SecureVariant.swift in Sources */, + 0786F7422B1649F100299A75 /* FilterChipVariant.swift in Sources */, 077C38642A9DDC79003D6B51 /* CardSmallVariant.swift in Sources */, - 07B94E0E2AD9904900AAD1A5 /* ListItemVaraintSelectionModel.swift in Sources */, 077C38662A9DDC79003D6B51 /* ToolBarVariantOptions.swift in Sources */, 077C38652A9DDC79003D6B51 /* CardComponent.swift in Sources */, 077C385B2A9DDC79003D6B51 /* ProgressBarVariant.swift in Sources */, - 07B94DEB2AD9239700AAD1A5 /* ListItemVariantStandardModel.swift in Sources */, + 07B94DEB2AD9239700AAD1A5 /* ListItemStandardVariantModel.swift in Sources */, + 078AB0552B30A7AA00C592AE /* Variants.swift in Sources */, 077C38862A9DDC79003D6B51 /* AboutModuleModel.swift in Sources */, 077C387C2A9DDC79003D6B51 /* ColorIllustration.swift in Sources */, 077C38782A9DDC79003D6B51 /* ColorsPage.swift in Sources */, @@ -1106,6 +1166,7 @@ 077C38752A9DDC79003D6B51 /* Guideline.swift in Sources */, 077C386A2A9DDC79003D6B51 /* NavigationBarModifiers.swift in Sources */, 077C387B2A9DDC79003D6B51 /* ColorDetail.swift in Sources */, + 07BDEA932B150263005574A2 /* ActionChipsVariant.swift in Sources */, 077C386B2A9DDC79003D6B51 /* NavigationBarVariant.swift in Sources */, 07B94E0D2AD9902A00AAD1A5 /* ListItemSelectionVariant.swift in Sources */, 077C38542A9DDC79003D6B51 /* ListComponent.swift in Sources */, @@ -1128,7 +1189,7 @@ 077C38872A9DDC79003D6B51 /* ModulesList.swift in Sources */, 077C38712A9DDC79003D6B51 /* BottomSheetStandardVariant.swift in Sources */, 077C384B2A9DDC79003D6B51 /* ComponentPage.swift in Sources */, - 077C385F2A9DDC79003D6B51 /* ChipsComponentModel.swift in Sources */, + 072D9EF82B0CA96A00763EAE /* ChoiceChipsVariant.swift in Sources */, 07B94DEA2AD9239700AAD1A5 /* ListItemStandardVariantOptions.swift in Sources */, 077C38772A9DDC79003D6B51 /* GuidelinesList.swift in Sources */, 077C38842A9DDC79003D6B51 /* AboutModule.swift in Sources */, @@ -1145,6 +1206,7 @@ 077C38692A9DDC79003D6B51 /* TabBarComponent.swift in Sources */, 07E4C8692AE7BC8E00FB4DAC /* ListModuleOptions.swift in Sources */, 077C387E2A9DDC79003D6B51 /* ColorsGuideline.swift in Sources */, + 07B543E72B289B2100A2B6ED /* ListItemSelectionVariantModel.swift in Sources */, 077C386F2A9DDC79003D6B51 /* BottomSheetExpandingVariant.swift in Sources */, 077C385C2A9DDC79003D6B51 /* ProgressIndicatorComponent.swift in Sources */, 07B94E0B2AD98D7900AAD1A5 /* ListItemStandardVariant.swift in Sources */, @@ -1153,6 +1215,7 @@ 077C38612A9DDC79003D6B51 /* CardExampleData.swift in Sources */, 077C384D2A9DDC79003D6B51 /* ComponentList.swift in Sources */, 077C38512A9DDC79003D6B51 /* CapitalizedTextInputsVariant.swift in Sources */, + 07B1A09C2B1E191F00ABF0A1 /* InputChipsVariant.swift in Sources */, 077C38632A9DDC79003D6B51 /* CardHorizontalVariant.swift in Sources */, 077C38882A9DDC79003D6B51 /* CardViewDemo.swift in Sources */, 077C387F2A9DDC79003D6B51 /* TypographyGuideline.swift in Sources */, @@ -1171,6 +1234,7 @@ files = ( F99FF08C2767B23E006236A0 /* DateFormatterTests.swift in Sources */, 51F2E6D52AE6AC2B00377A07 /* StringLocalizationTests.swift in Sources */, + 51EE08832B0F5B31003A254A /* AppNewsListViewModelTests.swift in Sources */, 51C776952AE1551600FD0AED /* CacheTests.swift in Sources */, 51C776932AE1512200FD0AED /* DateFormatterCacheTests.swift in Sources */, ); @@ -1399,7 +1463,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.15.0; + MARKETING_VERSION = 0.16.0; PRODUCT_BUNDLE_IDENTIFIER = "soft.cocoa.ods-ios-demo.dev"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1436,7 +1500,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.15.0; + MARKETING_VERSION = 0.16.0; PRODUCT_BUNDLE_IDENTIFIER = "soft.cocoa.ods-ios-demo.dev"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcworkspace/xcshareddata/swiftpm/Package.resolved index 974eee2e..b4ad9402 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,6 +18,15 @@ "revision": "4c9ef84552712e0117c37d4893270fdc28fb9288", "version": "3.1.0" } + }, + { + "package": "Flow", + "repositoryURL": "https://github.com/tevelee/SwiftUI-Flow", + "state": { + "branch": null, + "revision": "76c03c9fa9fa0e5470cb6d8f2da17466b2efd63d", + "version": "1.1.0" + } } ] }, diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Assets.xcassets/Recipes/FoodsAndEntertainment.imageset/Contents.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Assets.xcassets/Recipes/FoodsAndEntertainment.imageset/Contents.json new file mode 100644 index 00000000..fd66334d --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Assets.xcassets/Recipes/FoodsAndEntertainment.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_Beam_angle_110.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Assets.xcassets/Recipes/FoodsAndEntertainment.imageset/ic_Beam_angle_110.svg b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Assets.xcassets/Recipes/FoodsAndEntertainment.imageset/ic_Beam_angle_110.svg new file mode 100644 index 00000000..d858eee9 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Assets.xcassets/Recipes/FoodsAndEntertainment.imageset/ic_Beam_angle_110.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Base.lproj/AppNews.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Base.lproj/AppNews.json index 3e1698df..5f91e030 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Base.lproj/AppNews.json +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Base.lproj/AppNews.json @@ -1,8 +1,13 @@ [ + { + "version": "0.16.0", + "date": "2024-01-15", + "news": "Update typography names.\n Update chips API and design.\nUpdate CardHeaderFirst to get thumbnail from resources or url.\nRefactor About module to manage errors when loading resources.\nFix some accessibility issues." + }, { "version": "0.15.0", "date": "2023-11-14", - "news": "Add internationalization support.\n Add module List in demo application to illustrates header and footer of sections in list and update ListItem api to use SwiftUI elements.\nAdd accessibility statement in About module". + "news": "Add internationalization support.\n Add module List in demo application to illustrates header and footer of sections in list and update ListItem api to use SwiftUI elements.\nAdd accessibility statement in About module" }, { "version": "0.14.0", @@ -43,6 +48,6 @@ "version": "0.10.0", "date": "2023-02-02", "news": "Fix some bugs\n Add Tool bar component" - }, + } ] diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Base.lproj/Localizable.strings b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Base.lproj/Localizable.strings index d96b786b..4f2da56c 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Base.lproj/Localizable.strings +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Base.lproj/Localizable.strings @@ -35,13 +35,15 @@ "shared.trailing" = "Trailing"; "shared.recipes" = "Recipes"; "shared.foods" = "Foods"; +"shared.loading" = "Loading\U2026"; +"shared.bon_app" = "Bon appétit !"; // ===================== // MARK: - Miscellaneous // ===================== "misc.usage" = "Usage"; -"misc.coming_soon" = "Coming soon..."; +"misc.coming_soon" = "Coming soon\U2026"; "misc.variants" = "Variants"; // ================= @@ -189,9 +191,6 @@ "screens.components.buttons.variant.long" = "Terms and conditions"; "screens.components.buttons.options.toggle.full_width" = "Full width"; -"a11y.emphasis_button_hint" = "%@ emphasis button"; -"a11y.functional_button_hint" = "%@ functional button"; - // ==================================== // MARK: - Screens - Components - Cards // ==================================== @@ -217,12 +216,17 @@ "screens.components.chips.title" = "Chips"; "screens.components.chips.description" = "Chips are small components containing a number of elements that represent a calendar event or contact."; -"screens.components.chips.text_only" = "Text only"; -"screens.components.chips.with_icon" = "With icon from image"; -"screens.components.chips.with_system_icon" = "With system icon"; -"screens.components.chips.with_avatar" = "With avatar"; -"screens.components.chips.state.enable" = "Enable"; -"screens.components.chips.state.selected" = "Selected"; +"screens.components.chips.variant.action" = "Action chips"; +"screens.components.chips.action.description" = "The user can initiate actions by tapping on a chip."; +"screens.components.chips.variant.choice" = "Choice chips"; +"screens.components.chips.choice.description" = "The chips allow the user to select only one item within the proposed items."; +"screens.components.chips.variant.filter" = "Filter chips"; +"screens.components.chips.filter.description" = "In a search by tags user can activate the chips upon will."; +"screens.components.chips.variant.input" = "Input chips"; +"screens.components.chips.input.description" = "The user input searches are transformed into chips that can be added or deleted."; +"screens.components.chips.variant.picker" = "Chips picker"; +"screens.components.chips.variant.chip.clicked" = "%@ : clicked"; +"screens.components.chips.variant.input.remove_clicked" = "remove clicked"; // ==================================== // MARK: - Screens - Components - Lists @@ -235,7 +239,6 @@ "screens.components.lists.picker.navigate" = "Navigate"; "screens.components.lists.selection.description.checkmark" = "checkmark"; "screens.components.lists.selection.description.switch" = "Switch"; -"screens.components.lists.variant.clicked" = "%@ is clicked"; "screens.components.lists.alert" = "Information icon tapped! Bon appétit"; "screens.components.lists.options.description.info_button" = "Info button"; "screens.components.list.details" = "Details"; @@ -248,11 +251,11 @@ "screens.components.progress_indicators.description" = "Progress bar and activity indicator are used when a process is taking place to illustrate progress and to reassure users that something is happening, and if possible, how long it will take."; "screens.components.progress_indicators.progress_bar.title" = "Progress bar demo"; "screens.components.progress_indicators.activity_bar.title" = "Progress bar demo"; -"screens.components.progress_indicators.downloading" = "Downloading..."; +"screens.components.progress_indicators.downloading" = "Downloading\U2026"; "screens.components.progress_indicators.x_percent" = "%@ %%"; "screens.components.progress_indicators.toggle.label" = "Label"; "screens.components.progress_indicators.toggle.current_value" = "Current value"; -"screens.components.progress_indicators.toggle.loading" = "Loading..."; +"screens.components.progress_indicators.toggle.loading" = "Loading\U2026"; // ============================================ // MARK: - Screens - Components - Bottom Sheets @@ -262,6 +265,8 @@ "screens.components.bottom_sheets.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."; "screens.components.bottom_sheets.expanding" = "Expanding"; "screens.components.bottom_sheets.standard" = "Standard"; +"screens.components.bottom_sheets.example" = "Example"; +"screens.components.bottom_sheets.tutorial" = "Tutorial"; "screens.components.bottom_sheets.sample.title" = "Sheets: Bottom"; "screens.components.bottom_sheets.sample.subtitle" = "French products"; "screens.components.bottom_sheets.sample.recipe" = "Recipe"; @@ -331,7 +336,6 @@ "screens.modules.card_collections.titles.list" = "List of cards"; "screens.modules.card_collections.titles.grid" = "Grid of small cards"; -"screens.modules.card_collections.texts.bon_app" = "Bon appétit !"; // ================================= // MARK: - Screens - Modules - Lists diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Recipes.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Recipes.json index 6cacde6e..4056370e 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Recipes.json +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/Recipes.json @@ -2,6 +2,7 @@ "recipes": [ { "title": "Summer Salad", + "catId": "2", "subtitle": "20min", "ingredients": [ { @@ -33,12 +34,13 @@ "quantity": "50g" } ], - "description": "A great salad with refreshing ingredients that is reallly adpted to the summer light needs.", + "description": "A great salad with refreshing ingredients, really adapted for summer.", "url": "https://images.unsplash.com/photo-1512621776951-a57141f2eefd?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1740&q=80", "iconName": "Restaurant" }, { - "title": "Brocoli Soup", + "title": "Broccoli Soup", + "catId": "2", "subtitle": "12min", "ingredients": [ { @@ -72,10 +74,11 @@ ], "description": "Make a great cream of broccoli soup in the comfort of your home. It's thick, flavorful, creamy, and easy to make with simple ingredients.", "url": "https://images.unsplash.com/photo-1594756202469-9ff9799b2e4e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=776&q=80", - "iconName": "CookingPot" + "iconName": "Restaurant" }, { - "title": "Pesto farfalle", + "title": "Pesto Farfalle", + "catId": "3", "subtitle": "1h20", "ingredients": [ { @@ -109,6 +112,7 @@ }, { "title": "Fig Sponge Cake", + "catId": "4", "subtitle": "1h20", "ingredients": [ { @@ -146,6 +150,7 @@ }, { "title": "Raspberry Cake", + "catId": "4", "subtitle": "45min", "ingredients": [ { @@ -178,7 +183,8 @@ "iconName": "IceCream" }, { - "title": "Salmon curry", + "title": "Salmon Curry", + "catId": "3", "subtitle": "31min", "ingredients": [ { @@ -216,6 +222,7 @@ }, { "title": "Ham & Mozzarella Pasta", + "catId": "3", "subtitle": "17min", "ingredients": [ { @@ -247,12 +254,13 @@ "quantity": "30ml" } ], - "description": "", + "description": "A wonderful pasta dish that smells like a piece of Italy.", "url": "https://images.unsplash.com/photo-1481931098730-318b6f776db0?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=780&q=80", "iconName": "Restaurant" }, { "title": "Feta Pizza", + "catId": "3", "subtitle": "21min", "ingredients": [ { @@ -272,207 +280,836 @@ "quantity": "5ml" } ], - "description": "", + "description": "A simple revisit of the pizza that makes your ordinary a little more extraordinary.", "url": "https://images.unsplash.com/photo-1593560708920-61dd98c46a4e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80", "iconName": "IceCream" }, { "title": "Fajitas", + "catId": "3", "subtitle": "13min", - "ingredients": [], - "description": "", + "ingredients": [ + ], + "description": "Make your own fajitas and enjoy a sunny Mexican dinner with the rest of the family", "url": "https://images.unsplash.com/photo-1569692062823-f1196218f0a2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=686&q=80", "iconName": "Restaurant" }, { - "title": "Tomato soup", + "title": "Tomato Soup", + "catId": "2", "subtitle": "15min", "ingredients": [], - "description": "", + "description": "After the iconic Andy Warhol revisit of the tin box, make your own revisit of a basic but great simple meal.", "url": "https://images.unsplash.com/photo-1578020190125-f4f7c18bc9cb?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80", + "iconName": "Restaurant" + }, + { + "title": "Virgin Mojito Mocktail", + "catId": "1", + "subtitle": "15min", + "ingredients": [ + { + "foodId": 42, + "quantity": "2 pieces" + }, + { + "foodId": 43, + "quantity": "2 cl" + }, + { + "foodId": 44, + "quantity": "2 branches" + }, + { + "foodId": 41, + "quantity": "12 cl" + }, + { + "foodId": 45, + "quantity": "fill up the glass" + } + ], + "description": "The mojito is one of the world's most popular cocktails, It is originating from Cuba, and this virgin version is the perfect summer drink for the entire family.", + "url": "https://images.unsplash.com/photo-1551538827-9c037cb4f32a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Y29ja3RhaWx8ZW58MHx8MHx8&auto=format&fit=crop&w=500&q=60", + "iconName": "OrangeDetente" + }, + { + "title": "Virgin Sunrise Mocktail", + "catId": "1", + "subtitle": "15min", + "ingredients": [ + { + "foodId": 47, + "quantity": "1 dose" + }, + { + "foodId": 49, + "quantity": "limp" + }, + { + "foodId": 41, + "quantity": "1 dose" + }, + { + "foodId": 46, + "quantity": "full up the glass" + } + ], + "description": "This cocktail, with its bright striations of color, evokes a summer sunrise. This virgin version is very tasty. Note that the original cocktail was created in the early 1970s by Bobby Lozoff and Billy Rice at the Trident bar in Sausalito, California. It was popularized bu the Rolling Stones during their 1972 tour, as the band began ordering it at stops across the US.", + "url": "https://images.pexels.com/photos/8919168/pexels-photo-8919168.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1", + "iconName": "OrangeDetente" + }, + { + "title": "Bella Luna Mocktail", + "catId": "1", + "subtitle": "15min", + "ingredients": [ + { + "foodId": 47, + "quantity": "10 cl" + }, + { + "foodId": 48, + "quantity": "6 cl" + }, + { + "foodId": 42, + "quantity": "1 piece" + } + ], + "description": "A simple Orange based Mocktail very refreshing.", + "url": "https://images.unsplash.com/photo-1595864816539-b186b403f06b?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTh8fGNvY2t0YWlsJTIweWVsbG93fGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=500&q=60", + "iconName": "OrangeDetente" + }, + { + "title": "Virgin Colada Mocktail", + "catId": "1", + "subtitle": "15min", + "ingredients": [ + { + "foodId": 48, + "quantity": "12 cl" + }, + { + "foodId": 29, + "quantity": "4 cl" + }, + { + "foodId": 43, + "quantity": "2g" + } + ], + "description": "A sweet and fresh Mocktail. The original piña colada is a blended or iced cocktail that originated in Puerto Rico. Tails say that Puerto Rican pirate Roberto Cofresí, made this to boost his crew's morale. The name means “strained pineapple” in Spanish, a reference to the freshly pressed and strained pineapple juice used in the drink's preparation.", + "url": "https://images.unsplash.com/photo-1607644536940-6c300b5784c5?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NHx8Y29ja3RhaWwlMjBjb2xhZGF8ZW58MHx8MHx8&auto=format&fit=crop&w=500&q=60", + "iconName": "OrangeDetente" + }, + { + "title": "Tomatoes & Mozzarella di Buffala", + "catId": "2", + "subtitle": "15min", + "ingredients": [ + { + "foodId": 16, + "quantity": "20" + }, + { + "foodId": 34, + "quantity": "2" + }, + { + "foodId": 52, + "quantity": "20" + }, + { + "foodId": 53, + "quantity": "8 leaves" + }, + { + "foodId": 13, + "quantity": "a limp" + } + ], + "description": "A basic marvel from the Italian cuisine, very simple and quick for the summer.", + "url": "https://images.unsplash.com/photo-1580638149300-65f0b9e8fbff?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8cGxhdCUyMGVudHIlQzMlQTllfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=500&q=60", + "iconName": "Restaurant" + }, + { + "title": "Tapenade Bruschetta", + "catId": "2", + "subtitle": "15min", + "ingredients": [ + { + "foodId": 54, + "quantity": "1" + }, + { + "foodId": 13, + "quantity": "3 Table Spoons" + }, + { + "foodId": 1, + "quantity": "5" + }, + { + "foodId": 14, + "quantity": "1" + }, + { + "foodId": 23, + "quantity": "1" + }, + { + "foodId": 53, + "quantity": "3 branches" + }, + { + "foodId": 55, + "quantity": "1 Table Spoon" + }, + { + "foodId": 56, + "quantity": "300 g" + } + ], + "description": "Tapenade is the name for a type of spread that is made primarily out of chopped olives, capers, or anchovies. Originally from the Provence area of France, it’s savory, salty, and briny in flavor. ", + "url": "https://images.unsplash.com/photo-1625938144755-652e08e359b7?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTF8fHBsYXQlMjBlbnRyJUMzJUE5ZXxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=500&q=60", + "iconName": "Restaurant" + }, + { + "title": "Lemon Pie", + "catId": "4", + "subtitle": "15min", + "ingredients": [ + { + "foodId": 38, + "quantity": "1 cup" + }, + { + "foodId": 10, + "quantity": "2 tablespoons" + }, + { + "foodId": 57, + "quantity": "3 tablespoons" + }, + { + "foodId": 37, + "quantity": "1/4 teaspoon" + }, + { + "foodId": 40, + "quantity": "1 1/2 cup" + }, + { + "foodId": 23, + "quantity": "2" + }, + { + "foodId": 9, + "quantity": "2 tablespoons" + }, + { + "foodId": 58, + "quantity": "4" + }, + { + "foodId": 59, + "quantity": "1" + } + ], + "description": "A lemon meringue pie is a fantastic addition to any dessert table. This old-fashioned lemon meringue pie recipe has stood the test of time because it's easy to make, absolutely gorgeous, and totally irresistible.", + "url": "https://images.unsplash.com/photo-1519915028121-7d3463d20b13?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NHx8bGVtb24lMjBwaWV8ZW58MHx8MHx8&auto=format&fit=crop&w=500&q=60", + "iconName": "InformationData" + }, + { + "title": "Brownies", + "catId": "4", + "subtitle": "15min", + "ingredients": [ + { + "foodId": 50, + "quantity": "75 g" + }, + { + "foodId": 9, + "quantity": "80 g" + }, + { + "foodId": 38, + "quantity": "240 g" + }, + { + "foodId": 20, + "quantity": "2" + }, + { + "foodId": 10, + "quantity": "90 g" + }, + { + "foodId": 21, + "quantity": "1/2 tea spoon" + }, + { + "foodId": 51, + "quantity": "1 cup" + } + ], + "description": "An iconic chocolate baked cake that comes in a variety of forms and may be either fudgy or cakey, depending on their density. Brownies often, have a glossy 'skin' on their upper crust. Brownies was a direct request from Bertha Palmer to a pastry chef for a dessert suitable for ladies attending the Chicago World's Columbian Exposition. She requested a cake-like confection smaller than a piece of cake that could be included in boxed lunches.", + "url": "https://images.unsplash.com/photo-1611625358975-06a668e3a45f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mjh8fGJyb3duaWVzfGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=500&q=60", + "iconName": "IceCream" + }, + { + "title": "Chocolate Chips Cookies", + "catId": "4", + "subtitle": "15min", + "ingredients": [ + { + "foodId": 50, + "quantity": "350 g" + }, + { + "foodId": 39, + "quantity": "350 g" + }, + { + "foodId": 9, + "quantity": "250 g" + }, + { + "foodId": 20, + "quantity": "1" + }, + { + "foodId": 10, + "quantity": "375 g" + }, + { + "foodId": 22, + "quantity": "11 g" + }, + { + "foodId": 37, + "quantity": "1 pinch" + }, + { + "foodId": 21, + "quantity": "1/2 bag" + } + ], + "description": "A Cookie Monster's heaven... Cookies are most commonly baked until crisp or else for just long enough to ensure soft interior. The American name derives from the Dutch word 'koekje' or more precisely its informal, dialect variant koekie which means little cake, and arrived in American English with the Dutch settlement of New Netherland, in the early 1600s.", + "url": "https://images.unsplash.com/photo-1499636136210-6f4ee915583e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTF8fGxlJTIwZGVzc2VydHxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=500&q=60", + "iconName": "IceCream" + }, + { + "title": "Red Berries Crêpes", + "catId": "4", + "subtitle": "15min", + "ingredients": [ + { + "foodId": 11, + "quantity": "750 ml" + }, + { + "foodId": 10, + "quantity": "250 g" + }, + { + "foodId": 20, + "quantity": "6" + }, + { + "foodId": 9, + "quantity": "80 g" + }, + { + "foodId": 37, + "quantity": "a pinch" + }, + { + "foodId": 24, + "quantity": "100 g" + }, + { + "foodId": 60, + "quantity": "50 g" + }, + { + "foodId": 61, + "quantity": "1 cup" + }, + { + "foodId": 38, + "quantity": "1 tablespoon" + }, + { + "foodId": 22, + "quantity": "1 teaspoon" + } + ], + "description": "A simple delightment of these French crêpes accompanied with red berries and with this homemade whipped cream that is very easy to make and it holds its shape perfectly.", + "url": "https://images.unsplash.com/photo-1587314168485-3236d6710814?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Nnx8bGUlMjBkZXNzZXJ0fGVufDB8fDB8fA%3D%3D&auto=format&fit=crop&w=500&q=60", + "iconName": "OrangeExpert" + }, + { + "title": "Baguette", + "catId": "5", + "subtitle": "French Bread", + "ingredients": [ + { + "foodId": 10, + "quantity": "420 g" + }, + { + "foodId": 40, + "quantity": "255 ml" + }, + { + "foodId": 21, + "quantity": "11 g" + }, + { + "foodId": 37, + "quantity": "12 g" + } + ], + "description": "There is nothing like a freshly baked French baguette on a Sunday morning. Or any morning for that matter. A delight when crusty and beautifully colored on the outside, buttery soft and chewy on the inside, and with a tiny bit of butter.", + "url": "https://unexisting.com/file/missing/on/purpose.png", + "iconName": "Heart" + }, + { + "title": "Pain de Campagne", + "catId": "5", + "subtitle": "15min", + "ingredients": [ + { + "foodId": 62, + "quantity": "500 g" + }, + { + "foodId": 40, + "quantity": "200 ml" + }, + { + "foodId": 21, + "quantity": "11 g" + }, + { + "foodId": 37, + "quantity": "12 g" + } + ], + "description": "Best bread ever.", + "url": "https://images.unsplash.com/photo-1589367920969-ab8e050bbb04?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cGFpbnxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=500&q=60", + "iconName": "HandUp" + }, + { + "title": "Brioche Tressée", + "catId": "5", + "subtitle": "15min", + "ingredients": [ + { + "foodId": 10, + "quantity": "500 g" + }, + { + "foodId": 11, + "quantity": "180 ml" + }, + { + "foodId": 21, + "quantity": "25 g" + }, + { + "foodId": 37, + "quantity": "8 g" + }, + { + "foodId": 38, + "quantity": "80 g" + }, + { + "foodId": 20, + "quantity": "80 g" + }, + { + "foodId": 9, + "quantity": "100 g" + } + ], + "description": "", + "url": "https://images.unsplash.com/photo-1620416328855-f56e4e98637b?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NjJ8fHBhaW58ZW58MHx8MHx8&auto=format&fit=crop&w=500&q=60", + "iconName": "Tag" + }, + { + "title": "Bagels", + "catId": "5", + "subtitle": "At its most basic, traditional bagel dough contains wheat flour, salt, water, and yeast leavening.", + "ingredients": [ + { + "foodId": 10, + "quantity": "480g" + }, + { + "foodId": 37, + "quantity": "12g" + }, + { + "foodId": 39, + "quantity": "14g" + }, + { + "foodId": 40, + "quantity": "303g" + } + ], + "description": "A bagel (Yiddish: בײגל, romanized: beygl; Polish: bajgiel; also spelled beigel)[1] is a bread roll originating in the Jewish communities of Poland. It is traditionally shaped by hand into a roughly hand-sized ring from yeasted wheat dough that is first boiled for a short time in water and then baked. The result is a dense, chewy, doughy interior with a browned and sometimes crisp exterior. Bagels are often topped with seeds baked on the outer crust—traditional choices include poppy and sesame seeds—or with salt grains. Different dough types include whole-grain and rye.[3][4] The basic roll-with-a-hole design, hundreds of years old, allows even cooking and baking of the dough; it also allows groups of bagels to be gathered on a string or dowel for handling, transportation, and retail display.", + "url": "https://www.seekpng.com/png/full/199-1993606_royalty-free-stock-bagel-transparent-french-toast-maple.png", + "iconName": "Bagel-does-not-exist" + } + ], + "category": [ + { + "id": 1, + "name": "Drinks", + "iconName": "OrangeDetente" + }, + { + "id": 2, + "name": "Starter", + "iconName": "Food_and_Entertainment" + }, + { + "id": 3, + "name": "Main course", "iconName": "CookingPot" + }, + { + "id": 4, + "name": "Desert", + "iconName": "IceCream" + }, + { + "id": 5, + "name": "Bread", + "iconName": "Cafe" + }, + { + "id": 6, + "name": "Space food", + "iconName": "medicine" } ], "foods": [ { "id": 1, - "name": "tomato", + "name": "Tomato", "image": "https://images.unsplash.com/photo-1564874997803-e4d589d5fd41?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80" }, { "id": 2, - "name": "advocado", + "name": "Advocado", "image": "https://images.unsplash.com/photo-1519162808019-7de1683fa2ad?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1075&q=80" }, { "id": 3, - "name": "yellow pepper", + "name": "Yellow pepper", "image": "https://images.unsplash.com/photo-1563565375-f3fdfdbefa83?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=764&q=80" }, { "id": 4, - "name": "chickpea", - "image": "https://images.unsplash.com/photo-1644432757699-bb5a01e8fb0e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1074&q=80" + "name": "Chickpea", + "image": "https://images.pexels.com/photos/7717474/pexels-photo-7717474.jpeg?auto=compress&cs=tinysrgb&w=1600" }, { "id": 5, - "name": "red cabbage", - "image": "" + "name": "Red cabbage", + "image": "https://images.pexels.com/photos/5876007/pexels-photo-5876007.jpeg?auto=compress&cs=tinysrgb&w=1600" }, { "id": 6, - "name": "sweet potato", + "name": "Sweet potato", "image": "https://images.unsplash.com/photo-1617130094141-532436117aa1?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=880&q=80" }, { "id": 7, - "name": "watercress", + "name": "Watercress", "image": "https://images.unsplash.com/photo-1622463214111-b192a53371d2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1152&q=80" }, { "id": 8, - "name": "brocoli", + "name": "Brocoli", "image": "https://images.unsplash.com/photo-1615485291234-9d694218aeb3?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1074&q=80" }, { "id": 9, - "name": "butter", - "image": "https://images.unsplash.com/photo-1589985270826-4b7bb135bc9d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" + "name": "Butter", + "image": "https://media.istockphoto.com/id/177834117/es/foto/mantequilla-aislado-en-blanco.jpg?s=612x612&w=0&k=20&c=3OFv5OG4FIiWHDz9kycyKX16izAYZBArwPy4Wr4IDOM=" }, { "id": 10, - "name": "flour", - "image": "https://images.unsplash.com/photo-1610725664285-7c57e6eeac3f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80" + "name": "Flour", + "image": "https://media.istockphoto.com/photos/flour-picture-id535492963?b=1&k=20&m=535492963&s=612x612&w=0&h=LI-5F7rjhpRZYso3wbgb_-aceyotVm6nC_SEf8u_OD0=" }, { "id": 11, - "name": "milk", + "name": "Milk", "image": "https://images.unsplash.com/photo-1588710929895-6ee7a0a4d155?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1171&q=80" }, { "id": 12, - "name": "parsley", + "name": "Parsley", "image": "https://images.unsplash.com/photo-1583116935690-eb4893c5de84?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=880&q=80" }, { "id": 13, - "name": "olive oil", - "image": "https://images.unsplash.com/photo-1552592074-ea7a91b851b3?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1964&q=80" + "name": "Olive oil", + "image": "https://images.pexels.com/photos/1022385/pexels-photo-1022385.jpeg?auto=compress&cs=tinysrgb&w=1600" }, { "id": 14, - "name": "garlic", + "name": "Garlic", "image": "https://images.unsplash.com/photo-1587049332298-1c42e83937a7?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=880&q=80" }, { "id": 15, - "name": "basil", - "image": "https://images.unsplash.com/photo-1536777206078-5e694d16c678?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" + "name": "Basil", + "image": "https://media.istockphoto.com/photos/basil-leafs-picture-id174544785?b=1&k=20&m=174544785&s=612x612&w=0&h=Lbsbk5CROJfBqnDXS-eOOvNMPSULRU23bwSlFGX8Bjo=" }, { "id": 16, - "name": "cherry tomato", + "name": "Cherry tomato", "image": "https://images.unsplash.com/photo-1587411768515-eeac0647deed?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=880&q=80" }, { "id": 17, - "name": "parmesan", - "image": "https://images.unsplash.com/photo-1589881133595-a3c085cb731d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=765&q=80" + "name": "Parmesan", + "image": "https://media.istockphoto.com/id/1136203798/es/foto/queso-parmesano-aislado-sobre-fondo-blanco.jpg?s=612x612&w=0&k=20&c=3cijmSnWUcTL7d3sMcrM_TVq5snh494I3d0WXhpcPQw=" }, { "id": 18, - "name": "honey", + "name": "Honey", "image": "https://images.unsplash.com/photo-1625600243103-1dc6824c6c8a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=764&q=80" }, { "id": 19, - "name": "figs", + "name": "Figs", "image": "https://images.unsplash.com/photo-1601379760591-1d89ae6ee1b7?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80" }, { "id": 20, - "name": "eggs", + "name": "Eggs", "image": "https://images.unsplash.com/photo-1587486913049-53fc88980cfc?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=880&q=80" }, { "id": 21, - "name": "baking soda", - "image": "https://images.unsplash.com/photo-1638405803126-d12de49c7d47?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" + "name": "Baking soda", + "image": "https://media.istockphoto.com/id/473217706/fr/photo/cuill%C3%A8re-en-bois-avec-du-sel.jpg?s=612x612&w=0&k=20&c=uPAEOUXFLruMMe1bfX8WxwUAl4q3cfjN0-6twtSLBxg=" }, { "id": 22, - "name": "vanilla", + "name": "Vanilla", "image": "https://images.unsplash.com/photo-1610487512810-b614ad747572?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1164&q=80" }, { "id": 23, - "name": "lemon", + "name": "Lemon", "image": "https://images.unsplash.com/photo-1582287104445-6754664dbdb2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" }, { "id": 24, - "name": "raspberries", + "name": "Raspberries", "image": "https://images.unsplash.com/photo-1615484477676-c6f3c18e8462?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=880&q=80" }, { "id": 25, - "name": "salmon", - "image": "https://images.unsplash.com/photo-1599084993091-1cb5c0721cc6?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" + "name": "Salmon", + "image": "https://media.istockphoto.com/id/157641208/fr/photo/saumon.jpg?s=612x612&w=0&k=20&c=JsoJv4uy2IsDpuLKtOK-j2_jMr_MosEEaLvqIsNPErI=" }, { "id": 26, - "name": "oil", + "name": "Oil", "image": "https://images.unsplash.com/photo-1552592074-ea7a91b851b3?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=764&q=80" }, { "id": 27, - "name": "curry", - "image": "" + "name": "Curry", + "image": "https://media.istockphoto.com/id/185296258/es/foto/amarillo-spice.jpg?s=612x612&w=0&k=20&c=anc8Iee-Vr2-kI1CQ9mGstd6i-UJAjOjEWfgtwiMNLU=" }, { "id": 28, - "name": "onion", + "name": "Onion", "image": "https://images.unsplash.com/photo-1587049633312-d628ae50a8ae?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=880&q=80" }, { "id": 29, - "name": "coconut milk", + "name": "Coconut cream", "image": "https://images.unsplash.com/photo-1588413336022-43f5326d33b4?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80" }, { "id": 30, - "name": "coriander", - "image": "https://images.unsplash.com/photo-1588879460618-9249e7d947d1?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" + "name": "Coriander", + "image": "https://media.istockphoto.com/id/624698704/es/foto/manojo-de-perejil.jpg?s=612x612&w=0&k=20&c=YqNTrNpY5ftMkFsrKxVm932IuXWeuWM7aTxz18KaPR0=" }, { "id": 31, - "name": "spagetti", - "image": "https://images.unsplash.com/photo-1556910110-a5a63dfd393c?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80" + "name": "Spagetti", + "image": "https://media.istockphoto.com/id/1096157720/es/foto/secas-pastas-spaghetti.jpg?s=612x612&w=0&k=20&c=hauFhc3-rp9H0lVI5fiCGkrK_g4ncmGtmBY4QkdoCGE=" }, { "id": 32, - "name": "peas", - "image": "https://images.unsplash.com/photo-1592394533824-9440e5d68530?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1074&q=80" + "name": "Peas", + "image": "https://media.istockphoto.com/id/1133566291/es/foto/guisantes-verdes-sobre-fondo-blanco.jpg?s=612x612&w=0&k=20&c=B57b41Z-AylzQPdT8bDCUuvjnmSKWPdIMJEAXEKcei4=" }, { "id": 33, - "name": "ham", - "image": "https://images.unsplash.com/photo-1609518317991-10acee259279?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1025&q=80" + "name": "Ham", + "image": "https://media.istockphoto.com/id/518953076/es/foto/horneados-rebanada-de-jam%C3%B3n.jpg?s=612x612&w=0&k=20&c=3XH_ZgUBZCRAlJEunr9mvenocCxHXRE9l7VrTAxF8iM=" }, { "id": 34, - "name": "mozzarella", - "image": "https://images.unsplash.com/photo-1633253037246-12bb11ff545a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" + "name": "Mozzarella", + "image": "https://media.istockphoto.com/id/1341216531/es/foto/mozzarella-de-b%C3%BAfala-italiana-aislada-sobre-fondo-blanco.jpg?s=612x612&w=0&k=20&c=GUywj6PapNtXvo47FTtUpHyB-FN2EBrkredOHuzEbd0=" }, { "id": 35, - "name": "spinash leaves", - "image": "https://images.unsplash.com/photo-1574316071802-0d684efa7bf5?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=735&q=80" + "name": "Spinash leaves", + "image": "https://media.istockphoto.com/id/1216176567/fr/photo/spinash-isol%C3%A9.jpg?s=612x612&w=0&k=20&c=m7aqUBRbHI8ePDPxoi7--MtWLJvNMpzWRZBlxv47uUI=" }, { "id": 36, - "name": "feta", - "image": "https://images.unsplash.com/photo-1661349008073-136bed6e6788?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80" + "name": "Feta", + "image": "https://media.istockphoto.com/id/626516364/fr/photo/f%C3%A9ta.jpg?s=612x612&w=0&k=20&c=ObpAT2rZbHgwbcudPU6jMeltGDmHB-5EaGxOy7-vL68=" + }, + { + "id": 37, + "name": "Salt", + "image": "https://media.istockphoto.com/id/668551038/es/foto/sal-marina.jpg?s=612x612&w=0&k=20&c=2fzrFySOFc0Fc9i-Rq-3iUjQh5JBaItgjzoo8spnbbs=" + }, + { + "id": 38, + "name": "Sugar", + "image": "https://media.istockphoto.com/id/499842394/es/foto/de-az%C3%BAcar.jpg?s=612x612&w=0&k=20&c=khz4jN_iqwRhhOzFJa0AGZbEb1ktFgMHmtjhvFygs80=" + }, + { + "id": 39, + "name": "Brown sugar", + "image": "https://media.istockphoto.com/id/104676536/es/foto/ca%C3%B1a-de-az%C3%BAcar-hill-aislado-en-blanco.jpg?s=612x612&w=0&k=20&c=XeJv77eRb7HBzwC7SPd5_aQwl3dOuOpMAXmxs4PIzAc=" + }, + { + "id": 40, + "name": "Water", + "image": "https://media.istockphoto.com/id/485685046/es/foto/vaso-de-agua.jpg?s=612x612&w=0&k=20&c=okNxhczP-wl_5-_Fo3RnFz2vsb9VSucIuE5Kq4PmGr4=" + }, + { + "id": 41, + "name": "Sparkling water", + "image": "https://media.istockphoto.com/id/181957643/es/foto/soda-agua.jpg?s=612x612&w=0&k=20&c=ci6ZURhAPD3FxyfA1peu5mRCW8T3IX0ugCEWee1UFfM=" + }, + { + "id": 42, + "name": "Lime", + "image": "https://media.istockphoto.com/id/106491732/es/foto/lim%C3%B3n.jpg?s=612x612&w=0&k=20&c=Wv_WKA9VnujGkli4qWjiQGjou9PhXYcWcL8BtKrOhfw=" + }, + { + "id": 43, + "name": "Sugar cane Syrup", + "image": "https://media.istockphoto.com/id/175438109/es/foto/estrellas-de-cuchara-de-miel.jpg?s=612x612&w=0&k=20&c=2MFpjH-5Kb2P-mYMRrRoIPwLjt3CkDqqYm8wrLyXojY=" + }, + { + "id": 44, + "name": "Mint", + "image": "https://media.istockphoto.com/id/183754081/es/foto/mint.jpg?s=612x612&w=0&k=20&c=mt3p6aITKQV1vnt31bT6VIuNBebSmISjepzntbC-jvk=" + }, + { + "id": 45, + "name": "Crunched ice", + "image": "https://media.istockphoto.com/id/500399462/es/foto/hielo-picado-en-cubo.jpg?s=612x612&w=0&k=20&c=a8KSq4WkTAbVsDiYFKn3aHWwaZqCVOeQeBpQVrO3nI8=" + }, + { + "id": 46, + "name": "Ice cubes", + "image": "https://media.istockphoto.com/id/177131518/es/foto/cubos-de-hielo.jpg?s=612x612&w=0&k=20&c=71wDtnaGnZJVXV9NFsU2wl9H6AGbzICa2uiufEUv8nY=" + }, + { + "id": 47, + "name": "Orange juice", + "image": "https://media.istockphoto.com/id/175022686/es/foto/jugo-fresco-de-naranja.jpg?s=612x612&w=0&k=20&c=NmwQZgjQwm0EXufK78filJaW-oUYEu0dzEc7OLCyLxY=" + }, + { + "id": 48, + "name": "Pineapple juice", + "image": "https://media.istockphoto.com/id/176851361/es/foto/jugo-de-pi%C3%B1a.jpg?s=612x612&w=0&k=20&c=SNOvEr2rlyW7dfCnlvy5NQWfo7QOsHik2qKClCWiyQw=" + }, + { + "id": 49, + "name": "Grenadine", + "image": "https://media.istockphoto.com/id/475373289/es/foto/semillas-de-granada.jpg?s=612x612&w=0&k=20&c=06oAwzqd7-gRTPRnGR3AJfyP3GWZzx6SLzmoJlO3x8Q=" + }, + { + "id": 50, + "name": "Chocolate", + "image": "https://media.istockphoto.com/id/924850604/es/foto/tres-piezas-de-chocolate-con-leche.jpg?s=612x612&w=0&k=20&c=c46gdGCxMSYXf0OvF1sXv_EogHo5AAOIJNkY_dD6hTU=" + }, + { + "id": 51, + "name": "Walnuts", + "image": "https://media.istockphoto.com/id/639478614/fr/photo/walnut-seul-sur-fond-blanc-avec-trait-de-coupe.jpg?s=612x612&w=0&k=20&c=CQb72_wmJMqmpE3jqry_MEl5AWlDozQGzPnaf6PCvgI=" + }, + { + "id": 52, + "name": "Yellow tomato", + "image": "https://media.istockphoto.com/id/638646596/photo/tomatoes.jpg?s=612x612&w=0&k=20&c=n68zxSwjJMEW1Kc3xGDVEfvbXb7Ag4qvkj-bXBG77OE=" + }, + { + "id": 53, + "name": "Basil", + "image": "https://media.istockphoto.com/id/182173388/photo/basil-leafs.jpg?s=612x612&w=0&k=20&c=JPooP0Fjbc32DGrxvs9DZDX2nVEaTKrfrcR4n-_L66g=" + }, + { + "id": 54, + "name": "Baguette", + "image": "https://media.istockphoto.com/id/537246627/photo/wall.jpg?s=612x612&w=0&k=20&c=buK9BzxJNbAqvAdAQgzukOeWA8wiEBA5g9xEHpP22H4=" + }, + { + "id": 55, + "name": "Balsamic vinegar", + "image": "https://media.istockphoto.com/id/117489777/photo/balsamic-vinegar.jpg?s=612x612&w=0&k=20&c=DUQq-u3LiZwMA2XvrOXy5rfxJ1Z7hqgQumC-K_-VXsw=" + }, + { + "id": 56, + "name": "Olives", + "image": "https://media.istockphoto.com/id/97863822/photo/antipasti-olives-isolated-iii.jpg?s=612x612&w=0&k=20&c=0uUyRU3ZTZnZSgWhPcULQpDOSGrib-eKbO70Jt5Ru7Y=" + }, + { + "id": 57, + "name": "Cornstarch", + "image": "https://media.istockphoto.com/id/1207945185/photo/petri-dish-with-corn-starch-and-yellow-kernels-on-a-white-background-closeup-of-tapioca.jpg?s=612x612&w=0&k=20&c=dHobmfoGPUJJbPz0x8M9OB5z4OPwznOwPgUyjko-FCA=" + }, + { + "id": 58, + "name": "Egg yolks", + "image": "https://media.istockphoto.com/id/165160671/photo/brown-eggs.jpg?s=612x612&w=0&k=20&c=6w6AdAlBwmk9dMrkqdhxPtRpe51Sfz-WW1bJl-47bC0=" + }, + { + "id": 59, + "name": "Pie crust", + "image": "https://media.istockphoto.com/id/1007686728/photo/rolling-a-pastry-for-baking-a-pie-and-pricking-it-with-fork-recipe-concept.jpg?s=612x612&w=0&k=20&c=UBCe7na3L4mw_j-T5yaaXxqI5Pa33StQkfDuEYP0Ezg=" + }, + { + "id": 60, + "name": "Strawberries", + "image": "https://media.istockphoto.com/id/471674664/photo/two-strawberries-isolated-on-white-background.jpg?s=612x612&w=0&k=20&c=a5g5o0t4M5qGInHo4gKNa07TRdz0HHzZ31UmGW9HuBk=" + }, + { + "id": 61, + "name": "Heavy Cream", + "image": "https://media.istockphoto.com/id/478216750/photo/sour-cream.jpg?s=612x612&w=0&k=20&c=N029hQO6Rvb8onl-iQfOlu4wX3HmfnY6GwGGT9JXHk4=" + }, + { + "id": 62, + "name": "Wholemeal flour", + "image": "https://media.istockphoto.com/id/672050170/photo/rye-flour.jpg?s=612x612&w=0&k=20&c=A1Xo8sPiiFCGn4lUC2Z337HEleOIJmgDFlzOAjaXchk=" } ] } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/en.lproj/Localizable.strings b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/en.lproj/Localizable.strings index 834efb9b..640dc501 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/en.lproj/Localizable.strings +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/en.lproj/Localizable.strings @@ -35,13 +35,15 @@ "shared.trailing" = "Trailing"; "shared.recipes" = "Recipes"; "shared.foods" = "Foods"; +"shared.loading" = "Loading\U2026"; +"shared.bon_app" = "Bon appétit !"; // ===================== // MARK: - Miscellaneous // ===================== "misc.usage" = "Usage"; -"misc.coming_soon" = "Coming soon..."; +"misc.coming_soon" = "Coming soon\U2026"; "misc.variants" = "Variants"; // ================= @@ -189,9 +191,6 @@ "screens.components.buttons.variant.long" = "Terms and conditions"; "screens.components.buttons.options.toggle.full_width" = "Full width"; -"a11y.emphasis_button_hint" = "%@ emphasis button"; -"a11y.functional_button_hint" = "%@ functional button"; - // ==================================== // MARK: - Screens - Components - Cards // ==================================== @@ -217,12 +216,18 @@ "screens.components.chips.title" = "Chips"; "screens.components.chips.description" = "Chips are small components containing a number of elements that represent a calendar event or contact."; -"screens.components.chips.text_only" = "Text only"; -"screens.components.chips.with_icon" = "With icon from image"; -"screens.components.chips.with_system_icon" = "With system icon"; -"screens.components.chips.with_avatar" = "With avatar"; -"screens.components.chips.state.enable" = "Enable"; -"screens.components.chips.state.selected" = "Selected"; +"screens.components.chips.variant.action" = "Action chips"; +"screens.components.chips.action.description" = "The user can initiate actions by tapping on a chip."; +"screens.components.chips.variant.choice" = "Choice chips"; +"screens.components.chips.choice.description" = "The chips allow the user to select only one item within the proposed items."; +"screens.components.chips.variant.filter" = "Filter chips"; +"screens.components.chips.filter.description" = "In a search by tags user can activate the chips upon will."; +"screens.components.chips.variant.input" = "Input chips"; +"screens.components.chips.input.description" = "The user input searches are transformed into chips that can be added or deleted."; +"screens.components.chips.variant.picker" = "Chips picker"; +"screens.components.chips.variant.chip.clicked" = "%@ : clicked"; +"screens.components.chips.variant.input.remove_clicked" = "remove clicked"; + // ==================================== // MARK: - Screens - Components - Lists @@ -235,7 +240,6 @@ "screens.components.lists.picker.navigate" = "Navigate"; "screens.components.lists.selection.description.checkmark" = "checkmark"; "screens.components.lists.selection.description.switch" = "Switch"; -"screens.components.lists.variant.clicked" = "%@ is clicked"; "screens.components.lists.alert" = "Information icon tapped! Bon appétit"; "screens.components.lists.options.description.info_button" = "Info button"; "screens.components.list.details" = "Details"; @@ -248,11 +252,11 @@ "screens.components.progress_indicators.description" = "Progress bar and activity indicator are used when a process is taking place to illustrate progress and to reassure users that something is happening, and if possible, how long it will take."; "screens.components.progress_indicators.progress_bar.title" = "Progress bar demo"; "screens.components.progress_indicators.activity_bar.title" = "Progress bar demo"; -"screens.components.progress_indicators.downloading" = "Downloading..."; +"screens.components.progress_indicators.downloading" = "Downloading\U2026"; "screens.components.progress_indicators.x_percent" = "%@ %%"; "screens.components.progress_indicators.toggle.label" = "Label"; "screens.components.progress_indicators.toggle.current_value" = "Current value"; -"screens.components.progress_indicators.toggle.loading" = "Loading..."; +"screens.components.progress_indicators.toggle.loading" = "Loading\U2026"; // ============================================ // MARK: - Screens - Components - Bottom Sheets @@ -262,6 +266,8 @@ "screens.components.bottom_sheets.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."; "screens.components.bottom_sheets.expanding" = "Expanding"; "screens.components.bottom_sheets.standard" = "Standard"; +"screens.components.bottom_sheets.example" = "Example"; +"screens.components.bottom_sheets.tutorial" = "Tutorial"; "screens.components.bottom_sheets.sample.title" = "Sheets: Bottom"; "screens.components.bottom_sheets.sample.subtitle" = "French products"; "screens.components.bottom_sheets.sample.recipe" = "Recipe"; @@ -331,7 +337,6 @@ "screens.modules.card_collections.titles.list" = "List of cards"; "screens.modules.card_collections.titles.grid" = "Grid of small cards"; -"screens.modules.card_collections.texts.bon_app" = "Bon appétit !"; // ================================= // MARK: - Screens - Modules - Lists diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/fr.lproj/Localizable.strings b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/fr.lproj/Localizable.strings index f855058e..d3f9b04f 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/fr.lproj/Localizable.strings +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Resources/fr.lproj/Localizable.strings @@ -35,13 +35,15 @@ "shared.trailing" = "À la fin"; "shared.recipes" = "Recettes"; "shared.foods" = "Nourriture"; +"shared.loading" = "Chargement\U2026"; +"shared.bon_app" = "Bon appétit !"; // ===================== // MARK: - Miscellaneous // ===================== "misc.usage" = "Usage"; -"misc.coming_soon" = "À venir..."; +"misc.coming_soon" = "À venir\U2026"; "misc.variants" = "Déclinaisons"; // ================= @@ -189,9 +191,6 @@ "screens.components.buttons.variant.long" = "Termes et conditions"; "screens.components.buttons.options.toggle.full_width" = "Pleine largeur"; -"a11y.emphasis_button_hint" = "%@ bouton d'emphase"; -"a11y.functional_button_hint" = "%@ bouton fonctionnel"; - // ==================================== // MARK: - Screens - Components - Cards // ==================================== @@ -217,12 +216,17 @@ "screens.components.chips.title" = "Puces"; "screens.components.chips.description" = "Les puces sont de petits composants contenant un certain nombre d'éléments qui représentent un événement de calendrier ou un contact."; -"screens.components.chips.text_only" = "Texte seul"; -"screens.components.chips.with_icon" = "Avec une icône depuis une image"; -"screens.components.chips.with_system_icon" = "Avec une icône système"; -"screens.components.chips.with_avatar" = "Avec un avatar"; /* Dernier maître de l'air ou créature à la peau bleue */ -"screens.components.chips.state.enable" = "Activer"; -"screens.components.chips.state.selected" = "Selectionné"; +"screens.components.chips.variant.action" = "Action chips"; +"screens.components.chips.action.description" = "L'utilisateur peut effectuer une action en cliquant sur le chip."; +"screens.components.chips.variant.choice" = "Choice chips"; +"screens.components.chips.choice.description" = "Les chips permettent à l'utilisateur de selectionner un element parmis ceux proposés"; +"screens.components.chips.variant.filter" = "Filter chips"; +"screens.components.chips.filter.description" = "Lors d'une recherche par tags, l'utilisateur peut activer les chips souhaités."; +"screens.components.chips.variant.input" = "Input chips"; +"screens.components.chips.input.description" = "Les recherches saisies par l'utilisateur sont transformées en chips qui peuvent être ajoutées ou supprimées."; +"screens.components.chips.variant.picker" = "Chips picker"; +"screens.components.chips.variant.chip.clicked" = "%@ : cliqué"; +"screens.components.chips.variant.input.remove_clicked" = "suppression cliqué"; // ==================================== // MARK: - Screens - Components - Lists @@ -235,7 +239,6 @@ "screens.components.lists.picker.navigate" = "Naviguer"; "screens.components.lists.selection.description.checkmark" = "Encoche"; "screens.components.lists.selection.description.switch" = "Commutateur"; -"screens.components.lists.variant.clicked" = "%@ est tappé"; "screens.components.lists.alert" = "Icône d'information tappée ! Bon appétit"; "screens.components.lists.options.description.info_button" = "Bouton d'information"; "screens.components.list.details" = "Détails"; @@ -248,11 +251,11 @@ "screens.components.progress_indicators.description" = "Les indicateurs de progression indiquent aux utilisateurs que des éléments ou des pages sont en cours de chargement."; "screens.components.progress_indicators.progress_bar.title" = "Barre de progression"; "screens.components.progress_indicators.activity_bar.title" = "Barre d'activité"; -"screens.components.progress_indicators.downloading" = "Téléchargement en cours..."; +"screens.components.progress_indicators.downloading" = "Téléchargement en cours\U2026"; "screens.components.progress_indicators.x_percent" = "%@ %%"; "screens.components.progress_indicators.toggle.label" = "Label"; "screens.components.progress_indicators.toggle.current_value" = "Valeur actuelle"; -"screens.components.progress_indicators.toggle.loading" = "Chargement..."; +"screens.components.progress_indicators.toggle.loading" = "Chargement\U2026"; // ============================================ // MARK: - Screens - Components - Bottom Sheets @@ -262,6 +265,8 @@ "screens.components.bottom_sheets.description" = "Par défaut, une page est modale, présentant une expérience ciblée qui empêche les utilisateurs d'interagir avec la vue parente, jusqu'à ce qu'ils ferment la page. Une page modale est utile pour demander une information spécifique ou activer une tâche simple."; "screens.components.bottom_sheets.expanding" = "Étirable"; "screens.components.bottom_sheets.standard" = "Standard"; +"screens.components.bottom_sheets.example" = "Exemple"; +"screens.components.bottom_sheets.tutorial" = "Tutoriel"; "screens.components.bottom_sheets.sample.title" = "Pages inférieures"; "screens.components.bottom_sheets.sample.subtitle" = "Produits français"; "screens.components.bottom_sheets.sample.recipe" = "Recette"; @@ -331,7 +336,6 @@ "screens.modules.card_collections.titles.list" = "Liste de cartes"; "screens.modules.card_collections.titles.grid" = "Grille de petites cartes"; -"screens.modules.card_collections.texts.bon_app" = "Bon appétit !"; // ================================= // MARK: - Screens - Modules - Lists diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/ComponentList.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/ComponentList.swift index 0ab13940..2de30abc 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/ComponentList.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/ComponentList.swift @@ -37,11 +37,11 @@ struct ComponentsList: View { ProgressIndicatorComponent(), SliderComponent(), TabBarComponent(), - ToolBarComponent(), TextFieldComponent(), + ToolBarComponent(), ] - self.components = components.sorted { $0.title < $1.title } + self.components = components.sorted { $0.name < $1.name } } // ========== @@ -52,7 +52,7 @@ struct ComponentsList: View { NavigationView { ScrollView { LazyVGrid(columns: columns, spacing: ODSSpacing.none) { - ForEach(components, id: \.title) { component in + ForEach(components, id: \.id) { component in smallCard(for: component) } } @@ -63,6 +63,7 @@ struct ComponentsList: View { ComponentPage(component: components[0]) } + .navigationViewStyle(.stack) } // ===================== @@ -74,7 +75,7 @@ struct ComponentsList: View { ComponentPage(component: component) } label: { ODSCardSmall( - title: Text(component.title), + title: Text(component.name), imageSource: .image(themeProvider.imageFromResources(component.imageName))) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerComponent.swift index a17a0f4e..6c844d80 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerComponent.swift @@ -10,13 +10,13 @@ import OrangeDesignSystem import SwiftUI struct BannerComponent: Component { - let title: String + let name: String let imageName: String let description: String let variants: AnyView init() { - title = °°"screens.components.banners.title" + name = °°"screens.components.banners.title" imageName = "Banners" description = °°"screens.components.banners.description" variants = AnyView(BannerVariants()) @@ -26,7 +26,7 @@ struct BannerComponent: Component { struct BannerVariants: View { var body: some View { - VariantEntryItem(title: °°"screens.components.banners.variant.title_demo", technicalElement: "ODSBanner()") { + VariantEntryItem(title: "screens.components.banners.variant.title_demo", technicalElement: "ODSBanner()") { BannerVariant(model: BannerVariantModel()) .navigationTitle("screens.components.banners.variant.title") } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerVariantOptions.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerVariantOptions.swift index c3084907..a002dbde 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerVariantOptions.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Banners/BannerVariantOptions.swift @@ -9,7 +9,7 @@ import OrangeDesignSystem import SwiftUI -class BannerVariantModel: ObservableObject { +final class BannerVariantModel: ObservableObject { // ======================= // MARK: Stored Properties @@ -86,7 +86,7 @@ struct BannerVariantOptions: View { value: $model.buttonCount, in: 0 ... model.buttonsText.count) } - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .padding(.horizontal, ODSSpacing.m) .padding(.vertical, ODSSpacing.m) } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/BottomSheetComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/BottomSheetComponent.swift index d92cd04b..585b4900 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/BottomSheetComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/BottomSheetComponent.swift @@ -11,13 +11,13 @@ import OrangeDesignSystem import SwiftUI struct BottomSheetComponent: Component { - let title: String + let name: String let imageName: String let description: String let variants: AnyView init() { - title = °°"screens.components.bottom_sheets.title" + name = °°"screens.components.bottom_sheets.title" imageName = "BottomSheet" description = °°"screens.components.bottom_sheets.description" variants = AnyView(BottomSheetVariants()) @@ -27,12 +27,12 @@ struct BottomSheetComponent: Component { struct BottomSheetVariants: View { var body: some View { - VariantEntryItem(title: °°"screens.components.bottom_sheets.expanding", technicalElement: ".odsBottomSheetExpanding()") { + VariantEntryItem(title: "screens.components.bottom_sheets.expanding", technicalElement: ".odsBottomSheetExpanding()") { ExpandingBottomSheetVariantHome(model: BottomSheetVariantModel()) .navigationTitle("screens.components.bottom_sheets.expanding") } - VariantEntryItem(title: °°"screens.components.bottom_sheets.standard", technicalElement: ".odsBottomSheetStandard()") { + VariantEntryItem(title: "screens.components.bottom_sheets.standard", technicalElement: ".odsBottomSheetStandard()") { StandardBottomSheetVariant() .navigationTitle("screens.components.bottom_sheets.standard") } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift index 1b55225e..df9b7a2c 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/BottomSheet/Expanding/BottomSheetExpandingVariantOptions.swift @@ -9,7 +9,7 @@ import OrangeDesignSystem import SwiftUI -class BottomSheetVariantModel: ObservableObject { +final class BottomSheetVariantModel: ObservableObject { // ====================== // MARK: Store properties @@ -150,25 +150,25 @@ struct ExpandingBottomSheetVariantOptions: View { var body: some View { VStack(spacing: ODSSpacing.m) { - Group { - ODSChipPicker(title: °°"screens.components.bottom_sheets.variant.detent", - selection: $model.bottomSheetSize, - chips: ODSBottomSheetSize.chips) + ODSChoiceChipPicker( + title: Text("screens.components.bottom_sheets.variant.detent"), + chips: ODSBottomSheetSize.chips, + selection: $model.bottomSheetSize) - ODSChipPicker(title: °°"screens.components.bottom_sheets.variant.content", - selection: $model.contentType, - chips: ContentType.chips) + ODSChoiceChipPicker( + title: Text("screens.components.bottom_sheets.variant.content"), + chips: ContentType.chips, + selection: $model.contentType) - Toggle("shared.subtitle", isOn: $model.showSubtitle) - .padding(.horizontal, ODSSpacing.m) - .disabled(model.showIcon) + Toggle("shared.subtitle", isOn: $model.showSubtitle) + .padding(.horizontal, ODSSpacing.m) + .disabled(model.showIcon) - Toggle("shared.icon", isOn: $model.showIcon) - .padding(.horizontal, ODSSpacing.m) - .disabled(model.showSubtitle) - } + Toggle("shared.icon", isOn: $model.showIcon) + .padding(.horizontal, ODSSpacing.m) + .disabled(model.showSubtitle) } - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) } } @@ -178,36 +178,43 @@ enum ContentType: String, CaseIterable { case tutorial case example - var chip: ODSChip { - ODSChip(self, text: rawValue.capitalized) + var description: LocalizedStringKey { + switch self { + case .tutorial: + return "screens.components.bottom_sheets.tutorial" + case .example: + return "screens.components.bottom_sheets.example" + } } - static var chips: [ODSChip] { + var chip: ODSChoiceChip { + .init(text: Text(description), value: self) + } + + static var chips: [ODSChoiceChip] { Self.allCases.map { $0.chip } } } extension ODSBottomSheetSize { - var description: String { + var description: LocalizedStringKey { switch self { case .small: - return °°"screens.components.bottom_sheets.size.small" + return "screens.components.bottom_sheets.size.small" case .medium: - return °°"screens.components.bottom_sheets.size.medium" + return "screens.components.bottom_sheets.size.medium" case .large: - return °°"screens.components.bottom_sheets.size.large" + return "screens.components.bottom_sheets.size.large" case .hidden: - return °°"screens.components.bottom_sheets.size.hidden" + return "screens.components.bottom_sheets.size.hidden" } } - var chip: ODSChip { - ODSChip(self, text: description) + var chip: ODSChoiceChip { + .init(text: Text(description), value: self) } - static var chips: [ODSChip] { - Self.allCases - .filter { $0 != .hidden } - .map { $0.chip } + static var chips: [ODSChoiceChip] { + Self.allCases.map { $0.chip } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/ButtonsComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/ButtonsComponent.swift index 23ed5d87..7d9630ec 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/ButtonsComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/ButtonsComponent.swift @@ -14,13 +14,13 @@ import SwiftUI // ======================== struct ButtonComponent: Component { - let title: String + let name: String let imageName: String let description: String let variants: AnyView init() { - title = °°"screens.components.buttons.title" + name = °°"screens.components.buttons.title" imageName = "Buttons - Shape" description = °°"screens.components.buttons.description" variants = AnyView(ButtonVariants()) @@ -34,14 +34,14 @@ struct ButtonComponent: Component { struct ButtonVariants: View { var body: some View { - VariantEntryItem(title: °°"screens.components.buttons.variant.emphasis", technicalElement: "ODSButton()") { + VariantEntryItem(title: "screens.components.buttons.variant.emphasis", technicalElement: "ODSButton()") { CommonButtonVariant(model: EmphasisAndFunctionnalVariantModel()) { model in EmphasisVariant(model: model) } .navigationTitle("screens.components.buttons.variant.emphasis") } - VariantEntryItem(title: °°"screens.components.buttons.variant.functional", technicalElement: "ODSFunctionalButton()") { + VariantEntryItem(title: "screens.components.buttons.variant.functional", technicalElement: "ODSFunctionalButton()") { CommonButtonVariant(model: EmphasisAndFunctionnalVariantModel()) { model in FunctionalVariant(model: model) @@ -49,7 +49,7 @@ struct ButtonVariants: View { .navigationTitle("screens.components.buttons.variant.functional") } - VariantEntryItem(title: °°"screens.components.buttons.variant.icons", technicalElement: "ODSIconButton()") { + VariantEntryItem(title: "screens.components.buttons.variant.icons", technicalElement: "ODSIconButton()") { IconVariant(model: IconVariantModel()) .navigationTitle("screens.components.buttons.variant.icons") } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/EmphasisAndFunctionnalVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/EmphasisAndFunctionnalVariant.swift index cec7a387..a7c9144c 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/EmphasisAndFunctionnalVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/EmphasisAndFunctionnalVariant.swift @@ -27,7 +27,7 @@ struct EmphasisVariant: View { var body: some View { Text("screens.components.buttons.variant.emphasis.description") - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .padding(.bottom, ODSSpacing.xs) .frame(maxWidth: .infinity, alignment: .leading) @@ -35,7 +35,7 @@ struct EmphasisVariant: View { VStack(alignment: .center, spacing: ODSSpacing.s) { HStack { Text("\(emphasis.rawValue)".capitalized) - .odsFont(.headline) + .odsFont(.headlineS) Spacer() } .accessibilityAddTraits(.isHeader) @@ -45,7 +45,6 @@ struct EmphasisVariant: View { emphasis: emphasis, fullWidth: model.showFullWidth) {} .disabled(!model.showEnabled) - .accessibilityLabel("a11y.emphasis_button_hint" <- "\(emphasis.rawValue)") } } } @@ -69,14 +68,14 @@ struct FunctionalVariant: View { var body: some View { Text("screens.components.buttons.variant.functional.description") - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .padding(.bottom, ODSSpacing.xs) .frame(maxWidth: .infinity, alignment: .leading) ForEach(ODSFunctionalButton.Style.allCases, id: \.rawValue) { style in VStack(alignment: .center, spacing: ODSSpacing.s) { HStack { - Text(description(for: style)).odsFont(.headline) + Text(description(for: style)).odsFont(.headlineS) Spacer() } .accessibilityAddTraits(.isHeader) @@ -86,7 +85,6 @@ struct FunctionalVariant: View { style: style, fullWidth: model.showFullWidth) {} .disabled(!model.showEnabled) - .accessibilityLabel("a11y.functional_button_hint" <- "\(style.rawValue)") } } } @@ -107,7 +105,7 @@ struct FunctionalVariant: View { // MARK: - Emphasis and Functionnal Variant // ======================================== -class EmphasisAndFunctionnalVariantModel: ObservableObject { +final class EmphasisAndFunctionnalVariantModel: ObservableObject { // ======================= // MARK: Stored Properties diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/IconVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/IconVariant.swift index 1eb8ff9d..f569f13c 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/IconVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Buttons/IconVariant.swift @@ -37,21 +37,21 @@ struct IconVariant: View { ScrollView { VStack(spacing: ODSSpacing.m) { Text("screens.components.buttons.variant.description") - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .frame(maxWidth: .infinity, alignment: .leading) VariantsTitle().frame(maxWidth: .infinity, alignment: .leading) VStack(alignment: .center, spacing: ODSSpacing.l) { VStack(alignment: .center, spacing: ODSSpacing.s) { - Text("screens.components.buttons.icon_add").odsFont(.headline).frame(maxWidth: .infinity, alignment: .leading) + Text("screens.components.buttons.icon_add").odsFont(.headlineS).frame(maxWidth: .infinity, alignment: .leading) ODSIconButton(image: Image("Add")) {} .disabled(!model.showEnabled) } VStack(alignment: .center, spacing: ODSSpacing.s) { - Text("screens.components.buttons.icon_info").odsFont(.headline).frame(maxWidth: .infinity, alignment: .leading) + Text("screens.components.buttons.icon_info").odsFont(.headlineS).frame(maxWidth: .infinity, alignment: .leading) ODSIconButton(image: Image(systemName: "info.circle")) {} .disabled(!model.showEnabled) @@ -70,7 +70,7 @@ struct IconVariant: View { // MARK: - Icon Variant Model // ========================== -class IconVariantModel: ObservableObject { +final class IconVariantModel: ObservableObject { // ======================= // MARK: Stored properties diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardComponent.swift index 3627b3d4..c6826c96 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardComponent.swift @@ -14,13 +14,13 @@ import SwiftUI // ====================== struct CardComponent: Component { - let title: String + let name: String let imageName: String let description: String let variants: AnyView init() { - title = °°"screens.components.cards.title" + name = °°"screens.components.cards.title" imageName = "Cards_1" description = °°"screens.components.cards.description" variants = AnyView(CardVariants()) @@ -34,22 +34,22 @@ struct CardComponent: Component { struct CardVariants: View { var body: some View { - VariantEntryItem(title: °°"screens.components.cards.variant.vertical_image_first", technicalElement: "ODSCardVerticalImageFirst()") { + VariantEntryItem(title: "screens.components.cards.variant.vertical_image_first", technicalElement: "ODSCardVerticalImageFirst()") { CardVerticalImageFirstVariant(model: CardVerticalImageFirstVariantModel()) .navigationTitle("screens.components.cards.variant.vertical_image_first") } - VariantEntryItem(title: °°"screens.components.cards.variant.vertical_header_first", technicalElement: "ODSCardVerticalHeaderFirst()") { + VariantEntryItem(title: "screens.components.cards.variant.vertical_header_first", technicalElement: "ODSCardVerticalHeaderFirst()") { CardVerticalHeaderFirstVariant(model: CardVerticalHeaderFirstVariantModel()) .navigationTitle("screens.components.cards.variant.vertical_header_first") } - VariantEntryItem(title: °°"screens.components.cards.variant.small", technicalElement: "ODSCardSmall") { + VariantEntryItem(title: "screens.components.cards.variant.small", technicalElement: "ODSCardSmall") { CardSmallVariant(model: CardSmallVariantModel()) .navigationTitle("screens.components.cards.variant.small") } - VariantEntryItem(title: °°"screens.components.cards.variant.horizontal", technicalElement: "ODSCardHorizontal") { + VariantEntryItem(title: "screens.components.cards.variant.horizontal", technicalElement: "ODSCardHorizontal") { CardHorizontalVariant(model: CardHorizontalVariantModel()) .navigationTitle("screens.components.cards.variant.horizontal") } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardHorizontalVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardHorizontalVariant.swift index 6efbc4d9..ff0d297f 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardHorizontalVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardHorizontalVariant.swift @@ -17,20 +17,20 @@ extension ODSCardHorizontal.ImagePosition: CaseIterable { public static var allCases: [ODSCardHorizontal.ImagePosition] = [.leading, .trailing] - var description: String { + var description: LocalizedStringKey { switch self { case .leading: - return °°"shared.leading" + return "shared.leading" case .trailing: - return °°"shared.trailing" + return "shared.trailing" } } - var chip: ODSChip { - ODSChip(self, text: description) + var chip: ODSChoiceChip { + .init(text: Text(description), value: self) } - static var chips: [ODSChip] { + static var chips: [ODSChoiceChip] { Self.allCases.map { $0.chip } } } @@ -39,7 +39,7 @@ extension ODSCardHorizontal.ImagePosition: CaseIterable { // MARK: - Card Horizontal Variant Model // ===================================== -class CardHorizontalVariantModel: ObservableObject { +final class CardHorizontalVariantModel: ObservableObject { // ======================= // MARK: Stored Properties @@ -210,9 +210,10 @@ private struct CardHorizontalVariantOptions: View { Toggle("shared.text", isOn: $model.showText) .padding(.horizontal, ODSSpacing.m) - ODSChipPicker(title: °°"screens.components.card.picker.position", - selection: $model.imagePosition, - chips: ODSCardHorizontal.ImagePosition.chips) + ODSChoiceChipPicker( + title: Text("screens.components.card.picker.position"), + chips: ODSCardHorizontal.ImagePosition.chips, + selection: $model.imagePosition) Toggle("screens.components.card.divider", isOn: $model.showDivider) .padding(.horizontal, ODSSpacing.m) @@ -222,7 +223,7 @@ private struct CardHorizontalVariantOptions: View { in: 0 ... model.buttonsText.count) .padding(.horizontal, ODSSpacing.m) } - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .padding(.vertical, ODSSpacing.m) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardSmallVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardSmallVariant.swift index c13e9c15..b8e15934 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardSmallVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardSmallVariant.swift @@ -9,7 +9,7 @@ import OrangeDesignSystem import SwiftUI -class CardSmallVariantModel: ObservableObject { +final class CardSmallVariantModel: ObservableObject { // ======================= // MARK: Stored properties @@ -75,7 +75,7 @@ private struct CardSmallVariantOptions: View { var body: some View { Toggle("shared.subtitle", isOn: $model.showSubtitle) - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .padding(.horizontal, ODSSpacing.m) .padding(.vertical, ODSSpacing.m) } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalHeaderFirstVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalHeaderFirstVariant.swift index 2d214ffe..dba9c420 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalHeaderFirstVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalHeaderFirstVariant.swift @@ -13,7 +13,7 @@ import SwiftUI // MARK: - Card Vertical Header First Variant Model // ================================================ -class CardVerticalHeaderFirstVariantModel: ObservableObject { +final class CardVerticalHeaderFirstVariantModel: ObservableObject { // ======================= // MARK: Stored properties @@ -57,8 +57,8 @@ class CardVerticalHeaderFirstVariantModel: ObservableObject { showSubtitle ? Text(recipe.subtitle) : nil } - var thumbnail: Image? { - showThumbnail ? Image("ods_empty", bundle: Bundle.ods) : nil + var thumbnailSource: ODSImage.Source? { + showThumbnail ? .image(Image(recipe.iconName).renderingMode(.template)) : nil } var imageSource: ODSImage.Source { @@ -106,7 +106,7 @@ struct CardVerticalHeaderFirstVariant: View { .padding(.horizontal, ODSSpacing.m) .padding(.top, ODSSpacing.m) .onTapGesture { - model.displayAlert(text: "screens.components.card.alert") + model.displayAlert(text: "screens.components.card.alert".🌐) } } .alert(model.alertText, isPresented: $model.showAlert) { @@ -124,14 +124,14 @@ struct CardVerticalHeaderFirstVariant: View { ODSCardVerticalHeaderFirst(title: model.title, imageSource: model.imageSource, subtitle: model.subtitle, - thumbnail: model.thumbnail, + thumbnailSource: model.thumbnailSource, text: model.text) case 1: ODSCardVerticalHeaderFirst( title: model.title, imageSource: model.imageSource, subtitle: model.subtitle, - thumbnail: model.thumbnail, + thumbnailSource: model.thumbnailSource, text: model.text) { Button(model.firstButtonText) { @@ -143,7 +143,7 @@ struct CardVerticalHeaderFirstVariant: View { title: model.title, imageSource: model.imageSource, subtitle: model.subtitle, - thumbnail: model.thumbnail, + thumbnailSource: model.thumbnailSource, text: model.text) { Button(model.firstButtonText) { @@ -187,7 +187,7 @@ private struct CardVerticalHeaderFirstVariantOptions: View { value: $model.buttonCount, in: 0 ... model.buttonsText.count) } - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .padding(.vertical, ODSSpacing.m) .padding(.horizontal, ODSSpacing.m) } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalImageFirstVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalImageFirstVariant.swift index bfd11431..ea0f854a 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalImageFirstVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Cards/CardVerticalImageFirstVariant.swift @@ -13,7 +13,7 @@ import SwiftUI // MARK: - Card Vertical Image First Variant Model // =============================================== -class CardVerticalImageFirstVariantModel: ObservableObject { +final class CardVerticalImageFirstVariantModel: ObservableObject { // ======================= // MARK: Stored Properties @@ -186,7 +186,7 @@ private struct CardVerticalImageFirstVariantOptions: View { value: $model.buttonCount, in: 0 ... model.numberOfButtons) } - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .padding(.vertical, ODSSpacing.m) .padding(.horizontal, ODSSpacing.m) } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ActionChipsVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ActionChipsVariant.swift new file mode 100644 index 00000000..9d4d58d7 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ActionChipsVariant.swift @@ -0,0 +1,83 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import OrangeDesignSystem +import SwiftUI + +final class ActionChipVariantModel: ObservableObject { + @Published var showEnabled: Bool = true +} + +struct ActionChipVariant: View { + + // ======================= + // MARK: Stored properties + // ======================= + + @ObservedObject private var model: ActionChipVariantModel + @State private var showText: String? + private let food: Food + + // ================= + // MARK: Initializer + // ================= + + init(model: ActionChipVariantModel) { + self.model = model + food = RecipeBook.shared.foods[0] + } + + // ========== + // MARK: Body + // ========== + + var body: some View { + CustomizableVariant { + Toastable(showText: $showText) { + ScrollView(.vertical) { + VStack(alignment: .leading, spacing: ODSSpacing.m) { + Text("screens.components.chips.action.description") + .frame(maxWidth: .infinity, alignment: .leading) + + ODSActionChip( + text: Text(food.name), + leadingIcon: Image("FoodsAndEntertainment"), + action: { showText = "screens.components.chips.variant.chip.clicked".localized(with: food.name) + }) + .disabled(!model.showEnabled) + } + .padding(.all, ODSSpacing.m) + } + } + } options: { + ActionChipVariantOptions(model: model) + } + } +} + +struct ActionChipVariantOptions: View { + + // ======================= + // MARK: Stored properties + // ======================= + + @ObservedObject var model: ActionChipVariantModel + + // ========== + // MARK: Body + // ========== + + var body: some View { + VStack { + Toggle("shared.enabled", isOn: $model.showEnabled) + } + .odsFont(.bodyLBold) + .padding(.horizontal, ODSSpacing.m) + .padding(.vertical, ODSSpacing.s) + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ChipsComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ChipsComponent.swift index 62ef6802..591848c8 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ChipsComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ChipsComponent.swift @@ -14,16 +14,16 @@ import SwiftUI // ======================= struct ChipsComponent: Component { - let title: String + let name: String let imageName: String let description: String let variants: AnyView init() { - title = °°"screens.components.chips.title" + name = °°"screens.components.chips.title" imageName = "Chips" description = °°"screens.components.chips.description" - variants = AnyView(ChipsVariants(model: ChipsVariantModel())) + variants = AnyView(ChipsVariants()) } } @@ -33,59 +33,25 @@ struct ChipsComponent: Component { struct ChipsVariants: View { - @ObservedObject var model: ChipsVariantModel - var body: some View { - VStack(alignment: .leading, spacing: ODSSpacing.m) { - GroupedChips(title: °°"screens.components.chips.text_only", - chips: model.textOnlyChips, - removableChips: model.textOnlyRemovableChips, - selection: $model.selectedTextOnlyChip, - selectionRemovableChips: $model.selectedTextOnlyRemovableChip) - - GroupedChips(title: °°"screens.components.chips.with_icon", - chips: model.withIconChips, - removableChips: model.withIconRemovableChips, - selection: $model.selectedWithIconChip, - selectionRemovableChips: $model.selectedWithIconRemovableChip) - - GroupedChips(title: °°"screens.components.chips.with_system_icon", - chips: model.withSystemIconChips, - removableChips: model.withSystemIconRemovableChips, - selection: $model.selectedWithSystemIconChip, - selectionRemovableChips: $model.selectedWithSystemIconRemovableChip) - - GroupedChips(title: °°"screens.components.chips.with_avatar", - chips: model.withAvatarChips, - removableChips: model.withAvatarRemovableChips, - selection: $model.selectedWithAvatarChip, - selectionRemovableChips: $model.selectedWithAvatarRemovableChip) + VariantEntryItem(title: "screens.components.chips.variant.action", technicalElement: "ODSActionChip()") { + ActionChipVariant(model: ActionChipVariantModel()) + .navigationTitle("screens.components.chips.variant.action") } - } -} -// ===================== -// MARK: - Grouped Chips -// ===================== - -struct GroupedChips: View where ChipNotRemovable: Hashable, ChipRemovable: Hashable { - - let title: String - var chips: [ODSChip] - var removableChips: [ODSChip] - - let selection: Binding - let selectionRemovableChips: Binding + VariantEntryItem(title: "screens.components.chips.variant.input", technicalElement: "ODSInputChip()") { + InputChipVariant(model: InputChipVariantModel()) + .navigationTitle("screens.components.chips.variant.input") + } - var body: some View { - VStack(alignment: .leading, spacing: ODSSpacing.m) { - Text(title).odsFont(.title2).frame(maxWidth: .infinity, alignment: .leading) + VariantEntryItem(title: "screens.components.chips.variant.choice", technicalElement: "ODSChoiceChip()") { + ChoiceChipVariant(model: ChoiceChipVariantModel()) + .navigationTitle("screens.components.chips.variant.choice") + } - VStack(spacing: ODSSpacing.s) { - ODSChipPicker(selection: selection, chips: chips) - ODSChipPicker(selection: selectionRemovableChips, chips: removableChips) - } - .padding(.horizontal, -ODSSpacing.m) + VariantEntryItem(title: "screens.components.chips.variant.filter", technicalElement: "ODSFilterChip()") { + FilterChipVariant(model: FilterChipVariantModel()) + .navigationTitle("screens.components.chips.variant.filter") } } } @@ -94,12 +60,12 @@ struct GroupedChips: View where ChipNotRemovabl struct ChipsViewDemoSandBox_Previews: PreviewProvider { static var previews: some View { NavigationView { - ChipsVariants(model: ChipsVariantModel()) + ChipsVariants() .previewInterfaceOrientation(.portrait) } NavigationView { - ChipsVariants(model: ChipsVariantModel()) + ChipsVariants() .previewInterfaceOrientation(.portrait) .environment(\.dynamicTypeSize, .accessibility3) } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ChipsComponentModel.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ChipsComponentModel.swift deleted file mode 100644 index ef66bc88..00000000 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ChipsComponentModel.swift +++ /dev/null @@ -1,162 +0,0 @@ -// -// Software Name: Orange Design System (iOS) -// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA -// SPDX-License-Identifier: MIT -// -// This software is distributed under the MIT license. -// - -import OrangeDesignSystem -import SwiftUI - -class ChipsVariantModel: ObservableObject { - - // Text only section - enum TextOnlyChip: Int { - case enabled - case selected - case disabled - } - - enum TextOnlyRemovableChip: Int { - case enabled - case selected - case disabled - } - - let textOnlyChips: [ODSChip] - let textOnlyRemovableChips: [ODSChip] - - @Published var selectedTextOnlyChip: TextOnlyChip? - @Published var selectedTextOnlyRemovableChip: TextOnlyRemovableChip? - - // with icon section - enum WithIconChip: Int { - case enabled - case selected - case disabled - } - - enum WithIconRemovableChip: Int { - case enabled - case selected - case disabled - } - - let withIconChips: [ODSChip] - let withIconRemovableChips: [ODSChip] - - @Published var selectedWithIconChip: WithIconChip? - @Published var selectedWithIconRemovableChip: WithIconRemovableChip? - - // with system icon section - enum WithSystemIconChip: Int { - case enabled - case selected - case disabled - } - - enum WithSystemIconRemovableChip: Int { - case enabled - case selected - case disabled - } - - let withSystemIconChips: [ODSChip] - let withSystemIconRemovableChips: [ODSChip] - - @Published var selectedWithSystemIconChip: WithSystemIconChip? - @Published var selectedWithSystemIconRemovableChip: WithSystemIconRemovableChip? - - // with avatar section - enum WithAvatarChip: Int { - case enabled - case selected - case disabled - } - - enum WithAvatarRemovableChip: Int { - case enabled - case selected - case disabled - } - - let withAvatarChips: [ODSChip] - let withAvatarRemovableChips: [ODSChip] - - @Published var selectedWithAvatarChip: WithAvatarChip? - @Published var selectedWithAvatarRemovableChip: WithAvatarRemovableChip? - - init() { - - let avatarImage = Image("avatar", bundle: Bundle.main) - let iconImage = Image("iconsFunctionalUiEMIcHeartRecommend", bundle: Bundle.main) - - // Chips with textOnly - textOnlyChips = [ - ODSChip(.enabled, text: °°"screens.components.chips.state.enable"), - ODSChip(.selected, text: °°"screens.components.chips.state.selected"), - ODSChip(.disabled, text: °°"shared.disabled", disabled: true), - ] - - selectedTextOnlyChip = .selected - - textOnlyRemovableChips = [ - ODSChip(.enabled, text: °°"screens.components.chips.state.enable", removable: true), - ODSChip(.selected, text: °°"screens.components.chips.state.selected", removable: true), - ODSChip(.disabled, text: °°"shared.disabled", disabled: true, removable: true), - ] - - selectedTextOnlyRemovableChip = .selected - - // Chips with icon - withIconChips = [ - ODSChip(.enabled, text: °°"screens.components.chips.state.enable", thumbnail: .icon(iconImage)), - ODSChip(.selected, text: °°"screens.components.chips.state.selected", thumbnail: .icon(iconImage)), - ODSChip(.disabled, text: °°"shared.disabled", thumbnail: .icon(iconImage), disabled: true), - ] - - selectedWithIconChip = .selected - - withIconRemovableChips = [ - ODSChip(.enabled, text: °°"screens.components.chips.state.enable", thumbnail: .icon(iconImage), removable: true), - ODSChip(.selected, text: °°"screens.components.chips.state.selected", thumbnail: .icon(iconImage), removable: true), - ODSChip(.disabled, text: °°"shared.disabled", thumbnail: .icon(iconImage), disabled: true, removable: true), - ] - - selectedWithIconRemovableChip = .selected - - // System icons - withSystemIconChips = [ - ODSChip(.enabled, text: °°"screens.components.chips.state.enable", thumbnail: .iconSystem(name: "heart")), - ODSChip(.selected, text: °°"screens.components.chips.state.selected", thumbnail: .iconSystem(name: "heart")), - ODSChip(.disabled, text: °°"shared.disabled", thumbnail: .iconSystem(name: "heart"), disabled: true), - ] - - selectedWithSystemIconChip = .selected - - withSystemIconRemovableChips = [ - ODSChip(.enabled, text: °°"screens.components.chips.state.enable", thumbnail: .iconSystem(name: "heart"), removable: true), - ODSChip(.selected, text: °°"screens.components.chips.state.selected", thumbnail: .iconSystem(name: "heart"), removable: true), - ODSChip(.disabled, text: °°"shared.disabled", thumbnail: .iconSystem(name: "heart"), disabled: true, removable: true), - ] - selectedWithSystemIconRemovableChip = .selected - - // Chips with avatar - withAvatarChips = [ - ODSChip(.enabled, text: °°"screens.components.chips.state.enable", thumbnail: .avatar(avatarImage)), - ODSChip(.selected, text: °°"screens.components.chips.state.selected", thumbnail: .avatar(avatarImage)), - ODSChip(.disabled, text: °°"shared.disabled", thumbnail: .avatar(avatarImage), disabled: true), - ] - - selectedWithAvatarChip = .selected - - withAvatarRemovableChips = [ - ODSChip(.enabled, text: °°"screens.components.chips.state.enable", thumbnail: .avatar(avatarImage), removable: true), - ODSChip(.selected, text: °°"screens.components.chips.state.selected", thumbnail: .avatar(avatarImage), removable: true), - ODSChip(.disabled, text: °°"shared.disabled", thumbnail: .avatar(avatarImage), disabled: true, removable: true), - ] - - selectedWithAvatarRemovableChip = .selected - } -} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ChoiceChipsVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ChoiceChipsVariant.swift new file mode 100644 index 00000000..a68a14a3 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/ChoiceChipsVariant.swift @@ -0,0 +1,83 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import OrangeDesignSystem +import SwiftUI + +final class ChoiceChipVariantModel: ObservableObject { + @Published var showEnabled: Bool = true +} + +struct ChoiceChipVariant: View { + + // ======================= + // MARK: Stored properties + // ======================= + + @ObservedObject private var model: ChoiceChipVariantModel + @State private var isSelected: Bool = false + @State private var selectedFood: Food + private let foods: [Food] + + // ================= + // MARK: Initializer + // ================= + + init(model: ChoiceChipVariantModel) { + self.model = model + foods = Array(RecipeBook.shared.foods.prefix(6)) + _selectedFood = State(initialValue: foods.first!) + } + + // ========== + // MARK: Body + // ========== + + var body: some View { + CustomizableVariant { + ScrollView { + VStack(alignment: .leading, spacing: ODSSpacing.m) { + Text("screens.components.chips.choice.description") + .padding(.horizontal, ODSSpacing.m) + .frame(maxWidth: .infinity, alignment: .leading) + + ODSChoiceChipPicker( + chips: foods.map { .init(text: Text($0.name), value: $0) }, + selection: $selectedFood, + placement: .stacked) + .disabled(!model.showEnabled) + } + .padding(.vertical, ODSSpacing.m) + } + } options: { + ChoiceChipVariantOptions(model: model) + } + } +} + +struct ChoiceChipVariantOptions: View { + + // ======================= + // MARK: Stored properties + // ======================= + + @ObservedObject var model: ChoiceChipVariantModel + + // ========== + // MARK: Body + // ========== + + var body: some View { + VStack { + Toggle("shared.enabled", isOn: $model.showEnabled) + } + .odsFont(.bodyLBold) + .padding(.horizontal, ODSSpacing.m) + .padding(.vertical, ODSSpacing.s) + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/FilterChipVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/FilterChipVariant.swift new file mode 100644 index 00000000..c2b7f74c --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/FilterChipVariant.swift @@ -0,0 +1,121 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import OrangeDesignSystem +import SwiftUI + +final class FilterChipVariantModel: ObservableObject { + + @Published var showEnabled: Bool = true + @Published var leadingElement: LeadingElement = .none + + enum LeadingElement: Int, CaseIterable, Hashable { + case none + case avatar + + var description: LocalizedStringKey { + + switch self { + case .none: return "None" + case .avatar: return "Avatar" + } + } + + var chip: ODSChoiceChip { + .init(text: Text(description), value: self) + } + + static var chips: [ODSChoiceChip] { + Self.allCases.map { $0.chip } + } + } +} + +struct FilterChipVariant: View { + + // ======================= + // MARK: Stored properties + // ======================= + + @ObservedObject private var model: FilterChipVariantModel + @State private var selectedFoods: [Food] + private let foods: [Food] + + // ================= + // MARK: Initializer + // ================= + + init(model: FilterChipVariantModel) { + self.model = model + foods = Array(RecipeBook.shared.foods.prefix(6)) + selectedFoods = Array(RecipeBook.shared.foods.prefix(1)) + } + + // ========== + // MARK: Body + // ========== + + var body: some View { + CustomizableVariant { + ScrollView { + VStack(alignment: .leading, spacing: ODSSpacing.m) { + Text("screens.components.chips.filter.description") + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, ODSSpacing.m) + + ODSFilterChipPicker( + chips: foods.map { food in + .init(text: Text(food.name), leading: leading(for: food), value: food) + }, + selection: $selectedFoods) + .disabled(!model.showEnabled) + } + .padding(.top, ODSSpacing.m) + } + } options: { + FilterChipVariantOptions(model: model) + } + } + + func leading(for food: Food) -> ODSImage.Source? { + if model.leadingElement == .avatar, let url = food.image { + return ODSImage.Source(url: url) + } else { + return nil + } + } +} + +struct FilterChipVariantOptions: View { + + // ======================= + // MARK: Stored properties + // ======================= + + @ObservedObject var model: FilterChipVariantModel + let leadingElements = FilterChipVariantModel.LeadingElement.allCases + + // ========== + // MARK: Body + // ========== + + var body: some View { + VStack(spacing: ODSSpacing.m) { + ODSChoiceChipPicker( + title: Text("shared.leading"), + chips: leadingElements.map { .init(text: Text($0.description), value: $0) }, + selection: $model.leadingElement, + placement: .carousel) + + Toggle("shared.enabled", isOn: $model.showEnabled) + .padding(.horizontal, ODSSpacing.m) + } + .odsFont(.bodyLBold) + .padding(.vertical, ODSSpacing.s) + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/InputChipsVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/InputChipsVariant.swift new file mode 100644 index 00000000..268064b0 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Chips/InputChipsVariant.swift @@ -0,0 +1,126 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import OrangeDesignSystem +import SwiftUI + +final class InputChipVariantModel: ObservableObject { + + @Published var showEnabled: Bool = true + @Published var leadingElement: LeadingElement = .none + + enum LeadingElement: Int, CaseIterable, Hashable { + case none + case avatar + case icon + + var description: String { + + switch self { + case .none: return "None" + case .avatar: return "Avatar" + case .icon: return "Icon" + } + } + } +} + +struct InputChipVariant: View { + + // ======================= + // MARK: Stored properties + // ======================= + + @ObservedObject private var model: InputChipVariantModel + @State private var showText: String? + private let food: Food + + // ================= + // MARK: Initializer + // ================= + + init(model: InputChipVariantModel) { + self.model = model + food = RecipeBook.shared.foods[0] + } + + // ========== + // MARK: Body + // ========== + + var body: some View { + CustomizableVariant { + Toastable(showText: $showText) { + ScrollView { + VStack(alignment: .leading, spacing: ODSSpacing.m) { + Text("screens.components.chips.input.description") + .frame(maxWidth: .infinity, alignment: .leading) + + ODSInputChip( + text: Text(food.name), + leading: leading(for: food), + action: { + showText = "screens.components.chips.variant.chip.clicked".localized(with: food.name) + }, + removeAction: { + showText = "screens.components.chips.variant.input.remove_clicked".🌐 + }) + .disabled(!model.showEnabled) + } + } + .padding(.all, ODSSpacing.m) + } + } options: { + InputChipVariantOptions(model: model) + } + } + + func leading(for food: Food) -> ODSImage.Source? { + if model.leadingElement == .avatar, let url = food.image { + return ODSImage.Source(url: url) + } + + if model.leadingElement == .icon { + return .image(Image("Restaurant")) + } + + return nil + } +} + +struct InputChipVariantOptions: View { + + // ======================= + // MARK: Stored properties + // ======================= + + @ObservedObject var model: InputChipVariantModel + let leadingElement = InputChipVariantModel.LeadingElement.allCases + + // ========== + // MARK: Body + // ========== + + var body: some View { + VStack(spacing: ODSSpacing.m) { + + ODSChoiceChipPicker( + title: Text("shared.leading"), + chips: leadingElement.map { + .init(text: Text($0.description), value: $0) + }, + selection: $model.leadingElement, + placement: .carousel) + + Toggle("shared.enabled", isOn: $model.showEnabled) + .padding(.horizontal, ODSSpacing.m) + } + .padding(.vertical, ODSSpacing.s) + .odsFont(.bodyLBold) + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListComponent.swift index c8259a15..e9ea1da1 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListComponent.swift @@ -10,13 +10,13 @@ import OrangeDesignSystem import SwiftUI struct ListComponent: Component { - let title: String + let name: String let imageName: String let description: String let variants: AnyView init() { - title = °°"screens.components.lists.title" + name = °°"screens.components.lists.title" imageName = "Lists" description = °°"screens.components.lists.description" variants = AnyView(ListVariants()) @@ -25,11 +25,13 @@ struct ListComponent: Component { struct ListVariants: View { var body: some View { - VariantEntryItem(title: °°"screens.components.lists.variant.standard_lists", technicalElement: "ODSListItem()") { + VariantEntryItem(title: "screens.components.lists.variant.standard_lists", technicalElement: "ODSListItem()") { ListItemStandardVariant(model: ListItemStandardVariantModel()) + .navigationTitle("screens.components.lists.variant.standard_lists") } - VariantEntryItem(title: °°"screens.components.lists.variant.with_selections", technicalElement: "ODSListItem()") { + VariantEntryItem(title: "screens.components.lists.variant.with_selections", technicalElement: "ODSListItem()") { ListItemSelectionVariant(model: ListItemSelectionVariantModel()) + .navigationTitle("screens.components.lists.variant.with_selections") } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/LeadingOption.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/LeadingOption.swift index e8b92342..e45cf1aa 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/LeadingOption.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/LeadingOption.swift @@ -7,6 +7,7 @@ // import OrangeDesignSystem +import SwiftUI // ============= // MARK: Options @@ -19,26 +20,26 @@ enum LeadingOption: Int, CaseIterable { case wide case square - var description: String { + var description: LocalizedStringKey { switch self { case .none: - return °°"shared.none" + return "shared.none" case .icon: - return °°"shared.icon" + return "shared.icon" case .circle: - return °°"shared.circle" + return "shared.circle" case .wide: - return °°"shared.wide" + return "shared.wide" case .square: - return °°"shared.square" + return "shared.square" } } - var chip: ODSChip { - ODSChip(self, text: description) + var chip: ODSChoiceChip { + .init(text: Text(description), value: self) } - static var chips: [ODSChip] { + static var chips: [ODSChoiceChip] { Self.allCases.map { $0.chip } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/SelectionVariant/ListItemVaraintSelectionModel.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/SelectionVariant/ListItemSelectionVariantModel.swift similarity index 69% rename from OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/SelectionVariant/ListItemVaraintSelectionModel.swift rename to OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/SelectionVariant/ListItemSelectionVariantModel.swift index 7664f9cd..b039b844 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/SelectionVariant/ListItemVaraintSelectionModel.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/SelectionVariant/ListItemSelectionVariantModel.swift @@ -13,25 +13,25 @@ enum SelectionTrailingOption: CaseIterable { case toggle case checkmark - var description: String { + var description: LocalizedStringKey { switch self { case .toggle: - return °°"screens.components.lists.selection.description.switch" + return "screens.components.lists.selection.description.switch" case .checkmark: - return °°"screens.components.lists.selection.description.checkmark" + return "screens.components.lists.selection.description.checkmark" } } - private var chip: ODSChip { - ODSChip(self, text: description) + private var chip: ODSChoiceChip { + .init(text: Text(description), value: self) } - static var chips: [ODSChip] { + static var chips: [ODSChoiceChip] { Self.allCases.map { $0.chip } } } -class ListItemSelectionVariantModel: ObservableObject { +final class ListItemSelectionVariantModel: ObservableObject { // ======================= // MARK: Stored properties diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/SelectionVariant/ListItemSelectionVariantOptions.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/SelectionVariant/ListItemSelectionVariantOptions.swift index a7809112..a358ae1d 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/SelectionVariant/ListItemSelectionVariantOptions.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/SelectionVariant/ListItemSelectionVariantOptions.swift @@ -24,19 +24,21 @@ struct ListItemSelectionVariantOptions: View { var body: some View { VStack(spacing: ODSSpacing.none) { Toggle(isOn: $model.showSubtitle) { - Text("shared.subtitle").odsFont(.bodyBold) + Text("shared.subtitle").odsFont(.bodyLBold) } .padding(.horizontal, ODSSpacing.m) .padding(.vertical, ODSSpacing.s) - ODSChipPicker(title: °°"shared.leading", - selection: $model.leadingOption, - chips: LeadingOption.chips) + ODSChoiceChipPicker( + title: Text("shared.leading"), + chips: LeadingOption.chips, + selection: $model.leadingOption) .padding(.vertical, ODSSpacing.s) - ODSChipPicker(title: °°"shared.trailing", - selection: $model.trailingOption, - chips: SelectionTrailingOption.chips) + ODSChoiceChipPicker( + title: Text("shared.trailing"), + chips: SelectionTrailingOption.chips, + selection: $model.trailingOption) .padding(.vertical, ODSSpacing.s) } .padding(.top, ODSSpacing.s) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemStandardVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemStandardVariant.swift index 46c7f47a..c649efc1 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemStandardVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemStandardVariant.swift @@ -48,7 +48,7 @@ private struct ListItemStandardVariantInner: View { List { if model.navigate { NavigationLink { - Text("Bon appetit!") + Text("shared.bon_app") } label: { listItem } @@ -60,8 +60,8 @@ private struct ListItemStandardVariantInner: View { } .listStyle(.plain) .navigationBarTitleDisplayMode(.inline) - .alert("Information icon tapped! Bon appétit", isPresented: $showAlert) { - Button("close", role: .cancel) {} + .alert("screens.components.lists.alert", isPresented: $showAlert) { + Button("shared.close", role: .cancel) {} } } @@ -80,7 +80,7 @@ private struct ListItemStandardVariantInner: View { title: title, subtitle: subtitle, leading: leading, - trailingText: Text("Details")) + trailingText: Text("screens.components.list.details")) { iButtonAction() } @@ -89,7 +89,7 @@ private struct ListItemStandardVariantInner: View { title: title, subtitle: subtitle, leading: leading, - trailingText: Text("Details")) + trailingText: Text("screens.components.list.details")) case (false, true): ODSListItem( diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemVariantStandardModel.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemStandardVariantModel.swift similarity index 78% rename from OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemVariantStandardModel.swift rename to OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemStandardVariantModel.swift index fee49bc1..b73005e5 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemVariantStandardModel.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemStandardVariantModel.swift @@ -17,20 +17,20 @@ enum StandardTrailingOption: CaseIterable { case text case iButton - var description: String { + var description: LocalizedStringKey { switch self { case .text: - return °°"shared.text" + return "shared.text" case .iButton: - return °°"screens.components.lists.options.description.info_button" + return "screens.components.lists.options.description.info_button" } } - private var chip: ODSChip { - ODSChip(self, text: description) + private var chip: ODSFilterChip { + .init(text: Text(description), value: self) } - static var chips: [ODSChip] { + static var chips: [ODSFilterChip] { Self.allCases.map { $0.chip } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemStandardVariantOptions.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemStandardVariantOptions.swift index 3a2352b2..8a776805 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemStandardVariantOptions.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/ListItemVariant/StandardVariant/ListItemStandardVariantOptions.swift @@ -24,24 +24,26 @@ struct ListItemStandardVariantOptions: View { var body: some View { VStack(spacing: ODSSpacing.none) { Toggle(isOn: $model.showSubtitle) { - Text("shared.subtitle").odsFont(.bodyBold) + Text("shared.subtitle").odsFont(.bodyLBold) } .padding(.horizontal, ODSSpacing.m) .padding(.vertical, ODSSpacing.s) - ODSChipPicker(title: °°"shared.leading", - selection: $model.leadingOption, - chips: LeadingOption.chips) + ODSChoiceChipPicker( + title: Text("shared.leading"), + chips: LeadingOption.chips, + selection: $model.leadingOption) .padding(.vertical, ODSSpacing.s) - ODSChipPicker(title: °°"shared.trailing", - selection: $model.trailingOptions, - allowZeroSelection: true, - chips: StandardTrailingOption.chips) + ODSFilterChipPicker( + title: Text("shared.trailing"), + chips: StandardTrailingOption.chips, + selection: $model.trailingOptions, + placement: .carousel) .padding(.vertical, ODSSpacing.s) Toggle(isOn: $model.navigate) { - Text("screens.components.lists.picker.navigate").odsFont(.bodyBold) + Text("screens.components.lists.picker.navigate").odsFont(.bodyLBold) } .padding(.horizontal, ODSSpacing.m) .padding(.vertical, ODSSpacing.s) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/SelectionVariant/SelectionListOptions.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/SelectionVariant/SelectionListOptions.swift deleted file mode 100644 index a44f4e25..00000000 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/SelectionVariant/SelectionListOptions.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// Software Name: Orange Design System (iOS) -// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA -// SPDX-License-Identifier: MIT -// -// This software is distributed under the MIT license. -// - -import OrangeDesignSystem -import SwiftUI - -struct SelectionListVariantOptions: View { - - // ======================= - // MARK: Stored Properties - // ======================= - - @ObservedObject var model: SelectionListVariantModel - - // ========== - // MARK: Body - // ========== - - var body: some View { - VStack(spacing: ODSSpacing.none) { - Toggle(isOn: $model.showSubtitle) { - Text("shared.subtitle").odsFont(.bodyBold) - } - .padding(.horizontal, ODSSpacing.m) - .padding(.vertical, ODSSpacing.s) - - ODSChipPicker(title: °°"shared.leading", - selection: $model.leadingIconOption, - chips: LeadingIconOption.chips) - .padding(.vertical, ODSSpacing.s) - - ODSChipPicker(title: °°"shared.trailing", - selection: $model.trailingOption, - chips: ODSListSelectionItemModel.TrailingSelection.chips) - .padding(.vertical, ODSSpacing.s) - } - .padding(.top, ODSSpacing.s) - } -} - -extension ODSListSelectionItemModel.TrailingSelection { - - private var description: String { - switch self { - case .checkmark: - return °°"screens.components.lists.selection.description.checkmark" - case .toggle: - return °°"screens.components.lists.selection.description.switch" - } - } - - private var chip: ODSChip { - ODSChip(self, text: description) - } - - static var chips: [ODSChip] { - Self.allCases.map { $0.chip } - } -} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/StandardVariant/StandardList.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/StandardVariant/StandardList.swift deleted file mode 100644 index 09461774..00000000 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/StandardVariant/StandardList.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// Software Name: Orange Design System (iOS) -// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA -// SPDX-License-Identifier: MIT -// -// This software is distributed under the MIT license. -// - -import OrangeDesignSystem -import SwiftUI - -struct StandardListVariant: View { - - // ======================= - // MARK: Stored Properties - // ======================= - - let model: StandardListVariantModel - - // ========== - // MARK: Body - // ========== - - var body: some View { - CustomizableVariant { - StandardListVariantInner(model: model) - } options: { - StandardListVariantOptions(model: model) - } - } -} - -private struct StandardListVariantInner: View { - - // ======================= - // MARK: Stored Properties - // ======================= - - @ObservedObject var model: StandardListVariantModel - @State private var multiSelection: Set? = nil - - // ========== - // MARK: Body - // ========== - - var body: some View { - List /* (selection: $multiSelection) */ { - ForEach(model.itemModels, id: \.id) { itemModel in - if model.showDetails { - NavigationLink(itemModel) { - Text("screens.components.lists.variant.clicked" <- itemModel.title) - .navigationTitle(itemModel.title) - } - .listRowInsets(EdgeInsets()) - .listRowSeparator(Visibility.visible) - .padding(.horizontal, ODSSpacing.s) - } else { - ODSListStandardItem(model: itemModel) - .listRowInsets(EdgeInsets()) - .listRowSeparator(Visibility.visible) - .padding(.horizontal, ODSSpacing.s) - } - } - .onMove(perform: model.move) - .onDelete(perform: model.delete) - } - .toolbar { EditButton() } - .listStyle(.plain) - .navigationBarTitleDisplayMode(.inline) - .alert("screens.components.lists.alert", isPresented: $model.showSheetOnIButtonClicked) { - Button("shared.close", role: .cancel) {} - } - } -} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/StandardVariant/StandardListModel.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/StandardVariant/StandardListModel.swift deleted file mode 100644 index a9db0c0f..00000000 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/StandardVariant/StandardListModel.swift +++ /dev/null @@ -1,185 +0,0 @@ -// -// Software Name: Orange Design System (iOS) -// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA -// SPDX-License-Identifier: MIT -// -// This software is distributed under the MIT license. -// - -import OrangeDesignSystem -import SwiftUI - -// ============= -// MARK: Options -// ============= - -enum LeadingIconOption: Int, CaseIterable { - case none = 0 - case icon - case circle - case wide - case square - - var description: String { - switch self { - case .none: - return °°"shared.none" - case .icon: - return °°"shared.icon" - case .circle: - return °°"shared.circle" - case .wide: - return °°"shared.wide" - case .square: - return °°"shared.square" - } - } - - var chip: ODSChip { - ODSChip(self, text: description) - } - - static var chips: [ODSChip] { - Self.allCases.map { $0.chip } - } -} - -enum TrailingOption: Int, CaseIterable { - case text = 0 - case infoButton - - var description: String { - switch self { - case .text: - return °°"shared.text" - case .infoButton: - return °°"screens.components.lists.options.description.info_button" - } - } - - var chip: ODSChip { - ODSChip(self, text: description) - } - - static var chips: [ODSChip] { - Self.allCases.map { $0.chip } - } -} - -// ============== -// MARK: - Models -// ============== - -class StandardListVariantModel: ObservableObject { - - // ======================= - // MARK: Stored properties - // ======================= - - @Published var showSubtitle: Bool { - didSet { updateItems() } - } - - @Published var leadingIconOption: LeadingIconOption { - didSet { updateItems() } - } - - @Published var trailingOptions: [TrailingOption] { - didSet { updateItems() } - } - - @Published var itemModels: [ODSListStandardItemModel] = [] - - @Published var showDetails: Bool - - private var recipes: [Recipe] = [] - - // ================== - // MARK: Initializers - // ================== - - init() { - recipes = RecipeBook.shared.recipes - - showSubtitle = true - leadingIconOption = .circle - trailingOptions = [.text, .infoButton] - showDetails = true - - updateItems() - } - - //====================== - // MARK: Edition actions - // ===================== - - func delete(at offsets: IndexSet) { - recipes.remove(atOffsets: offsets) - updateItems() - } - - func move(from source: IndexSet, to destination: Int) { - recipes.move(fromOffsets: source, toOffset: destination) - updateItems() - } - - // ===================== - // MARK: Private helpers - // ===================== - - private func updateItems() { - itemModels = recipes.map { item(from: $0) } - } - - private func item(from recipe: Recipe) -> ODSListStandardItemModel { - return ODSListStandardItemModel(title: recipe.title, - subtitle: showSubtitle ? recipe.subtitle : nil, - leadingIcon: leadingIcon(from: recipe), - trailingActions: trailingActions) - } - - private func leadingIcon(from recipe: Recipe) -> ODSListItemLeadingIcon? { - let emptyImage = Image("ods_empty", bundle: Bundle.ods) - switch leadingIconOption { - case .none: - return nil - case .icon: - return .icon(Image(recipe.iconName)) - case .circle: - return .circularImage(source: .asyncImage(recipe.url, emptyImage)) - case .square: - return .squareImage(source: .asyncImage(recipe.url, emptyImage)) - case .wide: - return .wideImage(source: .asyncImage(recipe.url, emptyImage)) - } - } - - private var trailingActions: ODSListItemTrailingActions? { - - let showText = trailingOptions.contains { $0 == .text } - let showIButton = trailingOptions.contains { $0 == .infoButton } - - switch (showText, showIButton) { - case (true, true): - return ODSListItemTrailingActions(displayText: °°"screens.components.list.details", onIButtonClicked: onIButtonClicked) - case (true, false): - return ODSListItemTrailingActions(displayText: °°"screens.components.list.details") - case (false, true): - return ODSListItemTrailingActions(onIButtonClicked: onIButtonClicked) - default: - return nil - } - } - - // ===================== - // MARK: Buttons actions - // ===================== - - // Add button in navigation bar action - @Published var showSheetOnIButtonClicked: Bool = false - - // Info button action - func onIButtonClicked() { - showSheetOnIButtonClicked = true - } -} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/StandardVariant/StandardListOptions.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/StandardVariant/StandardListOptions.swift deleted file mode 100644 index 9526eb4f..00000000 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Lists/StandardVariant/StandardListOptions.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Software Name: Orange Design System (iOS) -// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA -// SPDX-License-Identifier: MIT -// -// This software is distributed under the MIT license. -// - -import OrangeDesignSystem -import SwiftUI - -struct StandardListVariantOptions: View { - - // ======================= - // MARK: Stored Properties - // ======================= - - @ObservedObject var model: StandardListVariantModel - - // ========== - // MARK: Body - // ========== - - var body: some View { - VStack(spacing: ODSSpacing.none) { - Toggle(isOn: $model.showSubtitle) { - Text("shared.subtitle").odsFont(.bodyBold) - } - .padding(.horizontal, ODSSpacing.m) - .padding(.vertical, ODSSpacing.s) - - ODSChipPicker(title: °°"shared.leading", - selection: $model.leadingIconOption, - chips: LeadingIconOption.chips) - .padding(.vertical, ODSSpacing.s) - - ODSChipPicker(title: °°"shared.trailing", - selection: $model.trailingOptions, - allowZeroSelection: true, - chips: TrailingOption.chips) - .padding(.vertical, ODSSpacing.s) - - Toggle(isOn: $model.showDetails) { - Text("screens.components.lists.picker.navigate").odsFont(.bodyBold) - } - .padding(.horizontal, ODSSpacing.m) - .padding(.vertical, ODSSpacing.s) - } - .padding(.top, ODSSpacing.s) - } -} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/NavigationBar/NavigationBarComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/NavigationBar/NavigationBarComponent.swift index 00938b31..145d2a0b 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/NavigationBar/NavigationBarComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/NavigationBar/NavigationBarComponent.swift @@ -10,16 +10,15 @@ import OrangeDesignSystem import SwiftUI struct NavigationBarComponent: Component { - let title: String + let name: String let imageName: String let description: String let variants: AnyView init() { - title = °°"screens.components.bars.navigation.title" + name = °°"screens.components.bars.navigation.title" imageName = "Navigation bars" description = °°"screens.components.bars.navigation.description" - variants = AnyView(NavigationBarVariants()) } } @@ -27,7 +26,7 @@ struct NavigationBarComponent: Component { struct NavigationBarVariants: View { var body: some View { - VariantEntryItem(title: °°"screens.components.bars.navigation.title", + VariantEntryItem(title: "screens.components.bars.navigation.title", technicalElement: "NavigationView", showThemeSelectionInNavigationBar: false) { NavigationBarVariant(model: NavigationBarVariantModel()) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/NavigationBar/NavigationBarModifiers.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/NavigationBar/NavigationBarModifiers.swift index af445fde..6790e803 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/NavigationBar/NavigationBarModifiers.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/NavigationBar/NavigationBarModifiers.swift @@ -13,7 +13,7 @@ import SwiftUI // MARK: - NavigationBar search model // ================================== -class NavigationBarSearchModel: ObservableObject { +final class NavigationBarSearchModel: ObservableObject { // ====================== // MARK: Store properties diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/NavigationBar/NavigationBarVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/NavigationBar/NavigationBarVariant.swift index ca18bece..fae6637b 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/NavigationBar/NavigationBarVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/NavigationBar/NavigationBarVariant.swift @@ -45,7 +45,7 @@ struct NavigationBarVariantContent: View { } } -class NavigationBarVariantModel: ObservableObject { +final class NavigationBarVariantModel: ObservableObject { // ====================== // MARK: Store properties @@ -100,18 +100,18 @@ class NavigationBarVariantModel: ObservableObject { } } - var description: String { + var description: LocalizedStringKey { switch self { - case .standard: return °°"screens.components.bars.navigation.standard_title.hint" - case .large: return °°"screens.components.bars.navigation.large_title.hint" + case .standard: return "screens.components.bars.navigation.standard_title.hint" + case .large: return "screens.components.bars.navigation.large_title.hint" } } - var chip: ODSChip { - ODSChip(self, text: description) + var chip: ODSChoiceChip { + .init(text: Text(description), value: self) } - static var chips: [ODSChip] { + static var elemens: [ODSChoiceChip] { Self.allCases.map { $0.chip } } } @@ -145,9 +145,11 @@ struct NavigationBarVariantOptions: View { var body: some View { VStack(spacing: ODSSpacing.m) { - ODSChipPicker(title: °°"screens.components.bars.navigation.size.hint", - selection: $model.titleSize, - chips: NavigationBarVariantModel.TitleSize.chips) + ODSChoiceChipPicker( + title: Text("screens.components.bars.navigation.size.hint"), + chips: NavigationBarVariantModel.TitleSize.elemens, + selection: $model.titleSize) + Group { Toggle("screens.components.bars.navigation.back_button.hint", isOn: $model.showBackButton) @@ -158,9 +160,9 @@ struct NavigationBarVariantOptions: View { Toggle("shared.search", isOn: $model.showSearch) } .padding(.horizontal, ODSSpacing.m) - .odsFont(.bodyBold) + .odsFont(.bodyLBold) } - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .padding(.vertical, ODSSpacing.m) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ProgressIndicator/ActivityIndicatorVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ProgressIndicator/ActivityIndicatorVariant.swift index 510e3048..244c20ab 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ProgressIndicator/ActivityIndicatorVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ProgressIndicator/ActivityIndicatorVariant.swift @@ -38,7 +38,7 @@ struct ActivityIndicatorVariant: View { } } -class ActivityIndicatorModel: ObservableObject { +final class ActivityIndicatorModel: ObservableObject { // ====================== // MARK: Store properties @@ -70,7 +70,7 @@ private struct ActivityIndicatorVariantOptions: View { var body: some View { VStack(spacing: ODSSpacing.m) { Toggle("screens.components.progress_indicators.toggle.label", isOn: $model.showLabel) - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .padding(.all, ODSSpacing.m) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ProgressIndicator/ProgressBarVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ProgressIndicator/ProgressBarVariant.swift index bb0f21a9..ffa73df8 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ProgressIndicator/ProgressBarVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ProgressIndicator/ProgressBarVariant.swift @@ -68,7 +68,7 @@ struct ProgressBarVariant: View { } } -class ProgressBarVariantModel: ObservableObject { +final class ProgressBarVariantModel: ObservableObject { // ====================== // MARK: Store properties @@ -109,7 +109,7 @@ private struct ProgressBarVariantOptions: View { } Toggle("screens.components.progress_indicators.toggle.current_value", isOn: $model.showCurrentValue) } - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .padding(.all, ODSSpacing.m) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ProgressIndicator/ProgressIndicatorComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ProgressIndicator/ProgressIndicatorComponent.swift index 12dac5dd..4f60e433 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ProgressIndicator/ProgressIndicatorComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ProgressIndicator/ProgressIndicatorComponent.swift @@ -10,13 +10,13 @@ import OrangeDesignSystem import SwiftUI struct ProgressIndicatorComponent: Component { - let title: String + let name: String let imageName: String let description: String let variants: AnyView init() { - title = °°"screens.components.progress_indicators.title" + name = °°"screens.components.progress_indicators.title" imageName = "Progress indicator" description = °°"screens.components.progress_indicators.description" variants = AnyView(ProgressIndicatorVariants()) @@ -26,12 +26,12 @@ struct ProgressIndicatorComponent: Component { private struct ProgressIndicatorVariants: View { var body: some View { - VariantEntryItem(title: °°"screens.components.progress_indicators.progress_bar.title", technicalElement: "ProgressView(value:, total:)") { + VariantEntryItem(title: "screens.components.progress_indicators.progress_bar.title", technicalElement: "ProgressView(value:, total:)") { ProgressBarVariant(model: ProgressBarVariantModel()) .navigationTitle("screens.components.progress_indicators.progress_bar.title") } - VariantEntryItem(title: °°"screens.components.progress_indicators.activity_bar.title", technicalElement: "ProgressView()") { + VariantEntryItem(title: "screens.components.progress_indicators.activity_bar.title", technicalElement: "ProgressView()") { ActivityIndicatorVariant(model: ActivityIndicatorModel()) .navigationTitle("screens.components.progress_indicators.activity_bar.title") } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Sliders/SliderComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Sliders/SliderComponent.swift index fcdc8fef..73cff656 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Sliders/SliderComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Sliders/SliderComponent.swift @@ -10,13 +10,13 @@ import OrangeDesignSystem import SwiftUI struct SliderComponent: Component { - let title: String + let name: String let imageName: String let description: String let variants: AnyView init() { - title = °°"screens.components.sliders.title" + name = °°"screens.components.sliders.title" imageName = "Slider" description = °°"screens.components.sliders.description" variants = AnyView(SliderVariants()) @@ -30,7 +30,7 @@ struct SliderVariants: View { // ========== var body: some View { - VariantEntryItem(title: °°"screens.components.sliders.slider.title", technicalElement: "ODSSlider()") { + VariantEntryItem(title: "screens.components.sliders.slider.title", technicalElement: "ODSSlider()") { SliderVariant(model: SliderVariantModel()) .navigationTitle("screens.components.sliders.slider.title") } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Sliders/SlidersVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Sliders/SlidersVariant.swift index 06a81462..33468540 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Sliders/SlidersVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/Sliders/SlidersVariant.swift @@ -39,7 +39,7 @@ struct SliderVariant: View { VStack { if model.showValue { Text(String(format: "%.2f", value)) - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .frame(maxWidth: .infinity, alignment: .leading) .accessibilityHidden(true) } @@ -79,7 +79,7 @@ struct SliderVariant: View { } } -class SliderVariantModel: ObservableObject { +final class SliderVariantModel: ObservableObject { // ====================== // MARK: Store properties @@ -118,7 +118,7 @@ struct SliderVariantOptions: View { Toggle("screens.components.sliders.sample.display_value", isOn: $model.showValue) Toggle("screens.components.sliders.sample.stepped", isOn: $model.stepped) } - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .padding(.vertical, ODSSpacing.m) .padding(.horizontal, ODSSpacing.m) } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TabBar/TabBarComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TabBar/TabBarComponent.swift index c9cc69cb..7ea2af1a 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TabBar/TabBarComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TabBar/TabBarComponent.swift @@ -10,13 +10,13 @@ import OrangeDesignSystem import SwiftUI struct TabBarComponent: Component { - let title: String + let name: String let imageName: String let description: String let variants: AnyView init() { - title = °°"screens.components.bars.tabs.title" + name = °°"screens.components.bars.tabs.title" imageName = "Tab bar" description = °°"screens.components.bars.tabs.description" variants = AnyView(TabBarVariants()) @@ -26,7 +26,7 @@ struct TabBarComponent: Component { private struct TabBarVariants: View { var body: some View { - VariantEntryItem(title: °°"screens.components.bars.tabs.title", technicalElement: "TabView") { + VariantEntryItem(title: "screens.components.bars.tabs.title", technicalElement: "TabView") { TabBarVariant(model: TabBarVariantModel()) .navigationTitle("screens.components.bars.tabs.title") } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TabBar/TabBarVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TabBar/TabBarVariant.swift index 4770cffe..1abbddaf 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TabBar/TabBarVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TabBar/TabBarVariant.swift @@ -127,13 +127,14 @@ struct TabBarVariantOptions: View { in: 2 ... model.numberOfItems) } .padding(.horizontal, ODSSpacing.m) - .odsFont(.bodyBold) + .odsFont(.bodyLBold) - ODSChipPicker(title: °°"screens.components.bars.tabs.options_picker.hint", - selection: $model.badgeOption, - chips: TabBarVariantModel.BadgeOption.chips) + ODSChoiceChipPicker( + title: Text("screens.components.bars.tabs.options_picker.hint"), + chips: TabBarVariantModel.BadgeOption.chips, + selection: $model.badgeOption) } - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) .padding(.vertical, ODSSpacing.m) } } @@ -142,7 +143,7 @@ struct TabBarVariantOptions: View { // MARK: - Tab Bar Variant Model // ============================= -class TabBarVariantModel: ObservableObject { +final class TabBarVariantModel: ObservableObject { // ====================== // MARK: Store properties @@ -218,22 +219,22 @@ class TabBarVariantModel: ObservableObject { case count case text - var description: String { + var description: LocalizedStringKey { switch self { case .none: - return °°"shared.none" + return "shared.none" case .count: - return °°"screens.components.bars.tabs.badge.description.count" + return "screens.components.bars.tabs.badge.description.count" case .text: - return °°"shared.text" + return "shared.text" } } - var chip: ODSChip { - ODSChip(self, text: description) + var chip: ODSChoiceChip { + .init(text: Text(description), value: self) } - static var chips: [ODSChip] { + static var chips: [ODSChoiceChip] { Self.allCases.map { $0.chip } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TextFields/CapitalizedTextInputsVariant.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TextFields/CapitalizedTextInputsVariant.swift index 575362ad..d548bc1a 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TextFields/CapitalizedTextInputsVariant.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TextFields/CapitalizedTextInputsVariant.swift @@ -43,7 +43,9 @@ private struct CapitalizedTextInputsVariant: View { textField .textInputAutocapitalization(model.selectedCapitalizationType.textInputAutocapitalization) .odsTextFieldStyle() - .id(model.selectedCapitalizationType.description) + // Need id, to be shure the text filed is recreated when + // CapitalizationType change in selection. + .id(model.selectedCapitalizationType) .padding(.horizontal, ODSSpacing.s) .padding(.top, ODSSpacing.m) .focused($isFocused) @@ -85,7 +87,7 @@ private struct CapitalizedTextInputsVariant: View { } } -private class CapitalizedTextInputsVariantModel: ObservableObject { +private final class CapitalizedTextInputsVariantModel: ObservableObject { // ==================== // MARK: Internal types @@ -106,20 +108,20 @@ private class CapitalizedTextInputsVariantModel: ObservableObject { } } - var description: String { + var description: LocalizedStringKey { switch self { - case .never: return °°"screens.components.textfields.variants.inputs.never" - case .characters: return °°"screens.components.textfields.variants.inputs.characters" - case .words: return °°"screens.components.textfields.variants.inputs.words" - case .sentences: return °°"screens.components.textfields.variants.inputs.sentences" + case .never: return "screens.components.textfields.variants.inputs.never" + case .characters: return "screens.components.textfields.variants.inputs.characters" + case .words: return "screens.components.textfields.variants.inputs.words" + case .sentences: return "screens.components.textfields.variants.inputs.sentences" } } - var chip: ODSChip { - ODSChip(self, text: description) + var chip: ODSChoiceChip { + .init(text: Text(description), value: self) } - static var chips: [ODSChip] { + static var chips: [ODSChoiceChip] { Self.allCases.map { $0.chip } } } @@ -191,9 +193,10 @@ private struct CapitalizedTextInputsVariantOptions: View { var body: some View { VStack(spacing: ODSSpacing.none) { - ODSChipPicker(title: °°"screens.components.textfields.variants.inputs.options.capitalization", - selection: $model.selectedCapitalizationType, - chips: CapitalizedTextInputsVariantModel.CapitalizationType.chips) + ODSChoiceChipPicker( + title: Text("screens.components.textfields.variants.inputs.options.capitalization"), + chips: CapitalizedTextInputsVariantModel.CapitalizationType.chips, + selection: $model.selectedCapitalizationType) .padding(.vertical, ODSSpacing.s) } .padding(.top, ODSSpacing.s) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TextFields/TextFieldComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TextFields/TextFieldComponent.swift index b03f5b8c..53c6ed54 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TextFields/TextFieldComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/TextFields/TextFieldComponent.swift @@ -11,22 +11,13 @@ import SwiftUI // MARK: Component struct TextFieldComponent: Component { - - // ====================== - // MARK: Store properties - // ====================== - - let title: String + let name: String let imageName: String let description: String let variants: AnyView - // ================= - // MARK: Initilizers - // ================= - init() { - title = °°"shared.text_field" + name = °°"shared.text_field" imageName = "Text edit menu" description = °°"screens.components.textfields.description" variants = AnyView(TextFieldVariants()) @@ -36,15 +27,15 @@ struct TextFieldComponent: Component { struct TextFieldVariants: View { var body: some View { - VariantEntryItem(title: °°"screens.components.textfields.variants.secure", technicalElement: "SecureField()") { + VariantEntryItem(title: "screens.components.textfields.variants.secure", technicalElement: "SecureField()") { SecureTextFieldVariant().navigationTitle("screens.components.textfields.variants.secure") } - VariantEntryItem(title: °°"shared.text_field", technicalElement: "TextField()") { + VariantEntryItem(title: "shared.text_field", technicalElement: "TextField()") { TextFieldVariant().navigationTitle("shared.text_field") } - VariantEntryItem(title: °°"screens.components.textfields.variants.editor", technicalElement: "TextEditor()") { + VariantEntryItem(title: "screens.components.textfields.variants.editor", technicalElement: "TextEditor()") { TextEditorVariant().navigationTitle("screens.components.textfields.variants.editor") } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ToolBar/ToolBarComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ToolBar/ToolBarComponent.swift index 7268c602..e62f1537 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ToolBar/ToolBarComponent.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ToolBar/ToolBarComponent.swift @@ -14,13 +14,13 @@ import SwiftUI // ========================== struct ToolBarComponent: Component { - let title: String + let name: String let imageName: String let description: String let variants: AnyView init() { - title = °°"screens.components.bars.tools.title" + name = °°"screens.components.bars.tools.title" imageName = "Bars - tool" description = °°"screens.components.bars.tools.description" variants = AnyView(ToolBarVariants()) @@ -34,7 +34,7 @@ struct ToolBarComponent: Component { private struct ToolBarVariants: View { var body: some View { - VariantEntryItem(title: °°"screens.components.bars.tools.title", technicalElement: "odsToolBar()") { + VariantEntryItem(title: "screens.components.bars.tools.title", technicalElement: "odsToolBar()") { ToolBarVariantHome(model: ToolBarVariantModel()) .navigationTitle("screens.components.bars.tools.title") } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ToolBar/ToolBarVariantOptions.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ToolBar/ToolBarVariantOptions.swift index 7bb3bc57..338d1c37 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ToolBar/ToolBarVariantOptions.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Pages/ToolBar/ToolBarVariantOptions.swift @@ -13,7 +13,7 @@ import SwiftUI // MARK: - Tab Bar Variant Model // ============================= -class ToolBarVariantModel: ObservableObject { +final class ToolBarVariantModel: ObservableObject { // ================ // MARK: Properties @@ -38,20 +38,20 @@ class ToolBarVariantModel: ObservableObject { case label case icon - var description: String { + var description: LocalizedStringKey { switch self { case .label: - return °°"shared.label" + return "shared.label" case .icon: - return °°"shared.icon" + return "shared.icon" } } - var chip: ODSChip { - ODSChip(self, text: description) + var chip: ODSChoiceChip { + .init(text: Text(description), value: self) } - static var chips: [ODSChip] { + static var chips: [ODSChoiceChip] { Self.allCases.map { $0.chip } } } @@ -151,9 +151,10 @@ struct ToolBarVariantOptions: View { var body: some View { VStack(spacing: ODSSpacing.m) { - ODSChipPicker(title: °°"screens.components.bars.tools.picker_hint", - selection: $model.itemType, - chips: ToolBarVariantModel.ItemType.chips) + ODSChoiceChipPicker( + title: Text("screens.components.bars.tools.picker_hint"), + chips: ToolBarVariantModel.ItemType.chips, + selection: $model.itemType) switch model.itemType { case .label: @@ -168,6 +169,6 @@ struct ToolBarVariantOptions: View { .padding(.horizontal, ODSSpacing.m) } } - .odsFont(.bodyBold) + .odsFont(.bodyLBold) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Template/Component.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Template/Component.swift index d3856c4f..795f8a82 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Template/Component.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Template/Component.swift @@ -9,8 +9,12 @@ import SwiftUI protocol Component { - var title: String { get } + var name: String { get } var imageName: String { get } var description: String { get } var variants: AnyView { get } } + +extension Component { + var id: String { name } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Template/ComponentPage.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Template/ComponentPage.swift index d7d03d59..dda4b821 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Template/ComponentPage.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Template/ComponentPage.swift @@ -31,7 +31,10 @@ struct ComponentPage: View { .accessibilityHidden(true) VStack(alignment: .leading, spacing: ODSSpacing.m) { - ComponentDescription(text: component.description) + Text(component.description) + .odsFont(.bodyLRegular) + .fixedSize(horizontal: false, vertical: true) + .padding(.top, ODSSpacing.xs) VariantsTitle() } @@ -48,87 +51,23 @@ struct ComponentPage: View { .listStyle(.plain) .padding(.top, ODSSpacing.none) .padding(.horizontal, ODSSpacing.none) - .navigationTitle(component.title) + .navigationTitle(component.name) .navigationbarMenuForThemeSelection() } } -private struct ComponentDescription: View { - let text: String - var body: some View { - Text(text) - .odsFont(.bodyRegular) - .fixedSize(horizontal: false, vertical: true) - .padding(.top, ODSSpacing.xs) - } -} - -struct VariantsTitle: View { - var body: some View { - Text("misc.variants") - .odsFont(.title1) - .accessibilityAddTraits(.isHeader) - } -} - -struct VariantEntryItem: View where VariantPage: View { - - // ======================= - // MARK: Stored Properties - // ======================= - - private let title: Text - private let technicalElement: Text - private let showThemeSelectionInNavigationBar: Bool - private let variantPage: () -> VariantPage - - // ================= - // MARK: Initializer - // ================= - - init( - title: String, - technicalElement: String, - showThemeSelectionInNavigationBar: Bool = true, - @ViewBuilder variantPage: @escaping () -> VariantPage) - { - self.title = Text(title) - self.technicalElement = Text(technicalElement) - self.showThemeSelectionInNavigationBar = showThemeSelectionInNavigationBar - self.variantPage = variantPage - } - - // ========== - // MARK: Body - // ========== - - var body: some View { - NavigationLink { - if showThemeSelectionInNavigationBar { - variantPage().navigationbarMenuForThemeSelection() - } else { - variantPage() - } - } label: { - ODSListItem(title: title, subtitle: technicalElement, leading: .icon(Image(systemName: "play.circle"))) - } - .odsListItemStyle(showSeparator: false) - } -} - #if DEBUG struct ComponentPage_Previews: PreviewProvider { struct TestComponent: Component { - let title: String + let name: String let imageName: String let description: String let variants: AnyView init() { - title = "Test" + name = "Test" imageName = "Cards_1" description = "This is a long text to illustrate the description area" - variants = AnyView(Variants()) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Template/Variants.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Template/Variants.swift new file mode 100644 index 00000000..97fc8659 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Components/Template/Variants.swift @@ -0,0 +1,63 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import OrangeDesignSystem +import SwiftUI + +struct VariantsTitle: View { + var body: some View { + Text("misc.variants") + .odsFont(.titleL) + .accessibilityAddTraits(.isHeader) + } +} + +struct VariantEntryItem: View where VariantPage: View { + + // ======================= + // MARK: Stored Properties + // ======================= + + private let title: LocalizedStringKey + private let technicalElement: String + private let showThemeSelectionInNavigationBar: Bool + private let variantPage: () -> VariantPage + + // ================= + // MARK: Initializer + // ================= + + init( + title: LocalizedStringKey, + technicalElement: String, + showThemeSelectionInNavigationBar: Bool = true, + @ViewBuilder variantPage: @escaping () -> VariantPage) + { + self.title = title + self.technicalElement = technicalElement + self.showThemeSelectionInNavigationBar = showThemeSelectionInNavigationBar + self.variantPage = variantPage + } + + // ========== + // MARK: Body + // ========== + + var body: some View { + NavigationLink { + if showThemeSelectionInNavigationBar { + variantPage().navigationbarMenuForThemeSelection() + } else { + variantPage() + } + } label: { + ODSListItem(title: Text(title), subtitle: Text(verbatim: technicalElement), leading: .icon(Image(systemName: "play.circle"))) + } + .odsListItemStyle(showSeparator: false) + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/GuidelinesList.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/GuidelinesList.swift index fa507245..5e4e93d9 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/GuidelinesList.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/GuidelinesList.swift @@ -35,12 +35,12 @@ struct GuidelinesList: View { NavigationView { ScrollView { LazyVGrid(columns: columns, spacing: ODSSpacing.xs) { - ForEach(guidelines, id: \.title) { guideline in + ForEach(guidelines, id: \.id) { guideline in NavigationLink { GuidelinePage(guideline: guideline) } label: { ODSCardVerticalImageFirst( - title: Text(guideline.title), + title: Text(guideline.name), imageSource: .image(imageFrom(resourceName: guideline.imageName))) } } @@ -52,6 +52,7 @@ struct GuidelinesList: View { GuidelinePage(guideline: guidelines[0]) // Why ? } + .navigationViewStyle(.stack) } // ==================== diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/ColorsGuideline.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/ColorsGuideline.swift index d995eee7..c7517601 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/ColorsGuideline.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/ColorsGuideline.swift @@ -10,13 +10,13 @@ import OrangeDesignSystem import SwiftUI struct ColorsGuideline: Guideline { - let title: String + let name: String let imageName: String let description: String let pageDescription: AnyView init() { - title = °°"screens.guidelines.colors.title" + name = °°"screens.guidelines.colors.title" imageName = "Colour" description = °°"screens.guidelines.colors.description" pageDescription = AnyView(ColorPageDescription()) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/ColorsPage.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/ColorsPage.swift index 386a49eb..a26f21d0 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/ColorsPage.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/ColorsPage.swift @@ -9,7 +9,7 @@ import OrangeDesignSystem import SwiftUI -class ScreenState: ObservableObject { +final class ScreenState: ObservableObject { @Published var colorScheme: ColorScheme = .light } @@ -18,7 +18,7 @@ struct ColorPageDescription: View { let screenState = ScreenState() var body: some View { - VariantEntryItem(title: °°"screens.guidelines.colors.color_palette.title", technicalElement: "ODSColorPalette()") { + VariantEntryItem(title: "screens.guidelines.colors.color_palette.title", technicalElement: "ODSColorPalette()") { ColorList().environmentObject(self.screenState) .navigationTitle("screens.guidelines.colors.color_palette.title") } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/Views/ColorDetail.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/Views/ColorDetail.swift index 5ce613a8..29ea5ffc 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/Views/ColorDetail.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/Views/ColorDetail.swift @@ -36,14 +36,14 @@ struct ColorDetail: View { .frame(width: 300, height: 150) VStack(alignment: .leading, spacing: 3) { - Text(colorDescription.assetName).odsFont(.headline) - Text(colorDescription.uiColor.rgba(colorScheme: screenState.colorScheme).displayableValue).odsFont(.caption1Regular) - Text(colorDescription.uiColor.hexa(colorScheme: screenState.colorScheme)).odsFont(.caption1Regular) + Text(colorDescription.assetName).odsFont(.headlineS) + Text(colorDescription.uiColor.rgba(colorScheme: screenState.colorScheme).displayableValue).odsFont(.labelMRegular) + Text(colorDescription.uiColor.hexa(colorScheme: screenState.colorScheme)).odsFont(.labelMRegular) Text("misc.usage") - .odsFont(.headline) + .odsFont(.headlineS) .padding(.top, ODSSpacing.l) - Text(usage).odsFont(.caption1Regular) + Text(usage).odsFont(.labelMRegular) } .padding(EdgeInsets(top: ODSSpacing.s, leading: ODSSpacing.m, bottom: ODSSpacing.l, trailing: ODSSpacing.m)) } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/Views/ColorIllustration.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/Views/ColorIllustration.swift index 1bde8cf1..d8ca49e7 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/Views/ColorIllustration.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Colors/Views/ColorIllustration.swift @@ -40,7 +40,7 @@ struct ColorIllustration: View { .font(.system(.caption, design: .monospaced)) Text(colorDescription.uiColor.hexa(colorScheme: screenState.colorScheme)) - .odsFont(.caption1Regular) + .odsFont(.labelMRegular) } .background(Color(uiColor: UIColor.systemBackground)) .colorScheme(self.screenState.colorScheme) @@ -78,7 +78,7 @@ struct ColorName: View { var body: some View { if let colorName = colorName { - Text(colorName).odsFont(.headline) + Text(colorName).odsFont(.headlineS) } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Spacings/SpacingsGuideline.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Spacings/SpacingsGuideline.swift index 8377dd92..db60d4d0 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Spacings/SpacingsGuideline.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Spacings/SpacingsGuideline.swift @@ -10,13 +10,13 @@ import OrangeDesignSystem import SwiftUI struct SpacingsGuideline: Guideline { - let title: String + let name: String let imageName: String let description: String let pageDescription: AnyView init() { - title = °°"screens.guidelines.spacings.title" + name = °°"screens.guidelines.spacings.title" imageName = "Spacing" description = °°"screens.guidelines.spacings.description" pageDescription = AnyView(SpacingsPageDescription()) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Spacings/SpacingsPage.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Spacings/SpacingsPage.swift index 67304544..bcaf2286 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Spacings/SpacingsPage.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Spacings/SpacingsPage.swift @@ -51,16 +51,16 @@ private struct SpacingItem: View { SpacingVisual(spacing: spacing) VStack(alignment: .leading) { - Text(spacing.name).odsFont(.bodyRegular) + Text(spacing.name).odsFont(.bodyLRegular) .frame(maxWidth: .infinity, alignment: .leading) - Text("\(spacing.rawValue, specifier: "%.0f") px").odsFont(.bodyRegular) + Text("\(spacing.rawValue, specifier: "%.0f") px").odsFont(.bodyLRegular) } Spacer() Text(spacing.ratio) .foregroundColor(Color(UIColor.secondaryLabel)) - .odsFont(.bodyRegular) + .odsFont(.bodyLRegular) } .padding(.top, ODSSpacing.s) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Typography/TypographyGuideline.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Typography/TypographyGuideline.swift index 4201386c..53e31867 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Typography/TypographyGuideline.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Typography/TypographyGuideline.swift @@ -10,13 +10,13 @@ import OrangeDesignSystem import SwiftUI struct TypographyGuideline: Guideline { - let title: String + let name: String let imageName: String - let pageDescription: AnyView let description: String + let pageDescription: AnyView init() { - title = °°"screens.guidelines.typographies.title" + name = °°"screens.guidelines.typographies.title" imageName = "Typography" description = °°"screens.guidelines.typographies.description" pageDescription = AnyView(TypographyPageDescription()) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Typography/TypographyPage.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Typography/TypographyPage.swift index 6eb725ed..63813159 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Typography/TypographyPage.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Pages/Typography/TypographyPage.swift @@ -43,32 +43,32 @@ struct TypographyPageDescription: View { extension ODSFontStyle { fileprivate var description: String { switch self { - case .largeTitle: - return "Large Title" - case .title1: - return "Title 1" - case .title2: - return "Title 2" - case .title3: - return "Title 3" - case .headline: - return "Headline" - case .bodyRegular: - return "Body (regular)" - case .bodyBold: - return "Body (bold)" - case .callout: - return "Callout" - case .subhead: - return "Subheadline" - case .footnote: - return "Footnote" - case .caption1Regular: - return "Caption 1 (regular)" - case .caption1Bold: - return "Caption 1 (bold)" - case .caption2: - return "Caption 2" + case .headlineL: + return "Headline L" + case .titleL: + return "Title L" + case .titleM: + return "Title M" + case .titleS: + return "Title S" + case .headlineS: + return "Headline S" + case .bodyLRegular: + return "Body L Regular" + case .bodyLBold: + return "Body L Bold" + case .bodyM: + return "Body M" + case .bodyS: + return "Body S" + case .labelL: + return "Label L" + case .labelMRegular: + return "Label M Regular" + case .labelMBold: + return "Label M Bold" + case .labelS: + return "Label S" } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Template/Guideline.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Template/Guideline.swift index 01c05f4a..6cbed028 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Template/Guideline.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Template/Guideline.swift @@ -9,8 +9,12 @@ import SwiftUI protocol Guideline { - var title: String { get } + var name: String { get } var imageName: String { get } var description: String { get } var pageDescription: AnyView { get } } + +extension Guideline { + var id: String { name } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Template/GuidelinePage.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Template/GuidelinePage.swift index e381208b..be53ebc4 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Template/GuidelinePage.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Guidelines/Template/GuidelinePage.swift @@ -46,7 +46,7 @@ struct GuidelinePage: View { .listStyle(.plain) .padding(.top, ODSSpacing.none) .padding(.horizontal, ODSSpacing.none) - .navigationTitle(guideline.title) + .navigationTitle(guideline.name) .navigationbarMenuForThemeSelection() .background(Color(UIColor.systemBackground)) } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/About/AboutModule.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/About/AboutModule.swift index a17e9b59..2f37477b 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/About/AboutModule.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/About/AboutModule.swift @@ -48,25 +48,24 @@ struct AboutSetup: View { Text("screens.modules.about.description") Text("screens.modules.about.customize") - .odsFont(.headline) + .odsFont(.headlineS) .padding(.top, ODSSpacing.m) .padding(.bottom, ODSSpacing.s) Text("screens.modules.about.mandatory") - ODSChipPicker( - title: °°"screens.modules.about.picker.app_section", - selection: $model.applicationSectionOptions, - allowZeroSelection: true, - chips: AboutModuleModel.ApplicationInformationOption.chips) + ODSFilterChipPicker( + title: Text("screens.modules.about.picker.app_section"), + chips: AboutModuleModel.ApplicationInformationOption.chips, + selection: $model.applicationSectionOptions) .padding(.vertical, ODSSpacing.m) .padding(.horizontal, -ODSSpacing.m) - ODSChipPicker( - title: °°"screens.modules.about.picker.optional_about_items", + ODSFilterChipPicker( + title: Text("screens.modules.about.picker.optional_about_items"), + chips: AboutModuleModel.OptionalAboutItem.chips, selection: $model.optionalAboutItems, - allowZeroSelection: true, - chips: AboutModuleModel.OptionalAboutItem.chips) + placement: .carousel) .padding(.vertical, ODSSpacing.s) .padding(.horizontal, -ODSSpacing.m) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/About/AboutModuleModel.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/About/AboutModuleModel.swift index 833db0ca..ec70ca99 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/About/AboutModuleModel.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/About/AboutModuleModel.swift @@ -14,7 +14,7 @@ import SwiftUI // MARK: - About Module Model // ========================== -class AboutModuleModel: ObservableObject { +final class AboutModuleModel: ObservableObject { // ================== // MARK: - Properties @@ -99,24 +99,24 @@ class AboutModuleModel: ObservableObject { case share case feedback - var description: String { + var description: LocalizedStringKey { switch self { case .version: - return °°"screens.about.app_information.option_description.version" + return "screens.about.app_information.option_description.version" case .description: - return °°"screens.about.app_information.option_description.description" + return "screens.about.app_information.option_description.description" case .share: - return °°"screens.about.app_information.option_description.share" + return "screens.about.app_information.option_description.share" case .feedback: - return °°"screens.about.app_information.option_description.feedback" + return "screens.about.app_information.option_description.feedback" } } - var chip: ODSChip { - ODSChip(self, text: description) + var chip: ODSFilterChip { + .init(text: Text(description), value: self) } - static var chips: [ODSChip] { + static var chips: [ODSFilterChip] { Self.allCases.map { $0.chip } } } @@ -130,22 +130,22 @@ class AboutModuleModel: ObservableObject { case legalInformation case rateTheApp - var description: String { + var description: LocalizedStringKey { switch self { case .appNews: - return °°"screens.about.app_information.option_description.app_news" + return "screens.about.app_information.option_description.app_news" case .legalInformation: - return °°"screens.about.app_information.option_description.legal_information" + return "screens.about.app_information.option_description.legal_information" case .rateTheApp: - return °°"screens.about.app_information.option_description.rate" + return "screens.about.app_information.option_description.rate" } } - var chip: ODSChip { - ODSChip(self, text: description) + var chip: ODSFilterChip { + .init(text: Text(description), value: self) } - static var chips: [ODSChip] { + static var chips: [ODSFilterChip] { Self.allCases.map { $0.chip } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/GridOfSmallCards.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/GridOfSmallCards.swift index 590b645a..544f4484 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/GridOfSmallCards.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/GridOfSmallCards.swift @@ -28,7 +28,7 @@ struct GrifOfSmallCards: View { LazyVGrid(columns: columns, spacing: ODSSpacing.xs) { ForEach(RecipeBook.shared.recipes, id: \.id) { recipe in NavigationLink { - Text("screens.modules.card_collections.texts.bon_app") + Text("shared.bon_app") .navigationBarTitleDisplayMode(.inline) .navigationbarMenuForThemeSelection() } label: { diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/ListOfVerticalImageFirstCard.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/ListOfVerticalImageFirstCard.swift index cd235bda..9fca0e33 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/ListOfVerticalImageFirstCard.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/CardListAndCollection/ListOfVerticalImageFirstCard.swift @@ -26,7 +26,7 @@ struct ListOfVerticalImageFirstCard: View { LazyVGrid(columns: columns, spacing: ODSSpacing.xs) { ForEach(RecipeBook.shared.recipes, id: \.id) { recipe in NavigationLink { - Text("screens.modules.card_collections.texts.bon_app") + Text("shared.bon_app") .navigationBarTitleDisplayMode(.inline) .navigationbarMenuForThemeSelection() } label: { diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModule.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModule.swift index 8b247d00..0ae05b04 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModule.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModule.swift @@ -83,17 +83,17 @@ private struct ListModuleInner: View { } footer: { if optionModel.showFooter { Text("screens.modules.lists.section.footer.text") - .odsFont(.caption2) + .odsFont(.labelS) } } - Section("shared.foods") { - ForEach(dataModel.selectedFoods, id: \.name) { food in - listItem(for: food).odsListItemStyle() - } - .onDelete(perform: dataModel.deleteFood) - .onMove(perform: dataModel.moveFood) + Section("shared.foods") { + ForEach(dataModel.selectedFoods, id: \.name) { food in + listItem(for: food).odsListItemStyle() } + .onDelete(perform: dataModel.deleteFood) + .onMove(perform: dataModel.moveFood) + } } .toolbar { if optionModel.isEditable { diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModuleDataModel.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModuleDataModel.swift index e23165d7..5c56fd54 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModuleDataModel.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModuleDataModel.swift @@ -8,7 +8,7 @@ import SwiftUI -class ListModuleDataModel: ObservableObject { +final class ListModuleDataModel: ObservableObject { // ======================= // MARK: Stored properties diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModuleOptions.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModuleOptions.swift index 50a2e0e4..5d7d0a9d 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModuleOptions.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModuleOptions.swift @@ -25,27 +25,27 @@ struct ListModuleOptions: View { VStack(spacing: ODSSpacing.none) { Toggle(isOn: $model.showHeader) { Text("screens.modules.lists.options.section.header") - .odsFont(.bodyBold) + .odsFont(.bodyLBold) } .padding(.horizontal, ODSSpacing.m) .padding(.vertical, ODSSpacing.s) Toggle(isOn: $model.showFooter) { Text("screens.modules.lists.options.section.footer") - .odsFont(.bodyBold) + .odsFont(.bodyLBold) } .padding(.horizontal, ODSSpacing.m) .padding(.vertical, ODSSpacing.s) - ODSChipPicker( - title: "screens.modules.lists.options.style".🌐, - selection: $model.listStyleOption, - chips: ListStyleOption.chips) + ODSChoiceChipPicker( + title: Text("screens.modules.lists.options.style"), + chips: ListStyleOption.elemnts, + selection: $model.listStyleOption) .padding(.vertical, ODSSpacing.s) Toggle(isOn: $model.isEditable) { Text("screens.modules.lists.options.editable") - .odsFont(.bodyBold) + .odsFont(.bodyLBold) } .padding(.horizontal, ODSSpacing.m) .padding(.vertical, ODSSpacing.s) diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModuleOptionsModel.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModuleOptionsModel.swift index 4d8953f0..a6c3338a 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModuleOptionsModel.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/Lists/ListModuleOptionsModel.swift @@ -16,7 +16,7 @@ enum ListStyleOption: Int, CaseIterable { case insetGrouped case sidebar - var description: String { + var description: LocalizedStringKey { switch self { case .plain: return "screens.modules.lists.options.style.plain" @@ -31,16 +31,16 @@ enum ListStyleOption: Int, CaseIterable { } } - var chip: ODSChip { - ODSChip(self, text: description.🌐) + var chip: ODSChoiceChip { + .init(text: Text(description), value: self) } - static var chips: [ODSChip] { + static var elemnts: [ODSChoiceChip] { Self.allCases.map { $0.chip } } } -class ListModuleOptionsModel: ObservableObject { +final class ListModuleOptionsModel: ObservableObject { // ======================= // MARK: Stored properties diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/ModulesList.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/ModulesList.swift index 5298dc57..39e46a53 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/ModulesList.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Screens/Modules/ModulesList.swift @@ -61,6 +61,7 @@ struct ModulesList: View { .navigationTitle("shared.modules") .navigationbarMenuForThemeSelection() } + .navigationViewStyle(.stack) } // ==================== diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/Domain/Recipes/RecipesBook.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/Domain/Recipes/RecipesBook.swift index 808508a9..3db25dbd 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/Domain/Recipes/RecipesBook.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/Domain/Recipes/RecipesBook.swift @@ -18,7 +18,7 @@ struct RecipeBook { static let shared: RecipeBook = .init() } -struct Food: Equatable { +struct Food: Equatable, Hashable { let id: Int let name: String let image: URL? diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/Domain/Recipes/RecipesLoader.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/Domain/Recipes/RecipesLoader.swift index 6f5946da..ea467eb4 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/Domain/Recipes/RecipesLoader.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/Domain/Recipes/RecipesLoader.swift @@ -8,7 +8,7 @@ import Foundation -class RecipeLoader { +final class RecipeLoader { // ================= // MARK: Initializer diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/UI/Themes/ThemeSelectionView.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/UI/Themes/ThemeSelectionView.swift index 2244a445..1031ce2a 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/UI/Themes/ThemeSelectionView.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/UI/Themes/ThemeSelectionView.swift @@ -16,7 +16,7 @@ import SwiftUI /// demo application. /// It also stores the current theme, selected by user. /// -class ThemeProvider: ObservableObject { +final class ThemeProvider: ObservableObject { // ======================= // MARK: Stored Properties @@ -107,7 +107,7 @@ struct ThemeSelectionButton: View { // MARK: - Hot switch Warning /// Will be removed when hot switch will be supported -class HotSwitchWarningIndicator: ObservableObject { +final class HotSwitchWarningIndicator: ObservableObject { @Published var showAlert: Bool = false } @@ -131,7 +131,7 @@ struct HotSwhitchIndicatorModifier: ViewModifier { func body(content: Content) -> some View { content .alert("Warning", isPresented: $hotSwitchWarningIndicator.showAlert) {} message: { - Text("You need to restart application to see design with new theme").odsFont(.title2) + Text("You need to restart application to see design with new theme").odsFont(.titleM) } } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/UI/ToastView.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/UI/ToastView.swift index d894c1d1..e56df619 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/UI/ToastView.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Utils/UI/ToastView.swift @@ -14,8 +14,9 @@ struct Toastable: View where Content: View { // ======================= // MARK: Stored Properties // ======================= + @Binding var showText: String? - private let content: () -> Content + let content: () -> Content // ========== // MARK: Body @@ -33,6 +34,7 @@ struct Toast: View { // ======================= // MARK: Stored Properties // ======================= + @Binding var showText: String? // ========== @@ -41,7 +43,9 @@ struct Toast: View { var body: some View { if let showText = self.showText { Text(showText) - .padding().background(Color(UIColor.systemGray4)).clipShape(Capsule()) + .padding(.vertical, ODSSpacing.xs) + .padding(.horizontal, ODSSpacing.s) + .background(Color(UIColor.systemGray4)).clipShape(Capsule()) .onAppear { DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.showText = nil diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/BottomSheetComponent.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/BottomSheetComponent.swift deleted file mode 100644 index a0bb6aab..00000000 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemo/Views/Components/Pages/BottomSheet/BottomSheetComponent.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// Software Name: Orange Design System (iOS) -// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA -// SPDX-License-Identifier: MIT -// -// This software is distributed under the MIT license. -// - -import BottomSheet -import OrangeDesignSystem -import SwiftUI - -struct BottomSheetComponent: Component { - let title: String - let imageName: String - let description: String - let variants: AnyView - - init() { - title = "screens.components.bottom_sheets.title" - imageName = "BottomSheet" - description = "screens.components.bottom_sheets.description" - variants = AnyView(BottomSheetVariants()) - } -} - -struct BottomSheetVariants: View { - var body: some View { - VariantEntryItem(text: °°"screens.components.bottom_sheets.expanding", technicalElement: ".odsBottomSheetExpanding()") { - ExpandingBottomSheetVariantHome(model: BottomSheetVariantModel()) - .navigationTitle("screens.components.bottom_sheets.expanding") - } - - VariantEntryItem(text: °°"screens.components.bottom_sheets.standard", technicalElement: ".odsBottomSheetStandard()") { - StandardBottomSheetVariant() - .navigationTitle("screens.components.bottom_sheets.standard") - } - } -} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/AppNewsListViewModelTests.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/AppNewsListViewModelTests.swift new file mode 100644 index 00000000..664db4ec --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/AppNewsListViewModelTests.swift @@ -0,0 +1,176 @@ +// +// Software Name: Orange Design System (iOS) +// SPDX-FileCopyrightText: Copyright (c) 2021 - 2023 Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license. +// + +import Foundation +@testable import OrangeDesignSystem +import XCTest + +final class AppNewsListViewModelTests: XCTestCase { + + // ================== + // MARK: - Test cases + // ================== + + func testAppNewsListViewModel_newInstance() { + // Given + let viewModel = AppNewsListViewModel(fromFile: "") + + // When + let state = viewModel.releaseDescriptions + + // Then + var testPassed = false + switch state { + case .loading: + testPassed = true + default: + testPassed = false + } + XCTAssertTrue(testPassed) + } + + func testAppNewsListViewModel_load_noExistingFile() { + // Given + let viewModel = AppNewsListViewModel(fromFile: "polynectar.recette") + + // When + viewModel.load() + let state = viewModel.releaseDescriptions + + // Then + switch state { + case let .error(error): + XCTAssertTrue(error == AppNewsListViewModel.Error.resourceNotFound, "Supposed to have an AppNewsListViewModel.Error.resourceNotFound instead of \(error)") + default: + XCTFail("Supposed to have an AppNewsListViewModel.Error.resourceNotFound") + } + } + + func testAppNewsListViewModel_load_notStringFile() { + // Given + let viewModel = AppNewsListViewModel(fromFile: stubPath(for: "NotStringFile", ofType: "jpg")) + + // When + viewModel.load() + let state = viewModel.releaseDescriptions + + // Then + switch state { + case let .error(error): + XCTAssertTrue(error == AppNewsListViewModel.Error.rawParsingFailure, "Supposed to have an AppNewsListViewModel.Error.rawParsingFailure instead of \(error)") + default: + XCTFail("Supposed to have an AppNewsListViewModel.Error.rawParsingFailure") + } + } + + func testAppNewsListViewModel_load_badJSONContent() { + // Given + let viewModel = AppNewsListViewModel(fromFile: stubPath(for: "FileWithoutJson", ofType: "json")) + + // When + viewModel.load() + let state = viewModel.releaseDescriptions + + // Then + switch state { + case let .error(error): + XCTAssertTrue(error == AppNewsListViewModel.Error.jsonParsingFailure, "Supposed to have an AppNewsListViewModel.Error.jsonParsingFailure instead of \(error)") + default: + XCTFail("Supposed to have an AppNewsListViewModel.Error.jsonParsingFailure") + } + } + + // "Not compliant" means date without expected format in JSON for example, i.e. requirements unmatched in view model parsing + func testAppNewsListViewModel_load_notCompliantObjects_badDateFormat() { + // Given + let viewModel = AppNewsListViewModel(fromFile: stubPath(for: "AppNewsMock_notCompliant_badDateFormat", ofType: "json")) + + // When + viewModel.load() + let state = viewModel.releaseDescriptions + + // Then + switch state { + case let .error(error): + XCTAssertTrue(error == AppNewsListViewModel.Error.jsonParsingFailure, "Supposed to have an AppNewsListViewModel.Error.jsonParsingFailure instead of \(error)") + default: + XCTFail("Supposed to have an AppNewsListViewModel.Error.jsonParsingFailure") + } + } + + func testAppNewsListViewModel_load_suitableFile() { + // Given + let jsonRawRelease1 = """ + { + "version": "0.15.0", + "date": "2023-11-14", + "news": "Add internationalization support." + } + """ + let expectedRelease1 = decode(from: jsonRawRelease1)! + + let jsonRawRelease2 = """ + { + "version": "0.14.0", + "date": "2023-10-09", + "news": "Update some API ᕙ(`▿´)ᕗ" + } + """ + let expectedRelease2 = decode(from: jsonRawRelease2)! + + let jsonRawRelease3 = """ + { + "version": "0.13.1", + "date": "2023-09-13", + "news": "Add more ✨ in your tests" + } + """ + let expectedRelease3 = decode(from: jsonRawRelease3)! + + // AppNewsMock.json must match the objects above of coruse + let viewModel = AppNewsListViewModel(fromFile: stubPath(for: "AppNewsMock", ofType: "json")) + + // When + viewModel.load() + let state = viewModel.releaseDescriptions + + // Then + switch state { + case let .loaded(releaseDescription): + XCTAssertTrue(releaseDescription.count == 3) + XCTAssertEqual(expectedRelease1.version, releaseDescription[0].version) + XCTAssertEqual(expectedRelease1.date, releaseDescription[0].date) + XCTAssertEqual(expectedRelease1.news, releaseDescription[0].news) + XCTAssertEqual(expectedRelease2.version, releaseDescription[1].version) + XCTAssertEqual(expectedRelease2.date, releaseDescription[1].date) + XCTAssertEqual(expectedRelease2.news, releaseDescription[1].news) + XCTAssertEqual(expectedRelease3.version, releaseDescription[2].version) + XCTAssertEqual(expectedRelease3.date, releaseDescription[2].date) + XCTAssertEqual(expectedRelease3.news, releaseDescription[2].news) + default: + XCTFail("Supposed to have some payload, not loading nor error state") + } + } + + private func decode(from rawJSON: String) -> AboutReleaseDescription? { + let dateFormatter = DateFormatter.formatter(for: "yyyy-MM-dd") + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .formatted(dateFormatter) + guard let decodedRelease = try? decoder.decode(AboutReleaseDescription.self, from: Data(rawJSON.utf8)) else { + fatalError("Not possible to decoded raw JSON for tests") + } + return decodedRelease + } + + func stubPath(for name: String, ofType ext: String) -> String { + guard let url = Bundle(for: AppNewsListViewModelTests.self).path(forResource: name, ofType: ext) else { + fatalError("Stub file '\(name).\(ext)' not found!") + } + return url + } +} diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/CacheTests.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/CacheTests.swift index b6233734..0f7e7916 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/CacheTests.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/CacheTests.swift @@ -11,11 +11,11 @@ import XCTest class CacheTests: XCTestCase { - func testSetValueSucceeds_dateFormatter() { + func testSetValueSucceeds_dateFormatter_localeDateTimeConfiguration() { // Given - let (key1, value1) = firstKeyValuePair() - let (key2, value2) = secondKeyValuePair() - let cache = Cache() + let (key1, value1) = firstKeyValuePairForLocaleDateTimeConfiguration() + let (key2, value2) = secondKeyValuePairForLocaleDateTimeConfiguration() + let cache = Cache() // When cache[key1] = value1 @@ -28,11 +28,45 @@ class CacheTests: XCTestCase { XCTAssertEqual(value2, cache.value(forKey: key2)) } - func testRemoveValueSucceeds_dateFormatter() { + func testSetValueSucceeds_dateFormatter_dateFormatConfiguration() { // Given - let (key1, value1) = firstKeyValuePair() - let (key2, value2) = secondKeyValuePair() - let cache = Cache() + let (key1, value1) = firstKeyValuePairForDateFormatConfiguration() + let (key2, value2) = secondKeyValuePairForDateFormatConfiguration() + let cache = Cache() + + // When + cache[key1] = value1 + cache.setValue(value2, forKey: key2) + + // Then + XCTAssertEqual(value1, cache[key1]) + XCTAssertEqual(value1, cache.value(forKey: key1)) + XCTAssertEqual(value2, cache[key2]) + XCTAssertEqual(value2, cache.value(forKey: key2)) + } + + func testRemoveValueSucceeds_dateFormatter_localeDateTimeConfiguration() { + // Given + let (key1, value1) = firstKeyValuePairForLocaleDateTimeConfiguration() + let (key2, value2) = secondKeyValuePairForLocaleDateTimeConfiguration() + let cache = Cache() + cache[key1] = value1 + cache[key2] = value2 + + // When + cache[key1] = nil + cache.removeValue(forKey: key2) + + // Then + XCTAssertNil(cache[key1]) + XCTAssertNil(cache[key2]) + } + + func testRemoveValueSucceeds_dateFormatter_dateFormatConfiguration() { + // Given + let (key1, value1) = firstKeyValuePairForDateFormatConfiguration() + let (key2, value2) = secondKeyValuePairForDateFormatConfiguration() + let cache = Cache() cache[key1] = value1 cache[key2] = value2 @@ -45,11 +79,27 @@ class CacheTests: XCTestCase { XCTAssertNil(cache[key2]) } - func testRemoveAllValuesSucceeds_dateFormatter() { + func testRemoveAllValuesSucceeds_dateFormatter_localeDateTimeConfiguration() { + // Given + let (key1, value1) = firstKeyValuePairForLocaleDateTimeConfiguration() + let (key2, value2) = secondKeyValuePairForLocaleDateTimeConfiguration() + let cache = Cache() + cache[key1] = value1 + cache[key2] = value2 + + // When + cache.removeAllValues() + + // Then + XCTAssertNil(cache[key1]) + XCTAssertNil(cache[key2]) + } + + func testRemoveAllValuesSucceeds_dateFormatter_dateFormatConfiguration() { // Given - let (key1, value1) = firstKeyValuePair() - let (key2, value2) = secondKeyValuePair() - let cache = Cache() + let (key1, value1) = firstKeyValuePairForDateFormatConfiguration() + let (key2, value2) = secondKeyValuePairForDateFormatConfiguration() + let cache = Cache() cache[key1] = value1 cache[key2] = value2 @@ -65,25 +115,43 @@ class CacheTests: XCTestCase { // MARK: - Helpers // =============== - private func firstKeyValuePair() -> (DateFormatterCache.Configuration, DateFormatter) { + private func firstKeyValuePairForLocaleDateTimeConfiguration() -> (DateFormatterCache.LocaleDateTimeConfiguration, DateFormatter) { let locale = Locale(identifier: "fr") let dateStyle = DateFormatter.Style.short let timeStyle = DateFormatter.Style.none - let key = DateFormatterCache.Configuration(locale: locale, dateStyle: dateStyle, timeStyle: timeStyle) + let key = DateFormatterCache.LocaleDateTimeConfiguration(locale: locale, dateStyle: dateStyle, timeStyle: timeStyle) let value = DateFormatter.formatter(for: locale, dateStyle: dateStyle, timeStyle: timeStyle) return (key, value) } - private func secondKeyValuePair() -> (DateFormatterCache.Configuration, DateFormatter) { + private func secondKeyValuePairForLocaleDateTimeConfiguration() -> (DateFormatterCache.LocaleDateTimeConfiguration, DateFormatter) { let locale = Locale(identifier: "pl") let dateStyle = DateFormatter.Style.medium let timeStyle = DateFormatter.Style.full - let key = DateFormatterCache.Configuration(locale: locale, dateStyle: dateStyle, timeStyle: timeStyle) + let key = DateFormatterCache.LocaleDateTimeConfiguration(locale: locale, dateStyle: dateStyle, timeStyle: timeStyle) let value = DateFormatter.formatter(for: locale, dateStyle: dateStyle, timeStyle: timeStyle) return (key, value) } + + private func firstKeyValuePairForDateFormatConfiguration() -> (DateFormatterCache.DateFormatConfiguration, DateFormatter) { + let dateFormat = "yyyy-MM-dd" + + let key = DateFormatterCache.DateFormatConfiguration(dateFormat: dateFormat) + let value = DateFormatter.formatter(for: dateFormat) + + return (key, value) + } + + private func secondKeyValuePairForDateFormatConfiguration() -> (DateFormatterCache.DateFormatConfiguration, DateFormatter) { + let dateFormat = "dd-mm-yy" + + let key = DateFormatterCache.DateFormatConfiguration(dateFormat: dateFormat) + let value = DateFormatter.formatter(for: dateFormat) + + return (key, value) + } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/Data/AppNewsMock.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/Data/AppNewsMock.json new file mode 100644 index 00000000..3d613324 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/Data/AppNewsMock.json @@ -0,0 +1,18 @@ +[ + { + "version": "0.15.0", + "date": "2023-11-14", + "news": "Add internationalization support." + }, + { + "version": "0.14.0", + "date": "2023-10-09", + "news": "Update some API ᕙ(`▿´)ᕗ" + }, + { + "version": "0.13.1", + "date": "2023-09-13", + "news": "Add more ✨ in your tests" + } +] + diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/Data/AppNewsMock_notCompliant_badDateFormat.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/Data/AppNewsMock_notCompliant_badDateFormat.json new file mode 100644 index 00000000..3fbc5d56 --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/Data/AppNewsMock_notCompliant_badDateFormat.json @@ -0,0 +1,8 @@ +[ + { + "version": "0.15.0", + "date": "01-01-1971", + "news": "Add internationalization support." + } +] + diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/Data/FileWithoutJson.json b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/Data/FileWithoutJson.json new file mode 100644 index 00000000..5878c9be --- /dev/null +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/Data/FileWithoutJson.json @@ -0,0 +1 @@ +No JSON content here diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/Data/NotStringFile.jpg b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/Data/NotStringFile.jpg new file mode 100644 index 00000000..e146e1a6 Binary files /dev/null and b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/Data/NotStringFile.jpg differ diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/DateFormatterCacheTests.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/DateFormatterCacheTests.swift index 0d5b1d8e..c1bead08 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/DateFormatterCacheTests.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/DateFormatterCacheTests.swift @@ -21,7 +21,7 @@ final class DateFormatterCacheTest: XCTestCase { // MARK: - Test cases // ================== - func testFormatterAvailabilityIfNotAdded() { + func testFormatterAvailabilityIfNotAdded_withLocaleDateTimeConfiguration() { // Given let locale = Locale(identifier: "fr") let dateStyle = DateFormatter.Style.short @@ -34,7 +34,18 @@ final class DateFormatterCacheTest: XCTestCase { XCTAssertNil(formatter) } - func testFormatterAvailabilityIfAdded() { + func testFormatterAvailabilityIfNotAdded_withDateFormatConfiguration() { + // Given + let dateFormat = "yyyy-mm-dd" + + // When + let formatter = DateFormatterCache.shared.formatter(for: dateFormat) + + // Then + XCTAssertNil(formatter) + } + + func testFormatterAvailabilityIfAdded_withLocaleDateTimeConfiguration() { // Given let locale = Locale(identifier: "fr") let dateStyle = DateFormatter.Style.short @@ -43,7 +54,7 @@ final class DateFormatterCacheTest: XCTestCase { formatter.locale = locale formatter.dateStyle = dateStyle formatter.timeStyle = timeStyle - DateFormatterCache.shared.store(formatter: formatter) + DateFormatterCache.shared.store(formatter: formatter, using: .localeDateTimeCache) // When guard let storedFormatter = DateFormatterCache.shared.formatter(for: locale, dateStyle: dateStyle, timeStyle: timeStyle) else { @@ -56,4 +67,21 @@ final class DateFormatterCacheTest: XCTestCase { XCTAssertEqual(formatter.dateStyle, storedFormatter.dateStyle) XCTAssertEqual(formatter.timeStyle, storedFormatter.timeStyle) } + + func testFormatterAvailabilityIfAdded_withDateFormatConfiguration() { + // Given + let dateFormat = "dd-mm-yy" + let formatter = DateFormatter() + formatter.dateFormat = dateFormat + DateFormatterCache.shared.store(formatter: formatter, using: .dateFormatCache) + + // When + guard let storedFormatter = DateFormatterCache.shared.formatter(for: dateFormat) else { + XCTFail("Nil value returned, must not") + return + } + + // Then + XCTAssertEqual(formatter.dateFormat, storedFormatter.dateFormat) + } } diff --git a/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/StringLocalizationTests.swift b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/StringLocalizationTests.swift index 7e5fdd51..ecb9fde2 100644 --- a/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/StringLocalizationTests.swift +++ b/OrangeDesignSystemDemo/OrangeDesignSystemDemoTests/StringLocalizationTests.swift @@ -164,14 +164,14 @@ final class StringTests: XCTestCase { XCTAssertTrue(result == someFakeKey, "Value under test is '\(result)'") } - // Expect to have in demo app for "fr" Localizables entry "screens.components.lists.variant.clicked" = "%@ est tappé" + // Expect to have in demo app for "fr" Localizables entry "screens.components.card.alert_2" = "%@ tappé" func testLocalizedWithOneArgWithAppWording() { // Given - let someAppLocalizable = "screens.components.lists.variant.clicked" + let someAppLocalizable = "screens.components.card.alert_2" // When let result = someAppLocalizable.localized(with: "Kenny") // Then - XCTAssertTrue(result == "Kenny est tappé", "Value under test is '\(result)'") + XCTAssertTrue(result == "Kenny tappé", "Value under test is '\(result)'") } // Expect to have in lib app Localizables entry "modules.about.app_information.full_version_text" = "Version %@" @@ -253,14 +253,14 @@ final class StringTests: XCTestCase { XCTAssertTrue(result == someFakeKey, "Value under test is '\(result)'") } - // Expect to have in demo app for "fr" Localizables entry "screens.components.lists.variant.clicked" = "%@ est tappé"; + // Expect to have in demo app for "fr" Localizables entry "screens.components.card.alert_2" = "%@ tappé"; func testArrowWithOneArgWithAppWording() { // Given - let someAppLocalizable = "screens.components.lists.variant.clicked" + let someAppLocalizable = "screens.components.card.alert_2" // When let result = someAppLocalizable <- "Kenny" // Then - XCTAssertTrue(result == "Kenny est tappé", "Value under test is '\(result)'") + XCTAssertTrue(result == "Kenny tappé", "Value under test is '\(result)'") } // Expect to have in lib app Localizables entry "modules.about.app_information.full_version_text" = "Version %@" diff --git a/OrangeDesignSystemDemo/Podfile.lock b/OrangeDesignSystemDemo/Podfile.lock index 54ce3f86..1fd7b894 100644 --- a/OrangeDesignSystemDemo/Podfile.lock +++ b/OrangeDesignSystemDemo/Podfile.lock @@ -25,4 +25,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a42688d2b760b4b407f7880dcb2d181ef4a93967 -COCOAPODS: 1.11.3 +COCOAPODS: 1.13.0 diff --git a/OrangeTheme/Sources/OrangeTheme/OrangeTheme.swift b/OrangeTheme/Sources/OrangeTheme/OrangeTheme.swift index 54714953..95a55cf4 100644 --- a/OrangeTheme/Sources/OrangeTheme/OrangeTheme.swift +++ b/OrangeTheme/Sources/OrangeTheme/OrangeTheme.swift @@ -301,31 +301,31 @@ public struct OrangeThemeFactory { theme.font = { style in switch style { - case .largeTitle: + case .headlineL: return Font.largeTitle.bold() - case .title1: + case .headlineS: + return Font.headline.bold() + case .titleL: return Font.title.bold() - case .title2: + case .titleM: return Font.title2.bold() - case .title3: + case .titleS: return Font.title3.bold() - case .headline: - return Font.headline.bold() - case .bodyRegular: - return Font.body - case .bodyBold: + case .bodyLBold: return Font.body.bold() - case .callout: + case .bodyLRegular: + return Font.body + case .bodyM: return Font.callout - case .subhead: + case .bodyS: return Font.subheadline.bold() - case .footnote: + case .labelL: return Font.footnote - case .caption1Regular: - return Font.caption - case .caption1Bold: + case .labelMBold: return Font.caption.bold() - case .caption2: + case .labelMRegular: + return Font.caption + case .labelS: return Font.caption2 } } diff --git a/Package.swift b/Package.swift index 1ecb32e8..4a3e36bd 100644 --- a/Package.swift +++ b/Package.swift @@ -29,6 +29,7 @@ let package = Package( ), ], dependencies: [ + .package(url: "https://github.com/tevelee/SwiftUI-Flow", .exact("1.1.0")), .package(url: "https://github.com/lucaszischka/BottomSheet", .exact("3.1.0")), .package(url: "https://github.com/Orange-OpenSource/accessibility-statement-lib-ios.git", .exact("1.0.0")) ], @@ -36,7 +37,8 @@ let package = Package( .target( name: "OrangeDesignSystem", dependencies: ["BottomSheet", - .product(name: "DeclarationAccessibility", package: "accessibility-statement-lib-ios") + .product(name: "DeclarationAccessibility", package: "accessibility-statement-lib-ios"), + .product(name: "Flow", package: "SwiftUI-Flow"), ], path: "OrangeDesignSystem/Sources"), .target( diff --git a/README.md b/README.md index 4bb6eaa2..3d70efbc 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,9 @@ ## Status -[![version 0.13.0](https://img.shields.io/badge/version-0.13.0-brightgreen.svg)](CHANGELOG.md) -[![iOS 15.0](https://img.shields.io/badge/iOS-15.0-informational.svg)](https://developer.apple.com/support/app-store "iOS 15 supports") [![MIT license](https://img.shields.io/github/license/Orange-OpenSource/ods-ios)](https://github.com/Orange-OpenSource/ods-ios/blob/qualif/LICENSE) -[![Versions](https://img.shields.io/github/v/release/Orange-OpenSource/ods-ios?label=Last%20version)](https://github.com/Orange-OpenSource/ods-ios/releases) +[![Versions](https://img.shields.io/github/v/release/Orange-OpenSource/ods-ios.svg?label=Last%20version)](https://github.com/Orange-OpenSource/ods-ios/releases) +[![iOS 15.0](https://img.shields.io/badge/iOS-15.0-informational.svg)](https://developer.apple.com/support/app-store "iOS 15 supports") [![Still maintained](https://img.shields.io/maintenance/yes/2023)](https://github.com/Orange-OpenSource/ods-ios/issues?q=is%3Aissue+is%3Aclosed) [![Code size](https://img.shields.io/github/languages/code-size/Orange-OpenSource/ods-ios)](https://github.com/Orange-OpenSource/ods-ios) diff --git a/THIRD-PARTY.md b/THIRD-PARTY.md index 53e4373b..882860d4 100644 --- a/THIRD-PARTY.md +++ b/THIRD-PARTY.md @@ -5,13 +5,13 @@ This document contains the list of Third Party Softwares along with the license Third Party Software may impose additional restrictions and it is the user's responsibility to ensure that they have met the licensing requirements of the relevant license of the Third Party Software they are using. -## In Application +## In Library ### accessibility-statement-lib-ios Copyright 2021-2023 Orange SA. -*accessibility-statement-lib-ios* is distributed under the terms and conditions ot the [Apache 2.0 License](https://opensource.org/license/apache-2-0/). +*accessibility-statement-lib-ios* is distributed under the terms and conditions of the [Apache 2.0 License](https://opensource.org/license/apache-2-0/). You may download the source code on the [following website](https://github.com/Orange-OpenSource/accessibility-statement-lib-ios). ### BottomSheet @@ -20,16 +20,25 @@ Version 3.1.0 Copyright 2021-2022 Lucas Zischka. -*BottomSheet* is distributed under the terms and conditions ot the [MIT License](http://opensource.org/licenses/MIT). +*BottomSheet* is distributed under the terms and conditions of the [MIT License](http://opensource.org/licenses/MIT). You may download the source code on the [following website](https://github.com/lucaszischka/BottomSheet). +### SwiftUI-Flow + +Copyright (c) 2023 Laszlo Teveli +*SwiftUI-Flow* is distributed under the terms and conditions of the [MIT License](http://opensource.org/licenses/MIT). + +You may download the source code on the [following website](https://github.com/tevelee/SwiftUI-Flow). + +## In Application + ### Parma Version 0.3.0 Copyright 2020 Leonard Chan. -*Parma* is distributed under the terms and conditions ot the [MIT License](http://opensource.org/licenses/MIT). +*Parma* is distributed under the terms and conditions of the [MIT License](http://opensource.org/licenses/MIT). You may download the source code on the [following website](https://github.com/dasautoooo/Parma). ## In Project @@ -40,7 +49,7 @@ Version 1.13.0 Copyright 2011 Eloy Durán, Fabio Pelosin, Samuel Giddins, Marius Rackwitz, Kyle Fuller, Boris Bügling, Orta Therox, Olivier Halligon, Danielle Tomlinson, Dimitris Koutsogiorgas, Paul Beusterien, Eric Amorde. -*CocoaPods* is distributed under the terms and conditions ot the [MIT License](http://opensource.org/licenses/MIT). +*CocoaPods* is distributed under the terms and conditions of the [MIT License](http://opensource.org/licenses/MIT). You may download the source code on the [following website](https://github.com/CocoaPods/CocoaPods). ### Fastlane @@ -49,7 +58,7 @@ Version 2.111.0 Copyright 2015-2022 The Fastlane Authors. -*Fastlane* is distributed under the terms and conditions ot the [MIT License](http://opensource.org/licenses/MIT). +*Fastlane* is distributed under the terms and conditions of the [MIT License](http://opensource.org/licenses/MIT). You may download the source code on the [following website](https://github.com/fastlane/fastlane). ### fastlane-plugin-changelog @@ -58,7 +67,7 @@ Version 0.16.0 Copyright 2018 Pavel Procházka. -*fastlane-plugin-changelog* is distributed under the terms and conditions ot the [MIT License](http://opensource.org/licenses/MIT). +*fastlane-plugin-changelog* is distributed under the terms and conditions of the [MIT License](http://opensource.org/licenses/MIT). You may download the source code on the [following website](https://github.com/pajapro/fastlane-plugin-changelog). ### fastlane-plugin-mattermost @@ -67,7 +76,7 @@ Version 1.3.2 Copyright 2020 cpfriend1721994. -*fastlane-plugin-mattermost* is distributed under the terms and conditions ot the [MIT License](http://opensource.org/licenses/MIT). +*fastlane-plugin-mattermost* is distributed under the terms and conditions of the [MIT License](http://opensource.org/licenses/MIT). You may download the source code on the [following website](https://github.com/cpfriend1721994/fastlane-plugin-mattermost). ### SwiftFormat @@ -76,7 +85,7 @@ Version 0.49.18 Copyright 2016 Nick Lockwood. -*SwiftFormat* is distributed under the terms and conditions ot the [MIT License](http://opensource.org/licenses/MIT). +*SwiftFormat* is distributed under the terms and conditions of the [MIT License](http://opensource.org/licenses/MIT). You may download the source code on the [following website](https://github.com/nicklockwood/SwiftFormat). ### SwiftLint @@ -85,6 +94,13 @@ Version 0.48.0 Copyright 2020 Realm Inc. -*SwiftLint* is distributed under the terms and conditions ot the [MIT License](http://opensource.org/licenses/MIT). +*SwiftLint* is distributed under the terms and conditions of the [MIT License](http://opensource.org/licenses/MIT). You may download the source code on the [following website](https://github.com/realm/SwiftLint). + +## For tests + +### FileWithoutUTF8Content.jpg + + *Oranges* by Dious is marked with [Public Domain Mark 1.0](https://creativecommons.org/publicdomain/mark/1.0/?ref=openverse). + You may download thie image on [openverse.org]( https://openverse.org/image/02120bd4-a489-4bdd-9b0a-5f59a68cc2e8?q=orange). diff --git a/docs/components/cards.md b/docs/components/cards.md index 56d9af2b..28d4ef96 100644 --- a/docs/components/cards.md +++ b/docs/components/cards.md @@ -44,25 +44,25 @@ This card is composed of two parts: - Media: (today an image) - Content: with a title, an optional subtitle an optional supporting text and optional buttons (zero up to two) +![Vertical image first card light](images/card_vertical_image_first_light.png) ![Vertical image first card dark](images/card_vertical_image_first_dark.png) + > **Implementation** -Card is configured using `ODSCardVerticalHeaderFirstModel` like this: +Card is configured like this: ```swift -let model = ODSCardVerticalImageFirstModel( - title: "Title", - subtitle: "Subtitle", - image: Image("ods_empty", bundle: Bundle.ods), - supportingText: "A supporting text to describe something") - -ODSCardVerticalImageFirst(model: model) { - ODSButton(text: "Button 1", emphasis: .low) { - // do something here - } - } buttonContent2: { - ODSButton(text: "Button 1", emphasis: .low) { - // do something here - } +ODSCardVerticalImageFirst( + title: Text("Title"), + imageSource: .image(Image("ods_empty", bundle: Bundle.ods)), + subtitle: Text("Subtitle"), + text: Text("A supporting text to describe something") +) { + Button("Button 1") { + // do something here + } +} secondButton: { + Button("Button 2") { + // do something here } } ``` @@ -76,9 +76,11 @@ This card is composed of three parts: - Media: (today an image) - Content: with an optional supporting text and optional buttons (zero up to two) +![Vertical header first card light](images/card_vertical_header_first_light.png) ![Vertical header first card dark](images/card_vertical_header_first_dark.png) + > **Implementation** -Card is configured using `ODSCardVerticalHeaderFirstModel` like this: +Card is configured like this: ```swift @@ -86,7 +88,7 @@ ODSCardVerticalHeaderFirst( title: Text("Title"), imageSource: .image(Image("ods_empty", bundle: Bundle.ods)), subtitle: Text("Subtitle"), - thumbnail: Image("ods_empty", bundle: Bundle.ods), + thumbnailSource: .image(Image("ods_empty", bundle: Bundle.ods)), text: Text("A supporting text to describe something") ) { Button("Button 1") { @@ -108,6 +110,8 @@ Thes content is composed by: - an optional subtitle - an optional text for larger description +![Horizontal card light](images/card_horizontal_light.png) ![Horizontal card dark](images/card_horizontal_dark.png) + > **Implementation** Card is configured like this: @@ -136,6 +140,8 @@ ODSCardHorizontal( The small card if prefered for two-column portrait mobile screen display. As it is smaller than full-width cards, it contains only title and subtitle (optional) in one line (Truncated tail). +![CardSmall](images/card_small_light.png) ![CardSmall dark](images/card_small_dark.png) + > **Implementation** Card is configured like this: diff --git a/docs/components/chips.md b/docs/components/chips.md index 4c8dd841..37f66e91 100644 --- a/docs/components/chips.md +++ b/docs/components/chips.md @@ -11,11 +11,10 @@ description: Chips are compact elements that represent an input, attribute, or a * [Specifications references](#specifications-references) * [Accessibility](#accessibility) * [Variants](#variants) -* [Choice selection](#choice-selection) - * [Single election](#single-selection) - * [Single selection, One chip selected](#single-selection-one-chip-selected) - * [Single selection, No chip selected](#single-selection-no-chip-selected) - * [Multiple selection](#multiple-selection) + * [Action chip](#action-chip) + * [Input chip](#input-chip) + * [Choice chip](#choice-chip) + * [Filter chip](#filter-chip) --- @@ -29,105 +28,140 @@ Please follow [accessibility criteria for development](https://a11y-guidelines.o Chips support dynamic types for accessibility. +Chips support content labeling for accessibility and are readable by most screen readers. Text rendered in chips is automatically provided to accessibility services. Additional content labels are usually unnecessary. + ## Variants -According to the `ODSChip` configuration following representations are available. +### Action chip -```swift - enum MyChip: Int { - case enabled - case selected - case disabled - } +Action chips offer actions related to primary content. They should appear dynamically and contextually in a UI. +An alternative to action chips are buttons, which should appear persistently and consistently. - // Text only - ODSChip(.enabled, text: "Enable") - - // Text with icon - ODSChip(.enabled, text: "Enable", thumbnail: .icon(iconImage)) - - // Text with system icon - ODSChip(.enabled, text: "Enable", thumbnail: .iconSystem(name: "heart")) - - // Text with avatar - ODSChip(.enabled, text: "Enable", thumbnail: .avatar(avatarImage)) +![Light action chip](images/chips_action_light.png) +![Dark action chip](images/chips_action_dark.png) + +``` swift +ODSActionChip( + text: Text("chip text"), + Image(systemname: "heart") + action: { doSomething() } +) ``` -A chip can also be disabled, and removable from the list of selection +To disable the chip call the `.disabled` on View. -``` swift - // Removable - ODSChip(.enabled, text: "Enable", removable: true) +### Input chip - // Disbaled - ODSChip(.enabled, text: "Enable", disbaled: true) +Input chips represent a complex piece of information in compact form, such as an entity (person, place, or thing) or text. They enable user input and verify that input by converting text into chips. +![Light input chip](images/chips_input_light.png) +![Dark input chip](images/chips_input_dark.png) + +``` swift +// Input chip with leading filled with icon or image for resources + +ODSInputChip( + text: Text(vhip text), + leadingAvatar: .image(Image("Avatar")), + action: { doSomething() }, + removeAction: { doSomething() } +) ``` +### Choice chip -## Choice selection +Choice chips allow selection of a single chip from a set of options. Choice chips clearly delineate and display options in a compact area. -The selection is managed by the `ODSChipPicker` providing the right type of selection. +**Note: To display a set of choice chips please see ODSChoiceChipsPicker** -### Single selection +![Light input chip](images/chips_choice_light.png) +![Dark input chip](images/chips_choice_dark.png) -The option allows a single chip selection from a set of options. According to the type of selection (optional or not), it is possible to accept at least one or zero selected chip. +``` swift +enum Ingredient: String, CaseIterable { + case chocolate, vanilla, strawberry +} -#### Single selection, One chip selected +ODSChoiceChipView( + chip: ODSChoiceChip(text: Text("Chocolate"), value: .chocolate), + selected: false: + action: { doSomething() } +) +ODSChoiceChipView( + chip: ODSChoiceChip(text: Text("Vanilla"), value: .vanilla), + selected: true: + action: { doSomething() } +) +``` -```swift - enum ChipTest Int{ - case title1 = 1, - case title2 = 2 - } +In order to display a set of choice chips you can follow this example: - struct ODSChipPickerSingleSelection: View { - @State var singleSelection: ChipTest - let chips = [ODSChip] +``` swift +@State var selection: Ingredient - var body: some View { - ODSChipPicker(title: "Single selection (at least one element)", - selection: $singleSelection, - chips: chips) +var body: some View { + ScrollView(.horizontal) { + ForEach(Ingredient.allCases, id: \.rawValue) { ingredient in + ODSChoiceChipView( + model: ODSChoiceChip(text: Text(ingredient.rawValue), value: ingredient), + selected: selection == ingredient, + action: { selection = ingredient } ) } - - init() { - chips = [ODSChip(.title1, text: "Chips 1"), ODSChip(.title2, text: "Chip 2")] - singleSelection = .title1 - } } +} ``` -#### Single selection, No chip selected +To simplify the chips placement and alignment, you can also use the __ODSChoiceChipsPicker__ like this: -```swift - struct ODSChipPickerSingleSelection: View { - @State var singleSelection: ChipTest? +``` swift +@State var selection: Ingredient - var body: some View { - ODSChipPicker(title: "Single selection (No chip seleted allowed)", - selection: $singleSelection, - chips: chips] - ) - } - } +ODSChoiceChipPicker( + title: Text("Select your ingredient"), + chips: Ingredient.allCases.map { ODSChoiceChip(text: Text($0.rawValue), value: $0) + selection: $selection, + placement: .carousel +) ``` - -### Multiple selection -The option allows a multiple chips selection from a set of options. Depending on `allowZeroSelection` parameter, it is possible to accept at least one or zero selected chip. +### Filter chip -```swift - struct ODSChipPickerMultipleSelection: View { - @State var multipleSelection: [Int] +Filter chips use tags or descriptive words to filter content. Filter chips allow selection of a set of chips from a set of options. Its usage is usefull to apply a filtering on a list of elmeents. - var body: some View { - ODSChipPicker(title: "Multiple selection", - selection: $multipleSelection, - allowZeroSelection: true, - chips: chips] - ) - } +**Note: To display a set of filter chips please see ODSFilterChipsPicker** + +![Light filter chips](images/chips_filter_light.png) ![Dark filter chips](images/chips_filter_dark.png) + +![Light filter chips with avatar](images/chips_filter_avatar_light.png) ![Dark filter chips with avatar](images/chips_filter_avatar_dark.png) + + +``` swift +enum Ingredient: String, CaseIterable { + case chocolate, vanilla, strawberry + + var image: Image { + Image("self.rawValue") } +} + +ODSFilterChipView( + chip: ODSFilterChip(text: Text("Chocolate"), leading: .image(Image("avatar")), value: .chocolate), + selected: false: + action: { doSomething() } +) +``` + +As the choice chip, to simplify the chips placement and alignment, you can also use the __ODSFilterChipsPicker__ like this: + +``` swift +@State var selection: [Ingredient] + +ODSFilterChipPicker( + title: Text("Select your ingredients"), + chips: Ingredient.allCases.map { ODSFilterChip(text: Text($0.rawValue), leading(.image($0.image)), value: $0) + selection: $selection, + placement: .carousel +) ``` + diff --git a/docs/components/images/card_horizontal_dark.png b/docs/components/images/card_horizontal_dark.png new file mode 100644 index 00000000..7fe518b8 Binary files /dev/null and b/docs/components/images/card_horizontal_dark.png differ diff --git a/docs/components/images/card_horizontal_light.png b/docs/components/images/card_horizontal_light.png new file mode 100644 index 00000000..fdd1bdb3 Binary files /dev/null and b/docs/components/images/card_horizontal_light.png differ diff --git a/docs/components/images/card_small_dark.png b/docs/components/images/card_small_dark.png new file mode 100644 index 00000000..211a72a9 Binary files /dev/null and b/docs/components/images/card_small_dark.png differ diff --git a/docs/components/images/card_small_light.png b/docs/components/images/card_small_light.png new file mode 100644 index 00000000..e2b9949b Binary files /dev/null and b/docs/components/images/card_small_light.png differ diff --git a/docs/components/images/card_vertical_header_first_dark.png b/docs/components/images/card_vertical_header_first_dark.png new file mode 100644 index 00000000..542c62eb Binary files /dev/null and b/docs/components/images/card_vertical_header_first_dark.png differ diff --git a/docs/components/images/card_vertical_header_first_light.png b/docs/components/images/card_vertical_header_first_light.png new file mode 100644 index 00000000..66460143 Binary files /dev/null and b/docs/components/images/card_vertical_header_first_light.png differ diff --git a/docs/components/images/card_vertical_image_first_dark.png b/docs/components/images/card_vertical_image_first_dark.png new file mode 100644 index 00000000..f9a5299d Binary files /dev/null and b/docs/components/images/card_vertical_image_first_dark.png differ diff --git a/docs/components/images/card_vertical_image_first_light.png b/docs/components/images/card_vertical_image_first_light.png new file mode 100644 index 00000000..58ee1f75 Binary files /dev/null and b/docs/components/images/card_vertical_image_first_light.png differ diff --git a/docs/components/images/chips_action_dark.png b/docs/components/images/chips_action_dark.png new file mode 100644 index 00000000..e0a50458 Binary files /dev/null and b/docs/components/images/chips_action_dark.png differ diff --git a/docs/components/images/chips_action_light.png b/docs/components/images/chips_action_light.png new file mode 100644 index 00000000..e2d8a9d6 Binary files /dev/null and b/docs/components/images/chips_action_light.png differ diff --git a/docs/components/images/chips_choice_dark.png b/docs/components/images/chips_choice_dark.png new file mode 100644 index 00000000..d848d8ad Binary files /dev/null and b/docs/components/images/chips_choice_dark.png differ diff --git a/docs/components/images/chips_choice_light.png b/docs/components/images/chips_choice_light.png new file mode 100644 index 00000000..37781814 Binary files /dev/null and b/docs/components/images/chips_choice_light.png differ diff --git a/docs/components/images/chips_filter_avatar_dark.png b/docs/components/images/chips_filter_avatar_dark.png new file mode 100644 index 00000000..e4269dcc Binary files /dev/null and b/docs/components/images/chips_filter_avatar_dark.png differ diff --git a/docs/components/images/chips_filter_avatar_light.png b/docs/components/images/chips_filter_avatar_light.png new file mode 100644 index 00000000..56857458 Binary files /dev/null and b/docs/components/images/chips_filter_avatar_light.png differ diff --git a/docs/components/images/chips_filter_dark.png b/docs/components/images/chips_filter_dark.png new file mode 100644 index 00000000..45fe2cf0 Binary files /dev/null and b/docs/components/images/chips_filter_dark.png differ diff --git a/docs/components/images/chips_filter_light.png b/docs/components/images/chips_filter_light.png new file mode 100644 index 00000000..d9c57620 Binary files /dev/null and b/docs/components/images/chips_filter_light.png differ diff --git a/docs/components/images/chips_input_dark.png b/docs/components/images/chips_input_dark.png new file mode 100644 index 00000000..315167eb Binary files /dev/null and b/docs/components/images/chips_input_dark.png differ diff --git a/docs/components/images/chips_input_light.png b/docs/components/images/chips_input_light.png new file mode 100644 index 00000000..6e928d1d Binary files /dev/null and b/docs/components/images/chips_input_light.png differ diff --git a/docs/guidelines/typography.md b/docs/guidelines/typography.md index d80b3083..5896248d 100644 --- a/docs/guidelines/typography.md +++ b/docs/guidelines/typography.md @@ -27,8 +27,8 @@ ODS library defines its own font style. The font associated to the style is defi Apply the font style on text like this: ``` swift - Text("Sample").odsFont(.title3) - TextField("A text field", text: $textToEdit).odsFont(.title3) + Text("Sample").odsFont(.titleS) + TextField("A text field", text: $textToEdit).odsFont(.titleS) ``` ### Apply font style on view @@ -38,13 +38,13 @@ In the example below, the first text field has a font style set directly, while ``` swift VStack { Text("Font applied to a text view.") - .odsFont(.largeTitle) + .odsFont(.headlineL) VStack { Text("These two text views have the same font") Text("applied to their parent view.") } - .odsFont(.title3) + .odsFont(.titleS) } ```