diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b117d7a4c..a73e68b9cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Switch to current version of Xcode run: scripts/xcode_select_current_version.sh - name: pod lib lint @@ -35,18 +35,20 @@ jobs: build_command: [ 'macos_build FluentUITestApp-macOS Debug build test', 'macos_build FluentUITestApp-macOS Release build test', + 'macos_build FluentUITestApp-macOS Debug build test -destination "platform=macOS,arch=x86_64"', 'ios_simulator_build FluentUI-iOS Debug build test -destination "platform=iOS Simulator,name=iPhone 8"', # Provide a destination for the iOS simulator unit tests 'ios_simulator_build FluentUI-iOS Release build', 'ios_device_build FluentUI-iOS Debug build', 'ios_device_build FluentUI-iOS Release build', 'ios_simulator_build Demo.Development Debug build', 'ios_simulator_build Demo.Development Release build', + 'ios_simulator_build Demo.Development Debug build test -destination "platform=iOS Simulator,name=iPhone 14 Pro"', 'ios_device_build Demo.Development Debug build', 'ios_device_build Demo.Development Release build', ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Switch to current version of Xcode run: scripts/xcode_select_current_version.sh - name: scripts/xcodebuild_wrapper.sh ${{ matrix.build_command }} diff --git a/.github/workflows/localize.yml b/.github/workflows/localize.yml index ef3505cfb4..b12fe443bf 100644 --- a/.github/workflows/localize.yml +++ b/.github/workflows/localize.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - shell: bash name: Localize env: diff --git a/.github/workflows/podPublish.yml b/.github/workflows/podPublish.yml index 9630f87f18..a2c942008f 100644 --- a/.github/workflows/podPublish.yml +++ b/.github/workflows/podPublish.yml @@ -10,7 +10,7 @@ jobs: runs-on: macos-12 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Switch to current version of Xcode run: scripts/xcode_select_current_version.sh - name: Publish to CocoaPod register diff --git a/ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/project.pbxproj b/ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/project.pbxproj index 95fe552150..a49ef67852 100644 --- a/ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/project.pbxproj +++ b/ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/project.pbxproj @@ -8,6 +8,49 @@ /* Begin PBXBuildFile section */ 2F0A96FC25CA047100EF9736 /* SearchBarDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0A96FB25CA047100EF9736 /* SearchBarDemoController.swift */; }; + 3A83F8BB2953B73700EF6629 /* ActivityIndicatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8BA2953B73700EF6629 /* ActivityIndicatorTest.swift */; }; + 3A83F8BD2953B75100EF6629 /* BaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8BC2953B75100EF6629 /* BaseTest.swift */; }; + 3A83F8BF2953B83300EF6629 /* ActivityIndicatorTest_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8BE2953B83300EF6629 /* ActivityIndicatorTest_SwiftUI.swift */; }; + 3A83F8C12953B89000EF6629 /* AvatarTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8C02953B89000EF6629 /* AvatarTest.swift */; }; + 3A83F8C32953B8A400EF6629 /* AvatarTest_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8C22953B8A400EF6629 /* AvatarTest_SwiftUI.swift */; }; + 3A83F8C52953B8B900EF6629 /* AvatarGroupTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8C42953B8B900EF6629 /* AvatarGroupTest.swift */; }; + 3A83F8C72953B8C800EF6629 /* BadgeFieldTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8C62953B8C800EF6629 /* BadgeFieldTest.swift */; }; + 3A83F8C92953B8D600EF6629 /* BadgeViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8C82953B8D600EF6629 /* BadgeViewTest.swift */; }; + 3A83F8CB2953B8E500EF6629 /* BottomCommandingControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8CA2953B8E500EF6629 /* BottomCommandingControllerTest.swift */; }; + 3A83F8CD2953B8F200EF6629 /* BottomSheetControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8CC2953B8F200EF6629 /* BottomSheetControllerTest.swift */; }; + 3A83F8CF2953B90000EF6629 /* ButtonTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8CE2953B90000EF6629 /* ButtonTest.swift */; }; + 3A83F8D12953B90D00EF6629 /* CardTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8D02953B90D00EF6629 /* CardTest.swift */; }; + 3A83F8D32953B91900EF6629 /* CardNudgeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8D22953B91900EF6629 /* CardNudgeTest.swift */; }; + 3A83F8D52953B92D00EF6629 /* ColorTokensTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8D42953B92D00EF6629 /* ColorTokensTest.swift */; }; + 3A83F8D72953B93A00EF6629 /* CommandBarTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8D62953B93A00EF6629 /* CommandBarTest.swift */; }; + 3A83F8D92953B94D00EF6629 /* DateTimePickerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8D82953B94D00EF6629 /* DateTimePickerTest.swift */; }; + 3A83F8DB2953B95B00EF6629 /* DrawerControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8DA2953B95B00EF6629 /* DrawerControllerTest.swift */; }; + 3A83F8DD2953B96800EF6629 /* HUDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8DC2953B96800EF6629 /* HUDTest.swift */; }; + 3A83F8DF2953B97900EF6629 /* HUDTest_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8DE2953B97900EF6629 /* HUDTest_SwiftUI.swift */; }; + 3A83F8E12953B99A00EF6629 /* IndeterminateProgressBarTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8E02953B99A00EF6629 /* IndeterminateProgressBarTest.swift */; }; + 3A83F8E32953B9A800EF6629 /* IndeterminateProgressBarTest_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8E22953B9A800EF6629 /* IndeterminateProgressBarTest_SwiftUI.swift */; }; + 3A83F8E52953B9B800EF6629 /* LabelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8E42953B9B800EF6629 /* LabelTest.swift */; }; + 3A83F8E72953B9D100EF6629 /* NavigationControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8E62953B9D100EF6629 /* NavigationControllerTest.swift */; }; + 3A83F8E92953BA4500EF6629 /* NotificationViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8E82953BA4500EF6629 /* NotificationViewTest.swift */; }; + 3A83F8EB2953BA5400EF6629 /* NotificationViewTest_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8EA2953BA5400EF6629 /* NotificationViewTest_SwiftUI.swift */; }; + 3A83F8ED2953BA6B00EF6629 /* OtherCellsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8EC2953BA6B00EF6629 /* OtherCellsTest.swift */; }; + 3A83F8EF2953BA7C00EF6629 /* PeoplePickerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8EE2953BA7C00EF6629 /* PeoplePickerTest.swift */; }; + 3A83F8F12953BA8900EF6629 /* PersonaButtonCarouselTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8F02953BA8900EF6629 /* PersonaButtonCarouselTest.swift */; }; + 3A83F8F32953BA9500EF6629 /* PersonaListViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8F22953BA9500EF6629 /* PersonaListViewTest.swift */; }; + 3A83F8F52953BAA200EF6629 /* PillButtonTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8F42953BAA200EF6629 /* PillButtonTest.swift */; }; + 3A83F8F72953BAAD00EF6629 /* PillButtonBarTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8F62953BAAD00EF6629 /* PillButtonBarTest.swift */; }; + 3A83F8F92953BABA00EF6629 /* PopupMenuControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8F82953BABA00EF6629 /* PopupMenuControllerTest.swift */; }; + 3A83F8FB2953BAC500EF6629 /* SearchBarTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8FA2953BAC500EF6629 /* SearchBarTest.swift */; }; + 3A83F8FD2953BAD300EF6629 /* SegmentedControlTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8FC2953BAD300EF6629 /* SegmentedControlTest.swift */; }; + 3A83F8FF2953BADF00EF6629 /* ShimmerViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F8FE2953BADF00EF6629 /* ShimmerViewTest.swift */; }; + 3A83F9012953BAEB00EF6629 /* SideTabBarTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F9002953BAEB00EF6629 /* SideTabBarTest.swift */; }; + 3A83F9032953BAF700EF6629 /* TabBarViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F9022953BAF700EF6629 /* TabBarViewTest.swift */; }; + 3A83F9052953BB0100EF6629 /* TableViewCellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F9042953BB0100EF6629 /* TableViewCellTest.swift */; }; + 3A83F9072953BB0E00EF6629 /* TableViewCellFileAccessoryViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F9062953BB0E00EF6629 /* TableViewCellFileAccessoryViewTest.swift */; }; + 3A83F9092953BB1B00EF6629 /* TableViewCellShimmerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F9082953BB1B00EF6629 /* TableViewCellShimmerTest.swift */; }; + 3A83F90B2953BB2700EF6629 /* TableViewHeaderFooterViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F90A2953BB2700EF6629 /* TableViewHeaderFooterViewTest.swift */; }; + 3A83F90D2953BB3500EF6629 /* TooltipTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F90C2953BB3500EF6629 /* TooltipTest.swift */; }; + 3A83F90F2953BB4000EF6629 /* TypographyTokensTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A83F90E2953BB4000EF6629 /* TypographyTokensTest.swift */; }; 43488C4A270FB7E800124C71 /* NotificationViewDemoController_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43488C49270FB7E800124C71 /* NotificationViewDemoController_SwiftUI.swift */; }; 497DC2DE24185896008D86F8 /* PillButtonBarDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497DC2DD24185896008D86F8 /* PillButtonBarDemoController.swift */; }; 5303259326B3198A00611D05 /* AvatarDemoController_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5303259226B3198A00611D05 /* AvatarDemoController_SwiftUI.swift */; }; @@ -31,6 +74,7 @@ 5373D55F2694C3070032A3B4 /* AvatarDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5373D55E2694C3070032A3B4 /* AvatarDemoController.swift */; }; 6F453CA528AC536300ED91A4 /* ShadowTokensDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F453CA428AC536300ED91A4 /* ShadowTokensDemoController.swift */; }; 6FEED93B28A6E5520099D178 /* AliasColorTokensDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FEED93A28A6E5520099D178 /* AliasColorTokensDemoController.swift */; }; + 6FC8AD3B28DBAF280010C0F8 /* ReadmeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FC8AD3A28DBAF280010C0F8 /* ReadmeViewController.swift */; }; 7D0931C124AAA3D30072458A /* SideTabBarDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0931C024AAA3D30072458A /* SideTabBarDemoController.swift */; }; 807E8B4528F9F8B8002B8F84 /* PillButtonDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807E8B4428F9F8B8002B8F84 /* PillButtonDemoController.swift */; }; 80AECC0C2630F1BB005AF2F3 /* BottomCommandingDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80AECC0B2630F1BB005AF2F3 /* BottomCommandingDemoController.swift */; }; @@ -80,11 +124,65 @@ FDCF7C8321BF35680058E9E6 /* SegmentedControlDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCF7C8221BF35680058E9E6 /* SegmentedControlDemoController.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 3A83F7C5295110FF00EF6629 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A5CEC20420E436F10016922A /* Project object */; + proxyType = 1; + remoteGlobalIDString = A5CEC20B20E436F10016922A; + remoteInfo = FluentUI.Demo; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 0A0516BF2940A407006DE0A2 /* DividerDemoController_SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DividerDemoController_SwiftUI.swift; sourceTree = ""; }; 0A0516C02940A407006DE0A2 /* DividerDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DividerDemoController.swift; sourceTree = ""; }; 11047D2522932DB1001F0F59 /* TabBarViewDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarViewDemoController.swift; sourceTree = ""; }; 2F0A96FB25CA047100EF9736 /* SearchBarDemoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchBarDemoController.swift; sourceTree = ""; }; + 3A83F7BF295110FF00EF6629 /* FluentUIDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FluentUIDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3A83F8BA2953B73700EF6629 /* ActivityIndicatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorTest.swift; sourceTree = ""; }; + 3A83F8BC2953B75100EF6629 /* BaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTest.swift; sourceTree = ""; }; + 3A83F8BE2953B83300EF6629 /* ActivityIndicatorTest_SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorTest_SwiftUI.swift; sourceTree = ""; }; + 3A83F8C02953B89000EF6629 /* AvatarTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarTest.swift; sourceTree = ""; }; + 3A83F8C22953B8A400EF6629 /* AvatarTest_SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarTest_SwiftUI.swift; sourceTree = ""; }; + 3A83F8C42953B8B900EF6629 /* AvatarGroupTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarGroupTest.swift; sourceTree = ""; }; + 3A83F8C62953B8C800EF6629 /* BadgeFieldTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeFieldTest.swift; sourceTree = ""; }; + 3A83F8C82953B8D600EF6629 /* BadgeViewTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeViewTest.swift; sourceTree = ""; }; + 3A83F8CA2953B8E500EF6629 /* BottomCommandingControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomCommandingControllerTest.swift; sourceTree = ""; }; + 3A83F8CC2953B8F200EF6629 /* BottomSheetControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetControllerTest.swift; sourceTree = ""; }; + 3A83F8CE2953B90000EF6629 /* ButtonTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTest.swift; sourceTree = ""; }; + 3A83F8D02953B90D00EF6629 /* CardTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardTest.swift; sourceTree = ""; }; + 3A83F8D22953B91900EF6629 /* CardNudgeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardNudgeTest.swift; sourceTree = ""; }; + 3A83F8D42953B92D00EF6629 /* ColorTokensTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorTokensTest.swift; sourceTree = ""; }; + 3A83F8D62953B93A00EF6629 /* CommandBarTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandBarTest.swift; sourceTree = ""; }; + 3A83F8D82953B94D00EF6629 /* DateTimePickerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimePickerTest.swift; sourceTree = ""; }; + 3A83F8DA2953B95B00EF6629 /* DrawerControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerControllerTest.swift; sourceTree = ""; }; + 3A83F8DC2953B96800EF6629 /* HUDTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDTest.swift; sourceTree = ""; }; + 3A83F8DE2953B97900EF6629 /* HUDTest_SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDTest_SwiftUI.swift; sourceTree = ""; }; + 3A83F8E02953B99A00EF6629 /* IndeterminateProgressBarTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndeterminateProgressBarTest.swift; sourceTree = ""; }; + 3A83F8E22953B9A800EF6629 /* IndeterminateProgressBarTest_SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndeterminateProgressBarTest_SwiftUI.swift; sourceTree = ""; }; + 3A83F8E42953B9B800EF6629 /* LabelTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelTest.swift; sourceTree = ""; }; + 3A83F8E62953B9D100EF6629 /* NavigationControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationControllerTest.swift; sourceTree = ""; }; + 3A83F8E82953BA4500EF6629 /* NotificationViewTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewTest.swift; sourceTree = ""; }; + 3A83F8EA2953BA5400EF6629 /* NotificationViewTest_SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewTest_SwiftUI.swift; sourceTree = ""; }; + 3A83F8EC2953BA6B00EF6629 /* OtherCellsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherCellsTest.swift; sourceTree = ""; }; + 3A83F8EE2953BA7C00EF6629 /* PeoplePickerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeoplePickerTest.swift; sourceTree = ""; }; + 3A83F8F02953BA8900EF6629 /* PersonaButtonCarouselTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonaButtonCarouselTest.swift; sourceTree = ""; }; + 3A83F8F22953BA9500EF6629 /* PersonaListViewTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonaListViewTest.swift; sourceTree = ""; }; + 3A83F8F42953BAA200EF6629 /* PillButtonTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButtonTest.swift; sourceTree = ""; }; + 3A83F8F62953BAAD00EF6629 /* PillButtonBarTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButtonBarTest.swift; sourceTree = ""; }; + 3A83F8F82953BABA00EF6629 /* PopupMenuControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupMenuControllerTest.swift; sourceTree = ""; }; + 3A83F8FA2953BAC500EF6629 /* SearchBarTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarTest.swift; sourceTree = ""; }; + 3A83F8FC2953BAD300EF6629 /* SegmentedControlTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlTest.swift; sourceTree = ""; }; + 3A83F8FE2953BADF00EF6629 /* ShimmerViewTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShimmerViewTest.swift; sourceTree = ""; }; + 3A83F9002953BAEB00EF6629 /* SideTabBarTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideTabBarTest.swift; sourceTree = ""; }; + 3A83F9022953BAF700EF6629 /* TabBarViewTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarViewTest.swift; sourceTree = ""; }; + 3A83F9042953BB0100EF6629 /* TableViewCellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCellTest.swift; sourceTree = ""; }; + 3A83F9062953BB0E00EF6629 /* TableViewCellFileAccessoryViewTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCellFileAccessoryViewTest.swift; sourceTree = ""; }; + 3A83F9082953BB1B00EF6629 /* TableViewCellShimmerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCellShimmerTest.swift; sourceTree = ""; }; + 3A83F90A2953BB2700EF6629 /* TableViewHeaderFooterViewTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewHeaderFooterViewTest.swift; sourceTree = ""; }; + 3A83F90C2953BB3500EF6629 /* TooltipTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipTest.swift; sourceTree = ""; }; + 3A83F90E2953BB4000EF6629 /* TypographyTokensTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypographyTokensTest.swift; sourceTree = ""; }; 43488C49270FB7E800124C71 /* NotificationViewDemoController_SwiftUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationViewDemoController_SwiftUI.swift; sourceTree = ""; }; 497DC2DD24185896008D86F8 /* PillButtonBarDemoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PillButtonBarDemoController.swift; sourceTree = ""; }; 5303259226B3198A00611D05 /* AvatarDemoController_SwiftUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarDemoController_SwiftUI.swift; sourceTree = ""; }; @@ -100,6 +198,7 @@ 5373F95626F28D9B007F1410 /* IndeterminateProgressBarDemoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IndeterminateProgressBarDemoController.swift; sourceTree = ""; }; 6F453CA428AC536300ED91A4 /* ShadowTokensDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowTokensDemoController.swift; sourceTree = ""; }; 6FEED93A28A6E5520099D178 /* AliasColorTokensDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AliasColorTokensDemoController.swift; sourceTree = ""; }; + 6FC8AD3A28DBAF280010C0F8 /* ReadmeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadmeViewController.swift; sourceTree = ""; }; 7D0931C024AAA3D30072458A /* SideTabBarDemoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SideTabBarDemoController.swift; sourceTree = ""; }; 7DC2FB2A24C0F4FD00367A55 /* TableViewCellFileAccessoryViewDemoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCellFileAccessoryViewDemoController.swift; sourceTree = ""; }; 807E8B4428F9F8B8002B8F84 /* PillButtonDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButtonDemoController.swift; sourceTree = ""; }; @@ -198,6 +297,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 3A83F7BC295110FF00EF6629 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; A5CEC20920E436F10016922A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -212,6 +318,56 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3A83F7C0295110FF00EF6629 /* FluentUIDemoTests */ = { + isa = PBXGroup; + children = ( + 3A83F8BC2953B75100EF6629 /* BaseTest.swift */, + 3A83F8BA2953B73700EF6629 /* ActivityIndicatorTest.swift */, + 3A83F8BE2953B83300EF6629 /* ActivityIndicatorTest_SwiftUI.swift */, + 3A83F8C02953B89000EF6629 /* AvatarTest.swift */, + 3A83F8C22953B8A400EF6629 /* AvatarTest_SwiftUI.swift */, + 3A83F8C42953B8B900EF6629 /* AvatarGroupTest.swift */, + 3A83F8C62953B8C800EF6629 /* BadgeFieldTest.swift */, + 3A83F8C82953B8D600EF6629 /* BadgeViewTest.swift */, + 3A83F8CA2953B8E500EF6629 /* BottomCommandingControllerTest.swift */, + 3A83F8CC2953B8F200EF6629 /* BottomSheetControllerTest.swift */, + 3A83F8CE2953B90000EF6629 /* ButtonTest.swift */, + 3A83F8D02953B90D00EF6629 /* CardTest.swift */, + 3A83F8D22953B91900EF6629 /* CardNudgeTest.swift */, + 3A83F8D42953B92D00EF6629 /* ColorTokensTest.swift */, + 3A83F8D62953B93A00EF6629 /* CommandBarTest.swift */, + 3A83F8D82953B94D00EF6629 /* DateTimePickerTest.swift */, + 3A83F8DA2953B95B00EF6629 /* DrawerControllerTest.swift */, + 3A83F8DC2953B96800EF6629 /* HUDTest.swift */, + 3A83F8DE2953B97900EF6629 /* HUDTest_SwiftUI.swift */, + 3A83F8E02953B99A00EF6629 /* IndeterminateProgressBarTest.swift */, + 3A83F8E22953B9A800EF6629 /* IndeterminateProgressBarTest_SwiftUI.swift */, + 3A83F8E42953B9B800EF6629 /* LabelTest.swift */, + 3A83F8E62953B9D100EF6629 /* NavigationControllerTest.swift */, + 3A83F8E82953BA4500EF6629 /* NotificationViewTest.swift */, + 3A83F8EA2953BA5400EF6629 /* NotificationViewTest_SwiftUI.swift */, + 3A83F8EC2953BA6B00EF6629 /* OtherCellsTest.swift */, + 3A83F8EE2953BA7C00EF6629 /* PeoplePickerTest.swift */, + 3A83F8F02953BA8900EF6629 /* PersonaButtonCarouselTest.swift */, + 3A83F8F22953BA9500EF6629 /* PersonaListViewTest.swift */, + 3A83F8F42953BAA200EF6629 /* PillButtonTest.swift */, + 3A83F8F62953BAAD00EF6629 /* PillButtonBarTest.swift */, + 3A83F8F82953BABA00EF6629 /* PopupMenuControllerTest.swift */, + 3A83F8FA2953BAC500EF6629 /* SearchBarTest.swift */, + 3A83F8FC2953BAD300EF6629 /* SegmentedControlTest.swift */, + 3A83F8FE2953BADF00EF6629 /* ShimmerViewTest.swift */, + 3A83F9002953BAEB00EF6629 /* SideTabBarTest.swift */, + 3A83F9022953BAF700EF6629 /* TabBarViewTest.swift */, + 3A83F9042953BB0100EF6629 /* TableViewCellTest.swift */, + 3A83F9062953BB0E00EF6629 /* TableViewCellFileAccessoryViewTest.swift */, + 3A83F9082953BB1B00EF6629 /* TableViewCellShimmerTest.swift */, + 3A83F90A2953BB2700EF6629 /* TableViewHeaderFooterViewTest.swift */, + 3A83F90C2953BB3500EF6629 /* TooltipTest.swift */, + 3A83F90E2953BB4000EF6629 /* TypographyTokensTest.swift */, + ); + path = FluentUIDemoTests; + sourceTree = ""; + }; 5303259626B31A6300611D05 /* SwiftUI */ = { isa = PBXGroup; children = ( @@ -265,6 +421,7 @@ A5DCA75F211E3B4C005F4CB7 /* DemoController.swift */, A5CEC21120E436F10016922A /* DemoListViewController.swift */, 92561E722718AD090072ED00 /* DemoTableViewController.swift */, + 6FC8AD3A28DBAF280010C0F8 /* ReadmeViewController.swift */, ); name = Shell; sourceTree = ""; @@ -273,6 +430,7 @@ isa = PBXGroup; children = ( A5CEC20E20E436F10016922A /* FluentUI.Demo */, + 3A83F7C0295110FF00EF6629 /* FluentUIDemoTests */, A5CEC20D20E436F10016922A /* Products */, A5CEC22920E44FBF0016922A /* Frameworks */, 5340828A26CFF298007716E1 /* Recovered References */, @@ -283,6 +441,7 @@ isa = PBXGroup; children = ( A5CEC20C20E436F10016922A /* FluentUI.Demo.app */, + 3A83F7BF295110FF00EF6629 /* FluentUIDemoTests.xctest */, ); name = Products; sourceTree = ""; @@ -315,7 +474,6 @@ A5CEC23220E452B80016922A /* Demos */ = { isa = PBXGroup; children = ( - 43488C49270FB7E800124C71 /* NotificationViewDemoController_SwiftUI.swift */, 532FE3D926EA6D8D007539C0 /* ActivityIndicatorDemoController.swift */, 532FE3DA26EA6D8D007539C0 /* ActivityIndicatorDemoController_SwiftUI.swift */, 5373D55E2694C3070032A3B4 /* AvatarDemoController.swift */, @@ -343,6 +501,7 @@ A5B6617523A4227300E801DD /* NotificationViewDemoController.swift */, D6296DAF295B8E1A002E8EB6 /* ObjectiveCDemoColorProviding.h */, D6296DB0295B8E5E002E8EB6 /* ObjectiveCDemoColorProviding.m */, + 43488C49270FB7E800124C71 /* NotificationViewDemoController_SwiftUI.swift */, FDDD73FC22C6D86B00A9D995 /* ObjectiveCDemoController.h */, FDDD73FB22C6D86B00A9D995 /* ObjectiveCDemoController.m */, B441478E228E02540040E88E /* OtherCellsDemoController.swift */, @@ -387,6 +546,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 3A83F7BE295110FF00EF6629 /* FluentUIDemoTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3A83F7CA295110FF00EF6629 /* Build configuration list for PBXNativeTarget "FluentUIDemoTests" */; + buildPhases = ( + 3A83F7BB295110FF00EF6629 /* Sources */, + 3A83F7BC295110FF00EF6629 /* Frameworks */, + 3A83F7BD295110FF00EF6629 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3A83F7C6295110FF00EF6629 /* PBXTargetDependency */, + ); + name = FluentUIDemoTests; + productName = FluentUIDemoTests; + productReference = 3A83F7BF295110FF00EF6629 /* FluentUIDemoTests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; A5CEC20B20E436F10016922A /* FluentUI.Demo */ = { isa = PBXNativeTarget; buildConfigurationList = A5CEC22020E436F20016922A /* Build configuration list for PBXNativeTarget "FluentUI.Demo" */; @@ -415,10 +592,15 @@ A5CEC20420E436F10016922A /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0940; + LastSwiftUpdateCheck = 1410; LastUpgradeCheck = 1240; ORGANIZATIONNAME = "Microsoft Corporation"; TargetAttributes = { + 3A83F7BE295110FF00EF6629 = { + CreatedOnToolsVersion = 14.1; + LastSwiftMigration = 1410; + TestTargetID = A5CEC20B20E436F10016922A; + }; A5CEC20B20E436F10016922A = { CreatedOnToolsVersion = 9.4.1; LastSwiftMigration = 1020; @@ -482,11 +664,19 @@ projectRoot = ""; targets = ( A5CEC20B20E436F10016922A /* FluentUI.Demo */, + 3A83F7BE295110FF00EF6629 /* FluentUIDemoTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 3A83F7BD295110FF00EF6629 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; A5CEC20A20E436F10016922A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -500,6 +690,56 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 3A83F7BB295110FF00EF6629 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3A83F8E12953B99A00EF6629 /* IndeterminateProgressBarTest.swift in Sources */, + 3A83F8FB2953BAC500EF6629 /* SearchBarTest.swift in Sources */, + 3A83F8EF2953BA7C00EF6629 /* PeoplePickerTest.swift in Sources */, + 3A83F8F52953BAA200EF6629 /* PillButtonTest.swift in Sources */, + 3A83F8C52953B8B900EF6629 /* AvatarGroupTest.swift in Sources */, + 3A83F9032953BAF700EF6629 /* TabBarViewTest.swift in Sources */, + 3A83F8BD2953B75100EF6629 /* BaseTest.swift in Sources */, + 3A83F8F92953BABA00EF6629 /* PopupMenuControllerTest.swift in Sources */, + 3A83F8CB2953B8E500EF6629 /* BottomCommandingControllerTest.swift in Sources */, + 3A83F9092953BB1B00EF6629 /* TableViewCellShimmerTest.swift in Sources */, + 3A83F8FD2953BAD300EF6629 /* SegmentedControlTest.swift in Sources */, + 3A83F8ED2953BA6B00EF6629 /* OtherCellsTest.swift in Sources */, + 3A83F8DB2953B95B00EF6629 /* DrawerControllerTest.swift in Sources */, + 3A83F8BB2953B73700EF6629 /* ActivityIndicatorTest.swift in Sources */, + 3A83F8F12953BA8900EF6629 /* PersonaButtonCarouselTest.swift in Sources */, + 3A83F8C12953B89000EF6629 /* AvatarTest.swift in Sources */, + 3A83F9052953BB0100EF6629 /* TableViewCellTest.swift in Sources */, + 3A83F90B2953BB2700EF6629 /* TableViewHeaderFooterViewTest.swift in Sources */, + 3A83F8E32953B9A800EF6629 /* IndeterminateProgressBarTest_SwiftUI.swift in Sources */, + 3A83F8C92953B8D600EF6629 /* BadgeViewTest.swift in Sources */, + 3A83F8CD2953B8F200EF6629 /* BottomSheetControllerTest.swift in Sources */, + 3A83F8D12953B90D00EF6629 /* CardTest.swift in Sources */, + 3A83F8CF2953B90000EF6629 /* ButtonTest.swift in Sources */, + 3A83F8DF2953B97900EF6629 /* HUDTest_SwiftUI.swift in Sources */, + 3A83F9012953BAEB00EF6629 /* SideTabBarTest.swift in Sources */, + 3A83F8FF2953BADF00EF6629 /* ShimmerViewTest.swift in Sources */, + 3A83F8BF2953B83300EF6629 /* ActivityIndicatorTest_SwiftUI.swift in Sources */, + 3A83F8DD2953B96800EF6629 /* HUDTest.swift in Sources */, + 3A83F8C72953B8C800EF6629 /* BadgeFieldTest.swift in Sources */, + 3A83F90F2953BB4000EF6629 /* TypographyTokensTest.swift in Sources */, + 3A83F8D72953B93A00EF6629 /* CommandBarTest.swift in Sources */, + 3A83F8D52953B92D00EF6629 /* ColorTokensTest.swift in Sources */, + 3A83F8F32953BA9500EF6629 /* PersonaListViewTest.swift in Sources */, + 3A83F8D32953B91900EF6629 /* CardNudgeTest.swift in Sources */, + 3A83F90D2953BB3500EF6629 /* TooltipTest.swift in Sources */, + 3A83F8D92953B94D00EF6629 /* DateTimePickerTest.swift in Sources */, + 3A83F9072953BB0E00EF6629 /* TableViewCellFileAccessoryViewTest.swift in Sources */, + 3A83F8E52953B9B800EF6629 /* LabelTest.swift in Sources */, + 3A83F8EB2953BA5400EF6629 /* NotificationViewTest_SwiftUI.swift in Sources */, + 3A83F8E72953B9D100EF6629 /* NavigationControllerTest.swift in Sources */, + 3A83F8C32953B8A400EF6629 /* AvatarTest_SwiftUI.swift in Sources */, + 3A83F8E92953BA4500EF6629 /* NotificationViewTest.swift in Sources */, + 3A83F8F72953BAAD00EF6629 /* PillButtonBarTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A5CEC20820E436F10016922A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -509,6 +749,7 @@ B441478F228E02540040E88E /* OtherCellsDemoController.swift in Sources */, 5303259326B3198A00611D05 /* AvatarDemoController_SwiftUI.swift in Sources */, 92D5598426A1523400328FD3 /* CardNudgeDemoController.swift in Sources */, + 6FC8AD3B28DBAF280010C0F8 /* ReadmeViewController.swift in Sources */, 5303259826B31A6300611D05 /* FluentUIDemoToggle.swift in Sources */, 9245E1F927BECDBB007616F3 /* GlobalColorTokensDemoController.swift in Sources */, D6296DB1295B8E5E002E8EB6 /* ObjectiveCDemoColorProviding.m in Sources */, @@ -573,6 +814,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 3A83F7C6295110FF00EF6629 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A5CEC20B20E436F10016922A /* FluentUI.Demo */; + targetProxy = 3A83F7C5295110FF00EF6629 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ A5CEC21A20E436F20016922A /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; @@ -621,6 +870,89 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 3A83F7C7295110FF00EF6629 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = UBF8T346G9; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.FluentUIDemoTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "63d62159-2691-4b44-9553-b668cc1746c1"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = FluentUI.Demo; + }; + name = Debug; + }; + 3A83F7C8295110FF00EF6629 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = UBF8T346G9; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.FluentUIDemoTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "63d62159-2691-4b44-9553-b668cc1746c1"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = FluentUI.Demo; + }; + name = Release; + }; + 3A83F7C9295110FF00EF6629 /* Dogfood */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = UBF8T346G9; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.FluentUIDemoTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "63d62159-2691-4b44-9553-b668cc1746c1"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = FluentUI.Demo; + }; + name = Dogfood; + }; A52B636D21376228009F7ADF /* Dogfood */ = { isa = XCBuildConfiguration; buildSettings = { @@ -893,6 +1225,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 3A83F7CA295110FF00EF6629 /* Build configuration list for PBXNativeTarget "FluentUIDemoTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3A83F7C7295110FF00EF6629 /* Debug */, + 3A83F7C8295110FF00EF6629 /* Release */, + 3A83F7C9295110FF00EF6629 /* Dogfood */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; A5CEC20720E436F10016922A /* Build configuration list for PBXProject "FluentUI.Demo" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/xcshareddata/xcschemes/Demo.Development.xcscheme b/ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/xcshareddata/xcschemes/Demo.Development.xcscheme index 821ea51e9a..56bc8221f0 100644 --- a/ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/xcshareddata/xcschemes/Demo.Development.xcscheme +++ b/ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/xcshareddata/xcschemes/Demo.Development.xcscheme @@ -73,6 +73,21 @@ BlueprintName = "FluentUITests" ReferencedContainer = "container:../FluentUI.xcodeproj"> + + + + + + + + diff --git a/ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/xcshareddata/xcschemes/Demo.Dogfood.xcscheme b/ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/xcshareddata/xcschemes/Demo.Dogfood.xcscheme index 5c7155bd09..dd169699ec 100644 --- a/ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/xcshareddata/xcschemes/Demo.Dogfood.xcscheme +++ b/ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/xcshareddata/xcschemes/Demo.Dogfood.xcscheme @@ -51,6 +51,17 @@ + + + + + + + + UIStackView in let label1 = UILabel() label1.text = "Label 1" diff --git a/ios/FluentUI.Demo/FluentUI.Demo/Demos/TabBarViewDemoController.swift b/ios/FluentUI.Demo/FluentUI.Demo/Demos/TabBarViewDemoController.swift index 6d08a90cfd..960d58806c 100644 --- a/ios/FluentUI.Demo/FluentUI.Demo/Demos/TabBarViewDemoController.swift +++ b/ios/FluentUI.Demo/FluentUI.Demo/Demos/TabBarViewDemoController.swift @@ -40,6 +40,8 @@ class TabBarViewDemoController: DemoController { override func viewDidLoad() { super.viewDidLoad() + readmeString = "The tab bar lets people quickly move through the main sections of an app.\n\nTab bars don’t move people in relation to their current page. If you need to let people go back and forth from their current location, try the navigation bar. If your app has the space for it, you can use a left rail instead of a tab bar." + container.addArrangedSubview(createButton(title: "Show tooltip for Home button", action: #selector(showTooltipForHomeButton))) container.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true diff --git a/ios/FluentUI.Demo/FluentUI.Demo/Demos/TableViewCellDemoController.swift b/ios/FluentUI.Demo/FluentUI.Demo/Demos/TableViewCellDemoController.swift index 20fb56edd4..dc8e219887 100644 --- a/ios/FluentUI.Demo/FluentUI.Demo/Demos/TableViewCellDemoController.swift +++ b/ios/FluentUI.Demo/FluentUI.Demo/Demos/TableViewCellDemoController.swift @@ -87,6 +87,13 @@ class TableViewCellDemoController: DemoTableViewController { } @objc private func selectionBarButtonTapped(sender: UIBarButtonItem) { + if let selectedRows = tableView.indexPathsForSelectedRows, isInSelectionMode { + for indexPath in selectedRows { + if let cell = tableView.cellForRow(at: indexPath) as? TableViewCell { + cell.isUnreadDotVisible = !cell.isUnreadDotVisible + } + } + } isInSelectionMode = !isInSelectionMode } @@ -201,6 +208,8 @@ extension TableViewCellDemoController { } let showsLabelAccessoryView = TableViewCellSampleData.hasLabelAccessoryViews(at: indexPath) + + cell.isUnreadDotVisible = section.isUnreadDotVisible cell.titleLeadingAccessoryView = showsLabelAccessoryView ? item.text1LeadingAccessoryView() : nil cell.titleTrailingAccessoryView = showsLabelAccessoryView ? item.text1TrailingAccessoryView() : nil cell.subtitleLeadingAccessoryView = showsLabelAccessoryView ? item.text2LeadingAccessoryView() : nil diff --git a/ios/FluentUI.Demo/FluentUI.Demo/Demos/TooltipDemoController.swift b/ios/FluentUI.Demo/FluentUI.Demo/Demos/TooltipDemoController.swift index bbd0fe2e16..978c7853dd 100644 --- a/ios/FluentUI.Demo/FluentUI.Demo/Demos/TooltipDemoController.swift +++ b/ios/FluentUI.Demo/FluentUI.Demo/Demos/TooltipDemoController.swift @@ -17,10 +17,11 @@ class TooltipDemoController: DemoController { titleView.setup(title: title ?? "", interactivePart: .title) titleView.delegate = self navigationItem.titleView = titleView - navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Show on title", style: .plain, target: self, action: #selector(showTitleTooltip)) + navigationItem.rightBarButtonItems?.append(UIBarButtonItem(title: "Show on title", style: .plain, target: self, action: #selector(showTitleTooltip))) container.addArrangedSubview(createButton(title: "Show single-line tooltip below", action: #selector(showSingleTooltipBelow))) container.addArrangedSubview(createButton(title: "Show double-line tooltip above", action: #selector(showDoubleTooltipAbove))) + container.addArrangedSubview(createButton(title: "Show tooltip with title above", action: #selector(showTooltipWithTitle))) container.addArrangedSubview(createButton(title: "Show with tap on tooltip dismissal", action: #selector(showTooltipWithTapOnTooltipDismissal))) container.addArrangedSubview(createButton(title: "Show with tap on tooltip or anchor dismissal", action: #selector(showTooltipWithTapOnTooltipOrAnchorDismissal))) container.addArrangedSubview(createLeftRightButtons()) @@ -103,6 +104,10 @@ class TooltipDemoController: DemoController { Tooltip.shared.show(with: "This is a very long message, and this is also pointing down.", for: sender) } + @objc func showTooltipWithTitle(sender: Button) { + Tooltip.shared.show(with: "This is the message of the tooltip.", title: "This is a tooltip title", for: sender) + } + @objc func showTooltipWithTapOnTooltipDismissal(sender: Button) { Tooltip.shared.show(with: "Tap on this tooltip to dismiss.", for: sender, preferredArrowDirection: .up, dismissOn: .tapOnTooltip) } @@ -124,22 +129,11 @@ class TooltipDemoController: DemoController { } @objc func showTopRightOffsetTooltip(sender: Button) { - guard let window = view.window else { - return - } - var margins = Tooltip.defaultScreenMargins - margins.top = edgeCaseStackView.convert(edgeCaseStackView.bounds, to: window).minY - window.safeAreaInsets.top - margins.left = window.frame.inset(by: window.safeAreaInsets).midX - Tooltip.shared.show(with: "This is a very long, offset message.", for: sender, preferredArrowDirection: .right, screenMargins: margins) + Tooltip.shared.show(with: "This is a very long, offset message.", for: sender, preferredArrowDirection: .right) } @objc func showBottomLeftOffsetTooltip(sender: Button) { - guard let window = view.window else { - return - } - var margins = Tooltip.defaultScreenMargins - margins.right = window.frame.inset(by: window.safeAreaInsets).midX - Tooltip.shared.show(with: "This is a very long, offset message.", for: sender, preferredArrowDirection: .left, screenMargins: margins) + Tooltip.shared.show(with: "This is a very long, offset message.", for: sender, preferredArrowDirection: .left) } @objc func showBottomRightOffsetTooltip(sender: Button) { @@ -156,3 +150,53 @@ extension TooltipDemoController: TwoLineTitleViewDelegate { present(alert, animated: true) } } + +// MARK: - TooltipDemoController: DemoAppearanceDelegate + +extension TooltipDemoController: DemoAppearanceDelegate { + func themeWideOverrideDidChange(isOverrideEnabled: Bool) { + guard let fluentTheme = self.view.window?.fluentTheme else { + return + } + + fluentTheme.register(tokenSetType: TooltipTokenSet.self, + tokenSet: isOverrideEnabled ? themeWideOverrideTooltipTokens : nil) + } + + func perControlOverrideDidChange(isOverrideEnabled: Bool) { + Tooltip.shared.tokenSet.replaceAllOverrides(with: isOverrideEnabled ? perControlOverrideTooltipTokens : nil) + } + + func isThemeWideOverrideApplied() -> Bool { + return self.view.window?.fluentTheme.tokens(for: TooltipTokenSet.self) != nil + } + + // MARK: - Custom tokens + private var themeWideOverrideTooltipTokens: [TooltipTokenSet.Tokens: ControlTokenValue] { + return [ + .tooltipColor: .dynamicColor { + // "Berry" + return DynamicColor(light: GlobalTokens.sharedColors(.berry, .shade30), + dark: GlobalTokens.sharedColors(.berry, .tint20)) + } + ] + } + + private var perControlOverrideTooltipTokens: [TooltipTokenSet.Tokens: ControlTokenValue] { + return [ + .tooltipColor: .dynamicColor { + // "Brass" + return DynamicColor(light: GlobalTokens.sharedColors(.brass, .tint40), + dark: GlobalTokens.sharedColors(.brass, .shade30)) + }, + .textColor: .dynamicColor { + // "Forest" + return DynamicColor(light: GlobalTokens.sharedColors(.forest, .shade30), + dark: GlobalTokens.sharedColors(.forest, .tint40)) + }, + .backgroundCornerRadius: .float { + return 0 + } + ] + } +} diff --git a/ios/FluentUI.Demo/FluentUI.Demo/Demos/TypographyTokensDemoController.swift b/ios/FluentUI.Demo/FluentUI.Demo/Demos/TypographyTokensDemoController.swift index 213fba1e0b..0a20aa6d61 100644 --- a/ios/FluentUI.Demo/FluentUI.Demo/Demos/TypographyTokensDemoController.swift +++ b/ios/FluentUI.Demo/FluentUI.Demo/Demos/TypographyTokensDemoController.swift @@ -10,6 +10,7 @@ class TypographyTokensDemoController: DemoTableViewController { override func viewDidLoad() { super.viewDidLoad() tableView.register(UITableViewCell.self, forCellReuseIdentifier: Constants.cellID) + readmeString = "All typography tokens available in the FluentUI framework" } override func numberOfSections(in tableView: UITableView) -> Int { diff --git a/ios/FluentUI.Demo/FluentUI.Demo/ReadmeViewController.swift b/ios/FluentUI.Demo/FluentUI.Demo/ReadmeViewController.swift new file mode 100644 index 0000000000..28ef0fefea --- /dev/null +++ b/ios/FluentUI.Demo/FluentUI.Demo/ReadmeViewController.swift @@ -0,0 +1,66 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import UIKit +import FluentUI + +class ReadmeViewController: UIViewController { + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + readmeLabel.text = readmeString ?? "< Documentation will be added soon >" + + let popoverHeight = readmeLabel.frame.height + Constants.topPadding + preferredContentSize = CGSize(width: Constants.popoverWidth, height: popoverHeight) + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.addSubview(readmeLabel) + setupConstraints() + } + + init(readmeString: String?) { + self.readmeString = readmeString + + super.init(nibName: nil, bundle: nil) + + modalPresentationStyle = .popover + popoverPresentationController?.permittedArrowDirections = .up + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private struct Constants { + static let topPadding: CGFloat = 25 + static let leadingAndTrailingPadding: CGFloat = 20 + static let popoverWidth: CGFloat = 400 + } + + private let readmeLabel: Label = { + let label = Label() + label.numberOfLines = 0 + + // TODO: Change color to fluent 2 tokens + label.textColor = UIColor(dynamicColor: DynamicColor(light: ColorValue(0x000000), + dark: ColorValue(0xFFFFFF))) + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private let readmeString: String? + + private func setupConstraints() { + NSLayoutConstraint.activate([ + readmeLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: Constants.topPadding), + readmeLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: Constants.leadingAndTrailingPadding), + readmeLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -Constants.leadingAndTrailingPadding) + ]) + } +} diff --git a/ios/FluentUI.Demo/FluentUI.Demo/TableViewCellSampleData.swift b/ios/FluentUI.Demo/FluentUI.Demo/TableViewCellSampleData.swift index cd961c864e..2b4f1ad893 100644 --- a/ios/FluentUI.Demo/FluentUI.Demo/TableViewCellSampleData.swift +++ b/ios/FluentUI.Demo/FluentUI.Demo/TableViewCellSampleData.swift @@ -28,7 +28,8 @@ class TableViewCellSampleData: TableViewSampleData { text2: "Research Notes", image: "excelIcon", text2LeadingAccessoryView: { createIconsAccessoryView(images: ["ic_fluent_share_20_regular", "ic_fluent_lock_closed_20_regular"]) }) - ] + ], + isUnreadDotVisible: true ), Section( title: "Inverted double line cell", diff --git a/ios/FluentUI.Demo/FluentUI.Demo/TableViewSampleData.swift b/ios/FluentUI.Demo/FluentUI.Demo/TableViewSampleData.swift index 72932b8a39..64d8c3af09 100644 --- a/ios/FluentUI.Demo/FluentUI.Demo/TableViewSampleData.swift +++ b/ios/FluentUI.Demo/FluentUI.Demo/TableViewSampleData.swift @@ -11,6 +11,7 @@ class TableViewSampleData { let title: String var item: Item { return items[0] } let items: [Item] + let isUnreadDotVisible: Bool let numberOfLines: Int let hasFullLengthLabelAccessoryView: Bool let hasAccessory: Bool @@ -25,9 +26,25 @@ class TableViewSampleData { let hasCustomLeadingView: Bool let hasHandler: Bool - init(title: String, items: [Item] = [], numberOfLines: Int = 1, hasFullLengthLabelAccessoryView: Bool = false, hasAccessory: Bool = false, accessoryButtonStyle: TableViewHeaderFooterView.AccessoryButtonStyle = .regular, allowsMultipleSelection: Bool = true, headerStyle: TableViewHeaderFooterView.Style = .header, hasFooter: Bool = false, footerText: String = "", footerLinkText: String = "", hasCustomLinkHandler: Bool = false, hasCustomAccessoryView: Bool = false, hasCustomLeadingView: Bool = false, hasHandler: Bool = false) { + init(title: String, + items: [Item] = [], + isUnreadDotVisible: Bool = false, + numberOfLines: Int = 1, + hasFullLengthLabelAccessoryView: Bool = false, + hasAccessory: Bool = false, + accessoryButtonStyle: TableViewHeaderFooterView.AccessoryButtonStyle = .regular, + allowsMultipleSelection: Bool = true, + headerStyle: TableViewHeaderFooterView.Style = .header, + hasFooter: Bool = false, + footerText: String = "", + footerLinkText: String = "", + hasCustomLinkHandler: Bool = false, + hasCustomAccessoryView: Bool = false, + hasCustomLeadingView: Bool = false, + hasHandler: Bool = false) { self.title = title self.items = items + self.isUnreadDotVisible = isUnreadDotVisible self.numberOfLines = numberOfLines self.hasFullLengthLabelAccessoryView = hasFullLengthLabelAccessoryView self.hasAccessory = hasAccessory diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/ActivityIndicatorTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/ActivityIndicatorTest.swift new file mode 100644 index 0000000000..b07a6e6765 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/ActivityIndicatorTest.swift @@ -0,0 +1,45 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class ActivityIndicatorTest: BaseTest { + override var controlName: String { "ActivityIndicator" } + + let inProgress: NSPredicate = NSPredicate(format: "identifier CONTAINS %@", "Activity Indicator that is in progress") + let progressHalted: NSPredicate = NSPredicate(format: "identifier CONTAINS %@", "Activity Indicator that is progress halted") + + func activityIndicatorExists(status: NSPredicate) -> Bool { + return app.images.element(matching: status).exists + } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } + + // tests start/stop functionality as well as hiding (activity indicator should disappear when stopped) + func testStartStopHide() throws { + let startStopButton: XCUIElement = app.buttons["Start / Stop activity"] + let hidesWhenStoppedButton: XCUIElement = app.cells.containing(.staticText, identifier: "Hides when stopped").firstMatch + + XCTAssert(activityIndicatorExists(status: inProgress)) + startStopButton.tap() + XCTAssert(!activityIndicatorExists(status: inProgress)) + + hidesWhenStoppedButton.tap() + XCTAssert(activityIndicatorExists(status: progressHalted)) + + startStopButton.tap() + XCTAssert(activityIndicatorExists(status: inProgress)) + } + + func testSizes() throws { + // ensures all 5 activity indicators sizes are shown + for i in 0...4 { + XCTAssert(app.images.containing(NSPredicate(format: "identifier MATCHES %@", "Activity Indicator.*size \(i).*")).element.exists) + } + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/ActivityIndicatorTest_SwiftUI.swift b/ios/FluentUI.Demo/FluentUIDemoTests/ActivityIndicatorTest_SwiftUI.swift new file mode 100644 index 0000000000..b8ac976ff1 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/ActivityIndicatorTest_SwiftUI.swift @@ -0,0 +1,71 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class ActivityIndicatorTestSwiftUI: ActivityIndicatorTest { + override func setUpWithError() throws { + try super.setUpWithError() + app.staticTexts["SwiftUI Demo"].tap() + } + + // launch test that ensures the demo app does not crash and is on the correct control page + override func testLaunch() throws { + XCTAssertTrue(app.navigationBars.element(matching: NSPredicate(format: "identifier CONTAINS %@", controlName)).exists) + } + + override func testStartStopHide() throws { + let animatingSwitch: XCUIElement = app.switches["Animating"] + let hidesWhenStoppedSwitch: XCUIElement = app.switches["Hides when stopped"] + + hidesWhenStoppedSwitch.tap() + XCTAssert(activityIndicatorExists(status: inProgress)) + + animatingSwitch.tap() + XCTAssert(activityIndicatorExists(status: progressHalted)) + + hidesWhenStoppedSwitch.tap() + XCTAssert(!activityIndicatorExists(status: inProgress)) + + animatingSwitch.tap() + XCTAssert(activityIndicatorExists(status: inProgress)) + } + + override func testSizes() throws { + let animatingSwitch: XCUIElement = app.switches["Animating"] + let hidesWhenStoppedSwitch: XCUIElement = app.switches["Hides when stopped"] + + XCTAssert(app.images.containing(NSPredicate(format: "identifier MATCHES %@", "Activity Indicator.*size 4")).element.exists) + app.buttons[".xLarge"].tap() + app.buttons[".large"].tap() + XCTAssert(app.images.containing(NSPredicate(format: "identifier MATCHES %@", "Activity Indicator.*size 3")).element.exists) + app.buttons[".large"].tap() + app.buttons[".medium"].tap() + XCTAssert(app.images.containing(NSPredicate(format: "identifier MATCHES %@", "Activity Indicator.*size 2")).element.exists) + app.buttons[".medium"].tap() + app.buttons[".small"].tap() + XCTAssert(app.images.containing(NSPredicate(format: "identifier MATCHES %@", "Activity Indicator.*size 1")).element.exists) + app.buttons[".small"].tap() + app.buttons[".xSmall"].tap() + XCTAssert(app.images.containing(NSPredicate(format: "identifier MATCHES %@", "Activity Indicator.*size 0")).element.exists) + + hidesWhenStoppedSwitch.tap() + animatingSwitch.tap() + + XCTAssert(app.images.containing(NSPredicate(format: "identifier MATCHES %@", "Activity Indicator.*size 0")).element.exists) + app.buttons[".xSmall"].tap() + app.buttons[".small"].tap() + XCTAssert(app.images.containing(NSPredicate(format: "identifier MATCHES %@", "Activity Indicator.*size 1")).element.exists) + app.buttons[".small"].tap() + app.buttons[".medium"].tap() + XCTAssert(app.images.containing(NSPredicate(format: "identifier MATCHES %@", "Activity Indicator.*size 2")).element.exists) + app.buttons[".medium"].tap() + app.buttons[".large"].tap() + XCTAssert(app.images.containing(NSPredicate(format: "identifier MATCHES %@", "Activity Indicator.*size 3")).element.exists) + app.buttons[".large"].tap() + app.buttons[".xLarge"].tap() + XCTAssert(app.images.containing(NSPredicate(format: "identifier MATCHES %@", "Activity Indicator.*size 4")).element.exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/AvatarGroupTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/AvatarGroupTest.swift new file mode 100644 index 0000000000..0a13b8a240 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/AvatarGroupTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class AvatarGroupTest: BaseTest { + override var controlName: String { "AvatarGroup" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/AvatarTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/AvatarTest.swift new file mode 100644 index 0000000000..18cc629514 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/AvatarTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class AvatarTest: BaseTest { + override var controlName: String { "Avatar" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/AvatarTest_SwiftUI.swift b/ios/FluentUI.Demo/FluentUIDemoTests/AvatarTest_SwiftUI.swift new file mode 100644 index 0000000000..133cf3e6dd --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/AvatarTest_SwiftUI.swift @@ -0,0 +1,20 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class AvatarTestSwiftUI: BaseTest { + override var controlName: String { "Avatar" } + + override func setUpWithError() throws { + try super.setUpWithError() + app.staticTexts["SwiftUI Demo"].tap() + } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars.element(matching: NSPredicate(format: "identifier CONTAINS %@", controlName)).exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/BadgeFieldTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/BadgeFieldTest.swift new file mode 100644 index 0000000000..98e30a5712 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/BadgeFieldTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class BadgeFieldTest: BaseTest { + override var controlName: String { "BadgeField" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/BadgeViewTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/BadgeViewTest.swift new file mode 100644 index 0000000000..89b24c1fac --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/BadgeViewTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class BadgeViewTest: BaseTest { + override var controlName: String { "BadgeView" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/BaseTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/BaseTest.swift new file mode 100644 index 0000000000..5eec75eaa7 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/BaseTest.swift @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class BaseTest: XCTestCase { + let app = XCUIApplication() + // must be overridden + var controlName: String { "Base" } + + override func setUpWithError() throws { + try super.setUpWithError() + continueAfterFailure = false + app.launch() + + let onHomePage: Bool = app.staticTexts["FluentUI DEV"].exists + if !onHomePage { + if !app.navigationBars.element.exists { + app.buttons["Dismiss"].tap() + } else { + app.buttons.firstMatch.tap() + } + } + app.staticTexts[controlName].tap() + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/BottomCommandingControllerTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/BottomCommandingControllerTest.swift new file mode 100644 index 0000000000..33f05096cf --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/BottomCommandingControllerTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class BottomCommandingControllerTest: BaseTest { + override var controlName: String { "BottomCommandingController" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/BottomSheetControllerTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/BottomSheetControllerTest.swift new file mode 100644 index 0000000000..1ddbddbad2 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/BottomSheetControllerTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class BottomSheetControllerTest: BaseTest { + override var controlName: String { "BottomSheetController" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/ButtonTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/ButtonTest.swift new file mode 100644 index 0000000000..e3603bfa73 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/ButtonTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class ButtonTest: BaseTest { + override var controlName: String { "Button" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/CardNudgeTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/CardNudgeTest.swift new file mode 100644 index 0000000000..38d56bc1bc --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/CardNudgeTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class CardNudgeTest: BaseTest { + override var controlName: String { "CardNudge" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/CardTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/CardTest.swift new file mode 100644 index 0000000000..858eb3e829 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/CardTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class CardTest: BaseTest { + override var controlName: String { "Card" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/ColorTokensTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/ColorTokensTest.swift new file mode 100644 index 0000000000..152172e599 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/ColorTokensTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class ColorTokensTest: BaseTest { + override var controlName: String { "ColorTokens" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/CommandBarTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/CommandBarTest.swift new file mode 100644 index 0000000000..822c3bdce1 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/CommandBarTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class CommandBarTest: BaseTest { + override var controlName: String { "CommandBar" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/DateTimePickerTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/DateTimePickerTest.swift new file mode 100644 index 0000000000..75a4c2a895 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/DateTimePickerTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class DateTimePickerTest: BaseTest { + override var controlName: String { "DateTimePicker" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/DrawerControllerTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/DrawerControllerTest.swift new file mode 100644 index 0000000000..90e33928e8 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/DrawerControllerTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class DrawerControllerTest: BaseTest { + override var controlName: String { "DrawerController" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/HUDTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/HUDTest.swift new file mode 100644 index 0000000000..d39da7d0d4 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/HUDTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class HUDTest: BaseTest { + override var controlName: String { "HUD" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/HUDTest_SwiftUI.swift b/ios/FluentUI.Demo/FluentUIDemoTests/HUDTest_SwiftUI.swift new file mode 100644 index 0000000000..39e381d96d --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/HUDTest_SwiftUI.swift @@ -0,0 +1,20 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class HUDTestSwiftUI: BaseTest { + override var controlName: String { "HUD" } + + override func setUpWithError() throws { + try super.setUpWithError() + app.staticTexts["SwiftUI Demo"].tap() + } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars.element(matching: NSPredicate(format: "identifier CONTAINS %@", controlName)).exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/IndeterminateProgressBarTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/IndeterminateProgressBarTest.swift new file mode 100644 index 0000000000..ef82fb9343 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/IndeterminateProgressBarTest.swift @@ -0,0 +1,42 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class IndeterminateProgressBarTest: BaseTest { + override var controlName: String { "IndeterminateProgressBar" } + + let inProgress: NSPredicate = NSPredicate(format: "identifier CONTAINS %@", "Indeterminate Progress Bar that is in progress") + let progressHalted: NSPredicate = NSPredicate(format: "identifier CONTAINS %@", "Indeterminate Progress Bar that is progress halted") + + func indeterminateProgressBarExists(status: NSPredicate) -> Bool { + return app.otherElements.element(matching: status).exists + } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } + + // tests start/stop functionality as well as hiding (indeterminate progress bar should disappear when stopped) + func testStartStopHide() throws { + let startStopButton: XCUIElement = app.buttons["Start / Stop activity"] + let hidesWhenStoppedButton: XCUIElement = app.cells.containing(.staticText, identifier: "Hides when stopped").firstMatch + + XCTAssert(indeterminateProgressBarExists(status: inProgress)) + XCTAssert(!indeterminateProgressBarExists(status: progressHalted)) + startStopButton.tap() + XCTAssert(!indeterminateProgressBarExists(status: inProgress)) + XCTAssert(!indeterminateProgressBarExists(status: progressHalted)) + + hidesWhenStoppedButton.tap() + XCTAssert(!indeterminateProgressBarExists(status: inProgress)) + XCTAssert(indeterminateProgressBarExists(status: progressHalted)) + + startStopButton.tap() + XCTAssert(indeterminateProgressBarExists(status: inProgress)) + XCTAssert(!indeterminateProgressBarExists(status: progressHalted)) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/IndeterminateProgressBarTest_SwiftUI.swift b/ios/FluentUI.Demo/FluentUIDemoTests/IndeterminateProgressBarTest_SwiftUI.swift new file mode 100644 index 0000000000..46cefce98f --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/IndeterminateProgressBarTest_SwiftUI.swift @@ -0,0 +1,39 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class IndeterminateProgressBarTestSwiftUI: IndeterminateProgressBarTest { + override func setUpWithError() throws { + try super.setUpWithError() + app.staticTexts["SwiftUI Demo"].tap() + } + + // launch test that ensures the demo app does not crash and is on the correct control page + override func testLaunch() throws { + XCTAssertTrue(app.navigationBars.element(matching: NSPredicate(format: "identifier CONTAINS %@", controlName)).exists) + } + + override func testStartStopHide() throws { + let animatingSwitch: XCUIElement = app.switches["Animating"] + let hidesWhenStoppedSwitch: XCUIElement = app.switches["Hides when stopped"] + + hidesWhenStoppedSwitch.tap() + XCTAssert(indeterminateProgressBarExists(status: inProgress)) + XCTAssert(!indeterminateProgressBarExists(status: progressHalted)) + + animatingSwitch.tap() + XCTAssert(!indeterminateProgressBarExists(status: inProgress)) + XCTAssert(indeterminateProgressBarExists(status: progressHalted)) + + hidesWhenStoppedSwitch.tap() + XCTAssert(!indeterminateProgressBarExists(status: inProgress)) + XCTAssert(!indeterminateProgressBarExists(status: progressHalted)) + + animatingSwitch.tap() + XCTAssert(indeterminateProgressBarExists(status: inProgress)) + XCTAssert(!indeterminateProgressBarExists(status: progressHalted)) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/LabelTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/LabelTest.swift new file mode 100644 index 0000000000..80cd3ae6e0 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/LabelTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class LabelTest: BaseTest { + override var controlName: String { "Label" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/NavigationControllerTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/NavigationControllerTest.swift new file mode 100644 index 0000000000..c7667f80ac --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/NavigationControllerTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class NavigationControllerTest: BaseTest { + override var controlName: String { "NavigationController" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/NotificationViewTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/NotificationViewTest.swift new file mode 100644 index 0000000000..4f263ddb98 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/NotificationViewTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class NotificationViewTest: BaseTest { + override var controlName: String { "NotificationView" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/NotificationViewTest_SwiftUI.swift b/ios/FluentUI.Demo/FluentUIDemoTests/NotificationViewTest_SwiftUI.swift new file mode 100644 index 0000000000..6985455d69 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/NotificationViewTest_SwiftUI.swift @@ -0,0 +1,20 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class NotificationViewTestSwiftUI: BaseTest { + override var controlName: String { "NotificationView" } + + override func setUpWithError() throws { + try super.setUpWithError() + app.buttons["Show"].firstMatch.tap() + } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars.element(matching: NSPredicate(format: "identifier CONTAINS %@", "Notification View")).exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/OtherCellsTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/OtherCellsTest.swift new file mode 100644 index 0000000000..e3329ed6d7 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/OtherCellsTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class OtherCellsTest: BaseTest { + override var controlName: String { "Other cells" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/PeoplePickerTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/PeoplePickerTest.swift new file mode 100644 index 0000000000..6964d1cc43 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/PeoplePickerTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class PeoplePickerTest: BaseTest { + override var controlName: String { "PeoplePicker" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/PersonaButtonCarouselTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/PersonaButtonCarouselTest.swift new file mode 100644 index 0000000000..73f8da33b3 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/PersonaButtonCarouselTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class PersonaButtonCarouselTest: BaseTest { + override var controlName: String { "PersonaButtonCarousel" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/PersonaListViewTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/PersonaListViewTest.swift new file mode 100644 index 0000000000..448aeb5065 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/PersonaListViewTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class PersonaListViewTest: BaseTest { + override var controlName: String { "PersonaListView" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/PillButtonBarTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/PillButtonBarTest.swift new file mode 100644 index 0000000000..39f99cfafa --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/PillButtonBarTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class PillButtonBarTest: BaseTest { + override var controlName: String { "PillButtonBar" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/PillButtonTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/PillButtonTest.swift new file mode 100644 index 0000000000..5a4c34566c --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/PillButtonTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class PillButtonTest: BaseTest { + override var controlName: String { "PillButton" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/PopupMenuControllerTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/PopupMenuControllerTest.swift new file mode 100644 index 0000000000..1de459d9b7 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/PopupMenuControllerTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class PopupMenuControllerTest: BaseTest { + override var controlName: String { "PopupMenuController" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/SearchBarTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/SearchBarTest.swift new file mode 100644 index 0000000000..fc28ac99d3 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/SearchBarTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class SearchBarTest: BaseTest { + override var controlName: String { "SearchBar" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/SegmentedControlTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/SegmentedControlTest.swift new file mode 100644 index 0000000000..6a273d93a8 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/SegmentedControlTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class SegmentedControlTest: BaseTest { + override var controlName: String { "SegmentedControl" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/ShimmerViewTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/ShimmerViewTest.swift new file mode 100644 index 0000000000..66f3303ee9 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/ShimmerViewTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class ShimmerViewTest: BaseTest { + override var controlName: String { "ShimmerView" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/SideTabBarTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/SideTabBarTest.swift new file mode 100644 index 0000000000..2bafb27c34 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/SideTabBarTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class SideTabBarTest: BaseTest { + override var controlName: String { "SideTabBar" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(!app.navigationBars.element.exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/TabBarViewTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/TabBarViewTest.swift new file mode 100644 index 0000000000..4f4719e617 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/TabBarViewTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class TabBarViewTest: BaseTest { + override var controlName: String { "TabBarView" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/TableViewCellFileAccessoryViewTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/TableViewCellFileAccessoryViewTest.swift new file mode 100644 index 0000000000..0224257fc9 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/TableViewCellFileAccessoryViewTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class TableViewCellFileAccessoryViewTest: BaseTest { + override var controlName: String { "TableViewCellFileAccessoryView" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/TableViewCellShimmerTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/TableViewCellShimmerTest.swift new file mode 100644 index 0000000000..55a7e44f21 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/TableViewCellShimmerTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class TableViewCellShimmerTest: BaseTest { + override var controlName: String { "TableViewCellShimmer" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/TableViewCellTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/TableViewCellTest.swift new file mode 100644 index 0000000000..339f7bf58f --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/TableViewCellTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class TableViewCellTest: BaseTest { + override var controlName: String { "TableViewCell" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/TableViewHeaderFooterViewTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/TableViewHeaderFooterViewTest.swift new file mode 100644 index 0000000000..f2ca2460f2 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/TableViewHeaderFooterViewTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class TableViewHeaderFooterViewTest: BaseTest { + override var controlName: String { "TableViewHeaderFooterView" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/TooltipTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/TooltipTest.swift new file mode 100644 index 0000000000..39b534a802 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/TooltipTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class TooltipTest: BaseTest { + override var controlName: String { "Tooltip" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Demo/FluentUIDemoTests/TypographyTokensTest.swift b/ios/FluentUI.Demo/FluentUIDemoTests/TypographyTokensTest.swift new file mode 100644 index 0000000000..5f296fbfa1 --- /dev/null +++ b/ios/FluentUI.Demo/FluentUIDemoTests/TypographyTokensTest.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import XCTest + +class TypographyTokensTest: BaseTest { + override var controlName: String { "TypographyTokens" } + + // launch test that ensures the demo app does not crash and is on the correct control page + func testLaunch() throws { + XCTAssertTrue(app.navigationBars[controlName].exists) + } +} diff --git a/ios/FluentUI.Tests/ColorTests.swift b/ios/FluentUI.Tests/ColorTests.swift index e026e2104c..dd9706432b 100644 --- a/ios/FluentUI.Tests/ColorTests.swift +++ b/ios/FluentUI.Tests/ColorTests.swift @@ -8,12 +8,6 @@ import XCTest class ColorTests: XCTestCase { - func testColorsExist() throws { - for paletteColor in Colors.Palette.allCases { - XCTAssertNotNil(paletteColor.color) - } - } - func testColorValue() { let hexColorValue = ColorValue(0xC7E0F4) XCTAssertEqual(hexColorValue.a, 1.0) diff --git a/ios/FluentUI.xcodeproj/project.pbxproj b/ios/FluentUI.xcodeproj/project.pbxproj index 5866a4a7b8..7e3545f1f5 100644 --- a/ios/FluentUI.xcodeproj/project.pbxproj +++ b/ios/FluentUI.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 2A9745DE281733D700E1A1FD /* TableViewCellTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9745DD281733D700E1A1FD /* TableViewCellTokenSet.swift */; }; 43488C46270FAD1300124C71 /* FluentNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43488C44270FAD0200124C71 /* FluentNotification.swift */; }; 4B53505F27F63E3F0033B47F /* NotificationModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B53505E27F63E3F0033B47F /* NotificationModifiers.swift */; }; + 4B8245D8293FC7A200CF0C77 /* TooltipTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8245D7293FC7A200CF0C77 /* TooltipTokenSet.swift */; }; 4BBD651F2755FD9500A8B09E /* MSFNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBD651E2755FD9500A8B09E /* MSFNotification.swift */; }; 4BDBE18928EC9E6F00314696 /* ShimmerTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDBE18828EC9E6F00314696 /* ShimmerTokenSet.swift */; }; 4BF01D9A27B37CF8005B32F2 /* NotificationTokenSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF01D9927B37CF8005B32F2 /* NotificationTokenSet.swift */; }; @@ -104,7 +105,7 @@ 5314E1A725F01A7C0099271A /* ActionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E782C02176AD5E00A7DFCE /* ActionsCell.swift */; }; 5314E1B025F01A980099271A /* Tooltip.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7DF05B21FA7F5000857267 /* Tooltip.swift */; }; 5314E1B125F01A980099271A /* TooltipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7DF05D21FA7FC100857267 /* TooltipView.swift */; }; - 5314E1B225F01A980099271A /* TooltipPositionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7DF05F21FA83C900857267 /* TooltipPositionController.swift */; }; + 5314E1B225F01A980099271A /* TooltipViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7DF05F21FA83C900857267 /* TooltipViewController.swift */; }; 5314E1BB25F01B070099271A /* TouchForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B483323621DEB5A00022B4CC /* TouchForwardingView.swift */; }; 5314E1C425F01B4E0099271A /* TwoLineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5BBE40214C6AF3008964B4 /* TwoLineTitleView.swift */; }; 5314E1CD25F01B730099271A /* AnimationSynchronizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0938E43235E8ED500256251 /* AnimationSynchronizer.swift */; }; @@ -242,6 +243,7 @@ 497DC2D724185885008D86F8 /* PillButtonBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PillButtonBar.swift; sourceTree = ""; }; 497DC2D824185885008D86F8 /* PillButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PillButton.swift; sourceTree = ""; }; 4B53505E27F63E3F0033B47F /* NotificationModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationModifiers.swift; sourceTree = ""; }; + 4B8245D7293FC7A200CF0C77 /* TooltipTokenSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipTokenSet.swift; sourceTree = ""; }; 4BBD651E2755FD9500A8B09E /* MSFNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSFNotification.swift; sourceTree = ""; }; 4BDBE18828EC9E6F00314696 /* ShimmerTokenSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShimmerTokenSet.swift; sourceTree = ""; }; 4BF01D9927B37CF8005B32F2 /* NotificationTokenSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationTokenSet.swift; sourceTree = ""; }; @@ -423,7 +425,7 @@ FD77752F21A490BA00033D58 /* DateTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimePicker.swift; sourceTree = ""; }; FD7DF05B21FA7F5000857267 /* Tooltip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tooltip.swift; sourceTree = ""; }; FD7DF05D21FA7FC100857267 /* TooltipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipView.swift; sourceTree = ""; }; - FD7DF05F21FA83C900857267 /* TooltipPositionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipPositionController.swift; sourceTree = ""; }; + FD7DF05F21FA83C900857267 /* TooltipViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipViewController.swift; sourceTree = ""; }; FD8D26422253FF330078E1D3 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; FD8D26432253FF330078E1D3 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = ""; }; FD8D26442253FF3E0078E1D3 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = ""; }; @@ -1190,8 +1192,9 @@ isa = PBXGroup; children = ( FD7DF05B21FA7F5000857267 /* Tooltip.swift */, - FD7DF05F21FA83C900857267 /* TooltipPositionController.swift */, + 4B8245D7293FC7A200CF0C77 /* TooltipTokenSet.swift */, FD7DF05D21FA7FC100857267 /* TooltipView.swift */, + FD7DF05F21FA83C900857267 /* TooltipViewController.swift */, ); path = Tooltip; sourceTree = ""; @@ -1513,6 +1516,7 @@ 92E7AD5026FE51FF00AE7FF8 /* DynamicColor.swift in Sources */, 5314E14E25F016CD0099271A /* ResizingHandleView.swift in Sources */, 804EDE1528C00CA400371C6B /* ContentHeightResolutionContext.swift in Sources */, + 4B8245D8293FC7A200CF0C77 /* TooltipTokenSet.swift in Sources */, 5314E1A625F01A7C0099271A /* BooleanCell.swift in Sources */, 92F8054E272B2DF3000EAFDB /* CardNudgeModifiers.swift in Sources */, 925D461D26FD133600179583 /* GlobalTokens.swift in Sources */, @@ -1568,7 +1572,7 @@ 5314E11825F015EA0099271A /* PeoplePicker.swift in Sources */, 5303259B26B31B6B00611D05 /* AvatarModifiers.swift in Sources */, 5314E2B225F024B60099271A /* Calendar+Extensions.swift in Sources */, - 5314E1B225F01A980099271A /* TooltipPositionController.swift in Sources */, + 5314E1B225F01A980099271A /* TooltipViewController.swift in Sources */, 5314E08A25F00F2D0099271A /* CommandBar.swift in Sources */, 9231491128BF026A001B033E /* HeadsUpDisplayTokenSet.swift in Sources */, 5314E18D25F0195C0099271A /* ShimmerView.swift in Sources */, diff --git a/ios/FluentUI.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/FluentUI.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index c609b4eba3..0000000000 --- a/ios/FluentUI.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,23 +0,0 @@ -{ - "pins" : [ - { - "identity" : "appcenter-sdk-apple", - "kind" : "remoteSourceControl", - "location" : "https://github.com/microsoft/appcenter-sdk-apple.git", - "state" : { - "revision" : "8354a50fe01a7e54e196d3b5493b5ab53dd5866a", - "version" : "4.4.2" - } - }, - { - "identity" : "plcrashreporter", - "kind" : "remoteSourceControl", - "location" : "https://github.com/microsoft/PLCrashReporter.git", - "state" : { - "revision" : "6b27393cad517c067dceea85fadf050e70c4ceaa", - "version" : "1.10.1" - } - } - ], - "version" : 2 -} diff --git a/ios/FluentUI/ActivityIndicator/ActivityIndicator.swift b/ios/FluentUI/ActivityIndicator/ActivityIndicator.swift index 869f40375c..c7da1c2f79 100644 --- a/ios/FluentUI/ActivityIndicator/ActivityIndicator.swift +++ b/ios/FluentUI/ActivityIndicator/ActivityIndicator.swift @@ -59,9 +59,23 @@ public struct ActivityIndicator: View, TokenizedControlView { "Accessibility.ActivityIndicator.Stopped.label".localized }() - SemiRing(color: color, - thickness: tokenSet[.thickness].float, - accessibilityLabel: accessibilityLabel) +#if DEBUG + let accessibilityIdentifier: String = { + let status: String = state.isAnimating ? "in progress" : "progress halted" + return "Activity Indicator that is \(status) and size \(state.size.rawValue)" + }() + + let semiRing = SemiRing(color: color, + thickness: tokenSet[.thickness].float, + accessibilityLabel: accessibilityLabel, + accessibilityIdentifier: accessibilityIdentifier) +#else + let semiRing = SemiRing(color: color, + thickness: tokenSet[.thickness].float, + accessibilityLabel: accessibilityLabel) +#endif + + semiRing .modifyIf(state.isAnimating, { animatedView in animatedView .rotationEffect(.degrees(rotationAngle), anchor: .center) @@ -92,6 +106,9 @@ public struct ActivityIndicator: View, TokenizedControlView { var color: Color var thickness: CGFloat var accessibilityLabel: String +#if DEBUG + var accessibilityIdentifier: String +#endif public var body: some View { Circle() @@ -103,6 +120,9 @@ public struct ActivityIndicator: View, TokenizedControlView { .accessibilityElement(children: .ignore) .accessibility(addTraits: .isImage) .accessibility(label: Text(accessibilityLabel)) +#if DEBUG + .accessibility(identifier: accessibilityIdentifier) +#endif } private let semiRingStartFraction: CGFloat = 0.0 diff --git a/ios/FluentUI/Badge Field/BadgeField.swift b/ios/FluentUI/Badge Field/BadgeField.swift index 5eb9bc45b2..6632778958 100644 --- a/ios/FluentUI/Badge Field/BadgeField.swift +++ b/ios/FluentUI/Badge Field/BadgeField.swift @@ -749,7 +749,9 @@ open class BadgeField: UIView { } private func updateLabelsVisibility(textFieldContent: String? = nil) { - let textFieldContent = textFieldContent ?? self.textFieldContent + var textFieldContent = textFieldContent ?? self.textFieldContent + // does not trim textFieldContent if it only contains whitespace + textFieldContent = textFieldContent.trimmingCharacters(in: .whitespaces) == "" ? textFieldContent : textFieldContent.trimmed() labelView.isHidden = label.isEmpty placeholderView.isHidden = !labelView.isHidden || !badges.isEmpty || !textFieldContent.isEmpty setNeedsLayout() @@ -1192,13 +1194,13 @@ extension BadgeField: UITextFieldDelegate { let didBadge = badgeText(newString, force: false) if !didBadge { // Placeholder - updateLabelsVisibility(textFieldContent: newString.trimmed()) + updateLabelsVisibility(textFieldContent: newString) badgeFieldDelegate?.badgeField?(self, willChangeTextFieldContentWithText: newString.trimmed()) } return !didBadge } - updateLabelsVisibility(textFieldContent: newString.trimmed()) + updateLabelsVisibility(textFieldContent: newString) // Handle delete key if string.isEmpty { diff --git a/ios/FluentUI/Button/Button.swift b/ios/FluentUI/Button/Button.swift index c0ca7d768a..e258522aa4 100644 --- a/ios/FluentUI/Button/Button.swift +++ b/ios/FluentUI/Button/Button.swift @@ -224,7 +224,8 @@ open class Button: UIButton, TokenizedControlInternal { setTitleColor(UIColor(dynamicColor: tokenSet[.foregroundPressedColor].dynamicColor), for: .highlighted) setTitleColor(UIColor(dynamicColor: tokenSet[.foregroundDisabledColor].dynamicColor), for: .disabled) - if #unavailable(iOS 15.0) { + if #available(iOS 15.0, *) { + } else { titleLabel?.font = UIFont.fluent(tokenSet[.titleFont].fontInfo) } diff --git a/ios/FluentUI/IndeterminateProgressBar/IndeterminateProgressBar.swift b/ios/FluentUI/IndeterminateProgressBar/IndeterminateProgressBar.swift index c6d6211004..7a8d097bb2 100644 --- a/ios/FluentUI/IndeterminateProgressBar/IndeterminateProgressBar.swift +++ b/ios/FluentUI/IndeterminateProgressBar/IndeterminateProgressBar.swift @@ -45,6 +45,9 @@ public struct IndeterminateProgressBar: View, TokenizedControlView { : "Accessibility.ActivityIndicator.Stopped.label".localized }() +#if DEBUG + let accessibilityIdentifier: String = "Indeterminate Progress Bar that is \(state.isAnimating ? "in progress" : "progress halted")" +#endif Rectangle() .fill(LinearGradient(gradient: Gradient(colors: [backgroundColor, gradientColor, backgroundColor]), @@ -57,6 +60,9 @@ public struct IndeterminateProgressBar: View, TokenizedControlView { alignment: .center) .background(backgroundColor) .accessibilityLabel(Text(accessibilityLabel)) +#if DEBUG + .accessibilityIdentifier(accessibilityIdentifier) +#endif .accessibilityAddTraits(.updatesFrequently) .modifyIf(state.isAnimating, { view in view diff --git a/ios/FluentUI/SegmentedControl/SegmentedControl.swift b/ios/FluentUI/SegmentedControl/SegmentedControl.swift index 87812c275a..5d603b4588 100644 --- a/ios/FluentUI/SegmentedControl/SegmentedControl.swift +++ b/ios/FluentUI/SegmentedControl/SegmentedControl.swift @@ -79,7 +79,7 @@ open class SegmentedControl: UIView, TokenizedControlInternal { // |--pillContainerView (used to create 16pt inset on either side) // | |--stackView (fill container view, uses restTabColor) // | | |--buttons (uses restLabelColor) - // | |--pillMaskedLabelsContainerView (fill container view, uses selectedTabColor) + // | |--pillMaskedContentContainerView (fill container view, uses selectedTabColor) // | | |.mask -> selectionView // | | |--pillMaskedLabels (uses selectedLabelColor) // | | |--pillMaskedImages (uses selectedLabelColor) @@ -88,7 +88,7 @@ open class SegmentedControl: UIView, TokenizedControlInternal { // pillContainerView (used to create 16pt inset on either side) // |--stackView (fill container view, uses restTabColor) // | |--buttons (uses restLabelColor) - // |--pillMaskedLabelsContainerView (fill container view, uses selectedTabColor) + // |--pillMaskedContentContainerView (fill container view, uses selectedTabColor) // | |.mask -> selectionView // | |--pillMaskedLabels (uses selectedLabelColor) // | |--pillMaskedImages (uses selectedLabelColor) @@ -195,9 +195,9 @@ open class SegmentedControl: UIView, TokenizedControlInternal { // Update appearance whenever overrideTokens changes. tokenSetSink = tokenSet.sinkChanges { [weak self] in - self?.updateColors() - self?.updateButtons() + self?.updateTokenizedValues() } + updateTokenizedValues() } public required init?(coder aDecoder: NSCoder) { @@ -355,7 +355,7 @@ open class SegmentedControl: UIView, TokenizedControlInternal { open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) invalidateIntrinsicContentSize() - layoutSubviews() + setNeedsLayout() } open override func sizeThatFits(_ size: CGSize) -> CGSize { @@ -399,10 +399,6 @@ open class SegmentedControl: UIView, TokenizedControlInternal { } } - func intrinsicContentSizeInvalidatedForChildView() { - invalidateIntrinsicContentSize() - } - /// Used to retrieve the view from the segment at the specified index open func segmentView(at index: Int) -> UIView? { guard index <= buttons.count else { @@ -416,9 +412,9 @@ open class SegmentedControl: UIView, TokenizedControlInternal { lazy public var tokenSet: SegmentedControlTokenSet = .init(style: { [weak self] in return self?.style ?? .primaryPill }) - var tokenSetSink: AnyCancellable? + private var tokenSetSink: AnyCancellable? - var selectionChangeAnimationDuration: TimeInterval { return 0.2 } + private let selectionChangeAnimationDuration: TimeInterval = 0.2 private func updateButtons() { for (index, button) in buttons.enumerated() { @@ -558,12 +554,10 @@ open class SegmentedControl: UIView, TokenizedControlInternal { } @objc private func themeDidChange(_ notification: Notification) { - guard let window = window, window.isEqual(notification.object) else { + guard let themeView = notification.object as? UIView, self.isDescendant(of: themeView) else { return } - tokenSet.update(window.fluentTheme) - updateColors() - updateButtons() + tokenSet.update(fluentTheme) } private func layoutSelectionView() { @@ -576,6 +570,13 @@ open class SegmentedControl: UIView, TokenizedControlInternal { selectionView.layer.cornerRadius = Constants.pillButtonCornerRadius } + private func updateTokenizedValues() { + updateColors() + updateButtons() + layoutSelectionView() + setNeedsLayout() + } + private func updateAccessibilityHints() { for (index, button) in buttons.enumerated() { button.accessibilityHint = String.localizedStringWithFormat("Accessibility.MSPillButtonBar.Hint".localized, diff --git a/ios/FluentUI/Table View/TableViewCell.swift b/ios/FluentUI/Table View/TableViewCell.swift index 4b03378a7f..aab39a0d2d 100644 --- a/ios/FluentUI/Table View/TableViewCell.swift +++ b/ios/FluentUI/Table View/TableViewCell.swift @@ -159,8 +159,7 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { /// The default leading padding in the cell. @objc public static let defaultPaddingLeading: CGFloat = { - let tokenSet = TableViewCellTokenSet(customViewSize: { .default }) - return tokenSet[.paddingLeading].float + return TableViewCellTokenSet.paddingLeading }() /// The default trailing padding in the cell. @@ -209,6 +208,7 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { @objc public class func height(title: String, subtitle: String = "", footer: String = "", + hasunreadDot: Bool = false, titleLeadingAccessoryView: UIView? = nil, titleTrailingAccessoryView: UIView? = nil, subtitleLeadingAccessoryView: UIView? = nil, @@ -264,6 +264,7 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { /// - titleFont: The title font; If not set, it will default to the font definition in tokens /// - subtitleFont: The subtitle font; If not set, it will default to the font definition in tokens /// - footerFont: The footer font; If not set, it will default to the font definition in tokens + /// - hasunreadDot: Boolean determining whether to show or hide the `unreadDotLayer`, on the leading edge of the `customView`. /// - titleLeadingAccessoryView: The accessory view on the leading edge of the title /// - titleTrailingAccessoryView: The accessory view on the trailing edge of the title /// - subtitleLeadingAccessoryView: The accessory view on the leading edge of the subtitle @@ -293,6 +294,7 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { titleFont: UIFont? = nil, subtitleFont: UIFont? = nil, footerFont: UIFont? = nil, + hasunreadDot: Bool = false, titleLeadingAccessoryView: UIView? = nil, titleTrailingAccessoryView: UIView? = nil, subtitleLeadingAccessoryView: UIView? = nil, @@ -397,6 +399,7 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { /// - title: The title string /// - subtitle: The subtitle string /// - footer: The footer string + /// - hasunreadDot: Boolean determining whether to show or hide the `unreadDotLayer`, on the leading edge of the `customView`. /// - titleLeadingAccessoryView: The accessory view on the leading edge of the title /// - titleTrailingAccessoryView: The accessory view on the trailing edge of the title /// - subtitleLeadingAccessoryView: The accessory view on the leading edge of the subtitle @@ -412,6 +415,7 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { @objc public class func preferredWidth(title: String, subtitle: String = "", footer: String = "", + hasunreadDot: Bool = false, titleLeadingAccessoryView: UIView? = nil, titleTrailingAccessoryView: UIView? = nil, subtitleLeadingAccessoryView: UIView? = nil, @@ -459,6 +463,7 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { /// - titleFont: The title font; If not set, it will default to the font definition in tokens /// - subtitleFont: The subtitle font; If not set, it will default to the font definition in tokens /// - footerFont: The footer font; If not set, it will default to the font definition in tokens + /// - hasunreadDot: Boolean determining whether to show or hide the `unreadDotLayer`, on the leading edge of the `customView`. /// - titleLeadingAccessoryView: The accessory view on the leading edge of the title /// - titleTrailingAccessoryView: The accessory view on the trailing edge of the title /// - subtitleLeadingAccessoryView: The accessory view on the leading edge of the subtitle @@ -484,6 +489,7 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { titleFont: UIFont? = nil, subtitleFont: UIFont? = nil, footerFont: UIFont? = nil, + hasunreadDot: Bool = false, titleLeadingAccessoryView: UIView? = nil, titleTrailingAccessoryView: UIView? = nil, subtitleLeadingAccessoryView: UIView? = nil, @@ -563,25 +569,26 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { text: text, labelAccessoryViewMarginLeading: labelAccessoryViewMarginLeading) let availableWidth = textAreaWidth - (leadingAccessoryAreaWidth + trailingAccessoryAreaWidth + labelAccessoryViewMarginTrailing) + let labelSize = text.preferredSize(for: font, width: availableWidth, numberOfLines: numberOfLines) if isAttributedTextSet, let attributedText = attributedText { - return preferredLabelSize(with: attributedText, availableTextWidth: availableWidth) + let attributedSize = preferredLabelSize(with: attributedText, availableTextWidth: availableWidth) + // The boundingRect method for NSAttributedString does not consider system font size, + // which is the default in the case where there is not .font attribute, resulting in an inaccurate + // height value. Taking the max of labelSize and attributedSize resolves this. + return CGSize(width: attributedSize.width, height: max(labelSize.height, attributedSize.height)) } - return text.preferredSize(for: font, width: availableWidth, numberOfLines: numberOfLines) + + return labelSize } private static func preferredLabelSize(with attributedText: NSAttributedString, availableTextWidth: CGFloat = .greatestFiniteMagnitude) -> CGSize { - // We need to have .usesDeviceMetrics to ensure that there is no trailing clipping in our label. - // However, it causes the bottom portion of the label to be clipped instead. Creating a calculated CGRect - // for width and height accommodates for both scenarios so that there is no clipping. let estimatedBoundsHeight = attributedText.boundingRect( with: CGSize(width: availableTextWidth, height: .greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil) - // We want the larger width value so that the label does not undergo any trucation. return CGSize(width: ceil(availableTextWidth), height: ceil(estimatedBoundsHeight.height)) - } private static func labelPreferredWidth(text: String, @@ -633,16 +640,17 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { } private static func customViewLeadingOffset(isInSelectionMode: Bool, - tokenSet: TableViewCellTokenSet) -> CGFloat { - return tokenSet[.paddingLeading].float + selectionModeAreaWidth(isInSelectionMode: isInSelectionMode, - selectionImageMarginTrailing: TableViewCellTokenSet.selectionImageMarginTrailing, - selectionImageSize: TableViewCellTokenSet.selectionImageSize) + leadingPadding: CGFloat) -> CGFloat { + return leadingPadding + selectionModeAreaWidth(isInSelectionMode: isInSelectionMode, + selectionImageMarginTrailing: TableViewCellTokenSet.selectionImageMarginTrailing, + selectionImageSize: TableViewCellTokenSet.selectionImageSize) } private static func textAreaLeadingOffset(customViewSize: MSFTableViewCellCustomViewSize, isInSelectionMode: Bool, tokenSet: TableViewCellTokenSet) -> CGFloat { - var textAreaLeadingOffset = customViewLeadingOffset(isInSelectionMode: isInSelectionMode, tokenSet: tokenSet) + var textAreaLeadingOffset = customViewLeadingOffset(isInSelectionMode: isInSelectionMode, + leadingPadding: TableViewCellTokenSet.paddingLeading) if customViewSize != .zero { textAreaLeadingOffset += tokenSet[.customViewDimensions].float + tokenSet[.customViewTrailingMargin].float } @@ -760,7 +768,7 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { /// The leading padding. @objc public var paddingLeading: CGFloat { get { - return _paddingLeading ?? tokenSet[.paddingLeading].float + return _paddingLeading ?? TableViewCellTokenSet.paddingLeading } set { if newValue != _paddingLeading { @@ -870,6 +878,28 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { } } + private lazy var unreadDotLayer: CALayer = { + let unreadDotLayer = CALayer() + let unreadDotSize = TableViewCellTokenSet.unreadDotDimensions + unreadDotLayer.bounds.size = CGSize(width: unreadDotSize, height: unreadDotSize) + unreadDotLayer.cornerRadius = unreadDotSize / 2 + unreadDotLayer.backgroundColor = UIColor(dynamicColor: isSelected ? tokenSet[.brandTextColor].dynamicColor : tokenSet[.selectionIndicatorOffColor].dynamicColor).cgColor + return unreadDotLayer + }() + + /// Boolean determining if the unread dot is visible. + @objc open var isUnreadDotVisible: Bool = false { + didSet { + if isUnreadDotVisible { + self.contentView.layer.addSublayer(unreadDotLayer) + accessibilityLabel = String(format: "Accessibility.TabBarItemView.UnreadFormat".localized, title) + } else { + unreadDotLayer.removeFromSuperlayer() + accessibilityLabel = title + } + } + } + /// The accessory view on the leading edge of the title @objc open var titleLeadingAccessoryView: UIView? { didSet { @@ -1494,10 +1524,22 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { ) } + if isUnreadDotVisible { + let unreadDotDimensions: CGFloat = TableViewCellTokenSet.unreadDotDimensions + let unreadDotYOffset = ceil((contentView.frame.height - unreadDotDimensions) / 2) + var unreadDotXOffset = TableViewCell.customViewLeadingOffset(isInSelectionMode: isInSelectionMode, + leadingPadding: TableViewCellTokenSet.unreadDotHorizontalPadding) + if effectiveUserInterfaceLayoutDirection == .rightToLeft { + unreadDotXOffset = self.contentView.frame.width - unreadDotXOffset - unreadDotDimensions + } + unreadDotLayer.frame.origin = CGPoint(x: unreadDotXOffset, y: unreadDotYOffset) + } + if let customView = customView { let customViewDimensions = tokenSet[.customViewDimensions].float let customViewYOffset = ceil((contentView.frame.height - customViewDimensions) / 2) - let customViewXOffset = TableViewCell.customViewLeadingOffset(isInSelectionMode: isInSelectionMode, tokenSet: tokenSet) + let customViewXOffset = TableViewCell.customViewLeadingOffset(isInSelectionMode: isInSelectionMode, + leadingPadding: TableViewCellTokenSet.paddingLeading) customView.frame = CGRect( origin: CGPoint(x: customViewXOffset, y: customViewYOffset), size: CGSize(width: customViewDimensions, height: customViewDimensions) @@ -1693,6 +1735,7 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { subtitleLineBreakMode = .byTruncatingTail footerLineBreakMode = .byTruncatingTail + isUnreadDotVisible = false titleLeadingAccessoryView = nil titleTrailingAccessoryView = nil subtitleLeadingAccessoryView = nil @@ -1934,6 +1977,7 @@ open class TableViewCell: UITableViewCell, TokenizedControlInternal { private func updateSelectionImageColor() { selectionImageView.tintColor = UIColor(dynamicColor: isSelected ? tokenSet[.brandTextColor].dynamicColor : tokenSet[.selectionIndicatorOffColor].dynamicColor) + unreadDotLayer.backgroundColor = UIColor(dynamicColor: tokenSet[.brandTextColor].dynamicColor).cgColor } private func updateSeparator(_ separator: Separator, with type: SeparatorType) { diff --git a/ios/FluentUI/Table View/TableViewCellTokenSet.swift b/ios/FluentUI/Table View/TableViewCellTokenSet.swift index b3cb8b50c1..c1fdba3508 100644 --- a/ios/FluentUI/Table View/TableViewCellTokenSet.swift +++ b/ios/FluentUI/Table View/TableViewCellTokenSet.swift @@ -71,9 +71,6 @@ public class TableViewCellTokenSet: ControlTokenSet MSFTableViewCellCustomViewSize) { @@ -163,9 +160,6 @@ public class TableViewCellTokenSet: ControlTokenSet Void)? - private var gestureView: UIView? - private var dismissMode: DismissMode = .tapAnywhere - - private override init() { } +open class Tooltip: NSObject, TokenizedControlInternal { /// Displays a tooltip based on the current settings, pointing to the supplied anchorView. /// If another tooltip view is already showing, it will be dismissed and the new tooltip will be shown. /// /// - Parameters: /// - message: The text to be displayed on the new tooltip view. + /// - title: The optional bolded text to be displayed above the message on the new tooltip view. /// - anchorView: The view to point to with the new tooltip's arrow. /// - preferredArrowDirection: The preferrred direction for the tooltip's arrow. Only the arrow's axis is guaranteed; the direction may be changed based on available space between the anchorView and the screen's margins. Defaults to down. /// - offset: An offset from the tooltip's default position. - /// - screenMargins: The margins from the window's safe area insets used for laying out the tooltip. Defaults to 16.0 pts on all sides. /// - dismissMode: The mode of tooltip dismissal. Defaults to tapping anywhere. /// - onTap: An optional closure used to do work after the user taps - @objc public func show(with message: String, for anchorView: UIView, preferredArrowDirection: ArrowDirection = .down, offset: CGPoint = CGPoint(x: 0, y: 0), screenMargins: UIEdgeInsets = defaultScreenMargins, dismissOn dismissMode: DismissMode = .tapAnywhere, onTap: (() -> Void)? = nil) { + @objc public func show(with message: String, + title: String?, + for anchorView: UIView, + preferredArrowDirection: ArrowDirection = .down, + offset: CGPoint = CGPoint(x: 0, y: 0), + dismissOn dismissMode: DismissMode = .tapAnywhere, + onTap: (() -> Void)? = nil) { hide() guard let window = anchorView.window else { preconditionFailure("Can't find anchorView's window") } - let boundingRect = window.bounds.inset(by: window.safeAreaInsets).inset(by: screenMargins) - let positionController = TooltipPositionController(anchorView: anchorView, message: message, boundingRect: boundingRect, preferredArrowDirection: preferredArrowDirection, offset: offset, arrowMargin: TooltipView.backgroundCornerRadius, arrowWidth: TooltipView.arrowSize.width) - let tooltipView = TooltipView(message: message, textAlignment: textAlignment, positionController: positionController) - - self.tooltipView = tooltipView - tooltipView.accessibilityViewIsModal = true + tooltipViewController = TooltipViewController(anchorView: anchorView, + message: message, + title: title, + textAlignment: textAlignment, + preferredArrowDirection: preferredArrowDirection, + offset: offset, + arrowMargin: tokenSet[.backgroundCornerRadius].float, + tokenSet: tokenSet) + self.anchorView = anchorView + guard let tooltipViewController = tooltipViewController, + let tooltipView = tooltipViewController.view else { + return + } + let rootViewController = window.rootViewController + let rootView = rootViewController?.view + rootViewController?.addChild(tooltipViewController) self.onTap = onTap - self.dismissMode = UIAccessibility.isVoiceOverRunning ? .tapOnTooltip : dismissMode - let gestureView = TouchForwardingView(frame: window.bounds) self.gestureView = gestureView switch self.dismissMode { case .tapAnywhere: - window.addSubview(tooltipView) - window.addSubview(gestureView) - gestureView.onTouches = { _ in - self.handleTapGesture() + rootView?.addSubview(tooltipView) + rootView?.addSubview(gestureView) + gestureView.onTouches = { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.handleTapGesture() } case .tapOnTooltip, .tapOnTooltipOrAnchor: - window.addSubview(gestureView) - window.addSubview(tooltipView) + rootView?.addSubview(gestureView) + rootView?.addSubview(tooltipView) gestureView.forwardsTouches = false tooltipView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))) if self.dismissMode == .tapOnTooltipOrAnchor { gestureView.passthroughView = anchorView - gestureView.onPassthroughViewTouches = { _ in - self.handleTapGesture() + gestureView.onPassthroughViewTouches = { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.handleTapGesture() } } } - // Layout tooltip - tooltipView.frame = positionController.tooltipRect + tooltipViewController.didMove(toParent: rootViewController) // Animate tooltip tooltipView.alpha = 0.0 @@ -128,9 +102,59 @@ open class Tooltip: NSObject { }) isShowing = true + } - UIDevice.current.beginGeneratingDeviceOrientationNotifications() - NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: UIDevice.orientationDidChangeNotification, object: nil) + /// Displays a tooltip based on the current settings, pointing to the supplied anchorView. + /// If another tooltip view is already showing, it will be dismissed and the new tooltip will be shown. + /// + /// - Parameters: + /// - message: The text to be displayed on the new tooltip view. + /// - anchorView: The view to point to with the new tooltip's arrow. + /// - preferredArrowDirection: The preferrred direction for the tooltip's arrow. Only the arrow's axis is guaranteed; the direction may be changed based on available space between the anchorView and the screen's margins. Defaults to down. + /// - offset: An offset from the tooltip's default position. + /// - dismissMode: The mode of tooltip dismissal. Defaults to tapping anywhere. + /// - onTap: An optional closure used to do work after the user taps + @objc public func show(with message: String, + for anchorView: UIView, + preferredArrowDirection: ArrowDirection = .down, + offset: CGPoint = CGPoint(x: 0, y: 0), + dismissOn dismissMode: DismissMode = .tapAnywhere, + onTap: (() -> Void)? = nil) { + show(with: message, + title: nil, + for: anchorView, + preferredArrowDirection: preferredArrowDirection, + offset: offset, + dismissOn: dismissMode, + onTap: onTap) + } + + /// Displays a tooltip based on the current settings, pointing to the supplied anchorView. + /// If another tooltip view is already showing, it will be dismissed and the new tooltip will be shown. + /// + /// - Parameters: + /// - message: The text to be displayed on the new tooltip view. + /// - anchorView: The view to point to with the new tooltip's arrow. + /// - preferredArrowDirection: The preferrred direction for the tooltip's arrow. Only the arrow's axis is guaranteed; the direction may be changed based on available space between the anchorView and the screen's margins. Defaults to down. + /// - offset: An offset from the tooltip's default position. + /// - screenMargins: The margins from the window's safe area insets used for laying out the tooltip. Defaults to 16.0 pts on all sides. + /// - dismissMode: The mode of tooltip dismissal. Defaults to tapping anywhere. + /// - onTap: An optional closure used to do work after the user taps + @available(*, deprecated, renamed: "show(with:for:preferredArrowDirection:offset:dismissOn:onTap:)", message: "Screen margins value has been tokenized. Passing in that param no longer has any effect and it will be removed in a future update.") + @objc public func show(with message: String, + for anchorView: UIView, + preferredArrowDirection: ArrowDirection = .down, + offset: CGPoint = CGPoint(x: 0, y: 0), + screenMargins: UIEdgeInsets = defaultScreenMargins, + dismissOn dismissMode: DismissMode = .tapAnywhere, + onTap: (() -> Void)? = nil) { + show(with: message, + title: nil, + for: anchorView, + preferredArrowDirection: preferredArrowDirection, + offset: offset, + dismissOn: dismissMode, + onTap: onTap) } /// Hides the currently shown tooltip. @@ -138,25 +162,111 @@ open class Tooltip: NSObject { gestureView?.removeFromSuperview() gestureView = nil - guard let tooltipView = tooltipView else { + guard let tooltipView = tooltipViewController?.view else { return } + tooltipViewController?.willMove(toParent: nil) + // Animate tooltip UIView.animate(withDuration: Constants.animationDuration, delay: 0.0, options: [.beginFromCurrentState, .curveEaseOut], animations: { tooltipView.alpha = 0.0 - }, completion: { _ in + }, completion: { [weak self] _ in + guard let strongSelf = self else { + return + } tooltipView.removeFromSuperview() - UIAccessibility.post(notification: .screenChanged, argument: tooltipView.positionController.anchorView) + UIAccessibility.post(notification: .screenChanged, argument: strongSelf.anchorView) }) - self.tooltipView = nil + tooltipViewController?.removeFromParent() + tooltipViewController = nil onTap = nil isShowing = false + } + + required public init?(coder aDecoder: NSCoder) { + preconditionFailure("init(coder:) has not been implemented") + } + + @objc(MSFTooltipArrowDirection) + public enum ArrowDirection: Int { + case up, down, left, right - UIDevice.current.endGeneratingDeviceOrientationNotifications() + var isVertical: Bool { + switch self { + case .up, .down: + return true + case .left, .right: + return false + } + } + + var opposite: ArrowDirection { + switch self { + case .up: + return .down + case .down: + return .up + case .left: + return .right + case .right: + return .left + } + } + } + + @objc(MSFTooltipDismissMode) + public enum DismissMode: Int { + case tapAnywhere + case tapOnTooltip + case tapOnTooltipOrAnchor + } + + @available(*, deprecated, message: "Screen margins value has been tokenized. Setting this var no longer has any effect and it will be removed in a future update.") + @objc public static let defaultScreenMargins = UIEdgeInsets(top: Constants.defaultMargin, left: Constants.defaultMargin, bottom: Constants.defaultMargin, right: Constants.defaultMargin) + + @objc public static let shared = Tooltip() + + /// The alignment of the text in the tooltip. Defaults to natural alignment (left for LTR languages, right for RTL languages). + @objc public var textAlignment: NSTextAlignment = .natural + /// Whether a tooltip is currently showing. + @objc public private(set) var isShowing: Bool = false + + // MARK: - TokenizedControl + public typealias TokenSetKeyType = TooltipTokenSet.Tokens + public var tokenSet: TooltipTokenSet = .init() + var tokenSetSink: AnyCancellable? + var fluentTheme: FluentTheme { + // Use anchor view to get theme since tooltip view will most likely be nil + guard let anchorView = anchorView else { + return FluentTheme.shared + } + return anchorView.fluentTheme + } + + @objc private func themeDidChange(_ notification: Notification) { + guard let themeView = notification.object as? UIView, let anchorView = anchorView, anchorView.isDescendant(of: themeView) else { + return + } + tokenSet.update(fluentTheme) + updateAppearance() + } + + private override init() { + super.init() + + NotificationCenter.default.addObserver(self, + selector: #selector(themeDidChange), + name: .didChangeTheme, + object: nil) + + // Update appearance whenever `tokenSet` changes. + tokenSetSink = tokenSet.sinkChanges { [weak self] in + self?.updateAppearance() + } } @objc private func handleTapGesture() { @@ -164,7 +274,18 @@ open class Tooltip: NSObject { hide() } - @objc private func orientationChanged(_ notification: Notification) { - hide() + private func updateAppearance() { + tooltipViewController?.updateAppearance() } + + private struct Constants { + static let animationDuration: TimeInterval = 0.1 + static let defaultMargin: CGFloat = 16.0 + } + + private var tooltipViewController: TooltipViewController? + private weak var anchorView: UIView? + private var onTap: (() -> Void)? + private var gestureView: UIView? + private var dismissMode: DismissMode = .tapAnywhere } diff --git a/ios/FluentUI/Tooltip/TooltipPositionController.swift b/ios/FluentUI/Tooltip/TooltipPositionController.swift deleted file mode 100644 index 52fa27076b..0000000000 --- a/ios/FluentUI/Tooltip/TooltipPositionController.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// - -import UIKit - -// MARK: TooltipPositionController - -class TooltipPositionController { - let anchorView: UIView - private(set) var arrowDirection: Tooltip.ArrowDirection = .down - - var arrowPosition: CGFloat { - let minPosition = arrowMargin - var idealPosition: CGFloat - var maxPosition: CGFloat - if arrowDirection.isVertical { - idealPosition = sourcePointInWindow.x - tooltipRect.minX - arrowWidth / 2 - maxPosition = tooltipRect.width - arrowMargin - arrowWidth - } else { - idealPosition = sourcePointInWindow.y - tooltipRect.minY - arrowWidth / 2 - maxPosition = tooltipRect.height - arrowMargin - arrowWidth - } - return max(minPosition, min(idealPosition, maxPosition)) - } - - var tooltipRect: CGRect { - return CGRect(origin: tooltipOrigin, size: tooltipSize) - } - - private lazy var tooltipOrigin: CGPoint = { - var tooltipOrigin = idealTooltipOrigin - if arrowDirection.isVertical { - tooltipOrigin.x = max(boundingRect.minX, min(tooltipOrigin.x, boundingRect.maxX - tooltipSize.width)) - } else { - tooltipOrigin.y = max(boundingRect.minY, min(tooltipOrigin.y, boundingRect.maxY - tooltipSize.height)) - } - tooltipOrigin.x += offset.x - tooltipOrigin.y += offset.y - return tooltipOrigin - }() - - private var idealTooltipOrigin: CGPoint { - switch arrowDirection { - case .up: - return CGPoint(x: sourcePointInWindow.x - tooltipSize.width / 2, y: sourcePointInWindow.y) - case .down: - return CGPoint(x: sourcePointInWindow.x - tooltipSize.width / 2, y: sourcePointInWindow.y - tooltipSize.height) - case .left: - return CGPoint(x: sourcePointInWindow.x, y: sourcePointInWindow.y - tooltipSize.height / 2) - case .right: - return CGPoint(x: sourcePointInWindow.x - tooltipSize.width, y: sourcePointInWindow.y - tooltipSize.height / 2) - } - } - - private var sourcePointInAnchorView: CGPoint { - switch arrowDirection { - case .up: - return CGPoint(x: anchorView.frame.width / 2, y: anchorView.frame.height) - case .down: - return CGPoint(x: anchorView.frame.width / 2, y: 0) - case .right: - return CGPoint(x: 0, y: anchorView.frame.height / 2) - case .left: - return CGPoint(x: anchorView.frame.width, y: anchorView.frame.height / 2) - } - } - - private var sourcePointInWindow: CGPoint { - return anchorView.convert(sourcePointInAnchorView, to: window) - } - - private let preferredArrowDirection: Tooltip.ArrowDirection - private let window: UIView - - private let arrowMargin: CGFloat - private let arrowWidth: CGFloat - private let boundingRect: CGRect - private let offset: CGPoint - - private var tooltipSize: CGSize = .zero - - init(anchorView: UIView, message: String, boundingRect: CGRect, preferredArrowDirection: Tooltip.ArrowDirection, offset: CGPoint, arrowMargin: CGFloat, arrowWidth: CGFloat) { - guard let window = anchorView.window else { - preconditionFailure("Can't find anchorView's window") - } - self.window = window - self.anchorView = anchorView - self.preferredArrowDirection = preferredArrowDirection - self.offset = offset - self.arrowMargin = arrowMargin - self.arrowWidth = arrowWidth - self.boundingRect = boundingRect - setupArrowDirectionAndTooltipSize(for: message) - } - - private func setupArrowDirectionAndTooltipSize(for message: String) { - let preferredBoundingRect = boundingRect.inset(by: anchorViewInset(for: preferredArrowDirection)) - let backupBoundingRect = boundingRect.inset(by: anchorViewInset(for: preferredArrowDirection.opposite)) - let preferredSize = TooltipView.sizeThatFits(preferredBoundingRect.size, message: message, arrowDirection: preferredArrowDirection) - let backupSize = TooltipView.sizeThatFits(backupBoundingRect.size, message: message, arrowDirection: preferredArrowDirection.opposite) - - var usePreferred = true - if preferredArrowDirection.isVertical { - if preferredBoundingRect.height < preferredSize.height && backupBoundingRect.height >= backupSize.height { - usePreferred = false - } - } else { - if preferredBoundingRect.width < preferredSize.width && backupBoundingRect.width >= backupSize.width { - usePreferred = false - } - } - if usePreferred { - arrowDirection = preferredArrowDirection - tooltipSize = preferredSize - } else { - arrowDirection = preferredArrowDirection.opposite - tooltipSize = backupSize - } - } - - private func anchorViewInset(for arrowDirection: Tooltip.ArrowDirection) -> UIEdgeInsets { - guard let window = anchorView.window else { - preconditionFailure("Can't find anchorView's window") - } - - var inset = UIEdgeInsets.zero - let anchorViewFrame = anchorView.convert(anchorView.bounds, to: window) - switch arrowDirection { - case .up: - inset.top = max(anchorViewFrame.maxY - boundingRect.minY, 0) - case .down: - inset.bottom = max(boundingRect.maxY - anchorViewFrame.minY, 0) - case .left: - inset.left = max(anchorViewFrame.maxX - boundingRect.minX, 0) - case .right: - inset.right = max(boundingRect.maxX - anchorViewFrame.minX, 0) - } - return inset - } -} diff --git a/ios/FluentUI/Tooltip/TooltipTokenSet.swift b/ios/FluentUI/Tooltip/TooltipTokenSet.swift new file mode 100644 index 0000000000..dc5046d0cb --- /dev/null +++ b/ios/FluentUI/Tooltip/TooltipTokenSet.swift @@ -0,0 +1,91 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import UIKit + +/// Design token set for the `Tooltip` control. +public class TooltipTokenSet: ControlTokenSet { + public enum Tokens: TokenSetKey { + /// The color of the body of the tooltip. + case tooltipColor + + /// The color of the text within the tooltip. + case textColor + + /// The information for the tooltip's shadow. + case shadowInfo + + /// The radius for the corners of the tooltip. + case backgroundCornerRadius + + /// The TextStyle of the message label. + case messageLabelTextStyle + + /// The TextStyle of the title label. + case titleLabelTextStyle + + /// The maximum width of the tooltip if the device's text size is not an accessibility size (in which case there is no maximum width). + case maximumWidth + + /// The height of the arrow of the tooltip. + case arrowHeight + + /// The width of the arrow of the tooltip. + case arrowWidth + } + + init() { + super.init { token, theme in + switch token { + case .tooltipColor: + return .dynamicColor { theme.aliasTokens.colors[.backgroundDarkStatic] } + + case .textColor: + return .dynamicColor { theme.aliasTokens.colors[.foregroundLightStatic] } + + case .shadowInfo: + return .shadowInfo { theme.aliasTokens.shadow[.shadow16] } + + case .backgroundCornerRadius: + return .float { GlobalTokens.corner(.radius80) } + + case .messageLabelTextStyle: + return .fontInfo { theme.aliasTokens.typography[.body2] } + + case .titleLabelTextStyle: + return .fontInfo { theme.aliasTokens.typography[.body1Strong] } + + case .maximumWidth: + return .float { 250.0 } + + case .arrowHeight: + return .float { 7.0 } + + case .arrowWidth: + return .float { 14.0 } + } + } + } +} + +// MARK: Constants +extension TooltipTokenSet { + + /// The horizontal padding between the text and edges of the tooltip. + static let paddingHorizontal: CGFloat = GlobalTokens.spacing(.size120) + + /// The vertical padding between the text and edges of the tooltip with both a title and message. + static let paddingVerticalWithTitle: CGFloat = GlobalTokens.spacing(.size120) + + /// The vertical padding between the text and edges of the tooltip with just a message. + static let paddingVerticalWithoutTitle: CGFloat = GlobalTokens.spacing(.size80) + + /// The vertical spacing between the title and message. + static let spacingVertical: CGFloat = GlobalTokens.spacing(.size80) + + /// The margins from the window's safe area insets used for laying out the tooltip. + static let screenMargin: CGFloat = GlobalTokens.spacing(.size160) + +} diff --git a/ios/FluentUI/Tooltip/TooltipView.swift b/ios/FluentUI/Tooltip/TooltipView.swift index 60c3450219..46ce0dff88 100644 --- a/ios/FluentUI/Tooltip/TooltipView.swift +++ b/ios/FluentUI/Tooltip/TooltipView.swift @@ -6,117 +6,73 @@ import UIKit // MARK: TooltipView - -class TooltipView: UIView, Shadowable { - - var shadow1: CALayer? - var shadow2: CALayer? - - private struct Constants { - static let messageLabelTextStyle: TextStyle = .subhead - - static let maximumWidth: CGFloat = 500 - - static let paddingHorizontal: CGFloat = 13 - static let totalPaddingVertical: CGFloat = 12 - } - - static let backgroundCornerRadius: CGFloat = 8 - static let arrowSize = CGSize(width: 14.0, height: 7.0) - - /// Returns the tooltip size - static func sizeThatFits(_ size: CGSize, message: String, arrowDirection: Tooltip.ArrowDirection) -> CGSize { - var messageBoundingSize = size - if arrowDirection.isVertical { - messageBoundingSize.height -= arrowSize.height - } else { - messageBoundingSize.width -= arrowSize.height - } - messageBoundingSize.height -= Constants.totalPaddingVertical - let messageLabelFittingSize = messageLabelSizeThatFits(messageBoundingSize, message: message) - var width = messageLabelFittingSize.width + 2 * Constants.paddingHorizontal - var height = messageLabelFittingSize.height + Constants.totalPaddingVertical - if arrowDirection.isVertical { - height += arrowSize.height - } else { - width += arrowSize.height - } - return CGSize(width: width, height: height) - } - - private static func messageLabelSizeThatFits(_ size: CGSize, message: String) -> CGSize { - let boundingWidth = min(Constants.maximumWidth, size.width) - 2 * Constants.paddingHorizontal - return message.preferredSize(for: Constants.messageLabelTextStyle.font, width: boundingWidth) - } - - let positionController: TooltipPositionController - - private let message: String - - private let backgroundView: UIView = { - let view = UIView() - view.layer.cornerRadius = backgroundCornerRadius - view.layer.cornerCurve = .continuous - - return view - }() - - private let arrowImageViewBaseImage: UIImage? - private let arrowImageView: UIImageView - - private let messageLabel: UILabel = { - let label = Label(style: Constants.messageLabelTextStyle) - label.numberOfLines = 0 - return label - }() - - init(message: String, textAlignment: NSTextAlignment, positionController: TooltipPositionController) { +class TooltipView: UIView { + + init(anchorView: UIView, + message: String, + title: String? = nil, + textAlignment: NSTextAlignment, + preferredArrowDirection: Tooltip.ArrowDirection, + offset: CGPoint, + arrowMargin: CGFloat, + tokenSet: TooltipTokenSet) { + self.anchorView = anchorView self.message = message - self.positionController = positionController + self.titleMessage = title + self.preferredArrowDirection = preferredArrowDirection + self.offset = offset + self.arrowMargin = arrowMargin + self.tokenSet = tokenSet - arrowImageViewBaseImage = UIImage.staticImageNamed("tooltip-arrow") + let arrowImageViewBaseImage = UIImage.staticImageNamed("tooltip-arrow") arrowImageView = UIImageView(image: arrowImageViewBaseImage) + arrowImageView.image = arrowImageViewBaseImage?.withTintColor(UIColor(dynamicColor: tokenSet[.tooltipColor].dynamicColor), renderingMode: .alwaysOriginal) super.init(frame: .zero) - updateColors() - - NotificationCenter.default.addObserver(self, - selector: #selector(themeDidChange), - name: .didChangeTheme, - object: nil) - - isAccessibilityElement = true - addSubview(backgroundView) + accessibilityViewIsModal = true arrowImageView.transform = transformForArrowImageView() addSubview(arrowImageView) messageLabel.text = message messageLabel.textAlignment = textAlignment - messageLabel.isAccessibilityElement = false - addSubview(messageLabel) - } - required init?(coder aDecoder: NSCoder) { - preconditionFailure("init(coder:) has not been implemented") + if let titleLabel = titleLabel, let title = title { + titleLabel.text = title + titleLabel.textAlignment = textAlignment + } + + addSubview(textContainer) + + // TODO: Integrate with new applyShadow functionality + // Shadow + layer.insertSublayer(CALayer(), at: 0) + layer.insertSublayer(CALayer(), at: 0) + updateShadows() + + isAccessibilityElement = true } - override func layoutSubviews() { - super.layoutSubviews() + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + func updateTooltipSizeAndOrigin() { + updateArrowDirectionAndTooltipRect(for: message, title: titleMessage, tokenSet: tokenSet) + frame.size = tooltipRect.size backgroundView.frame = bounds - - if positionController.arrowDirection.isVertical { - arrowImageView.frame.origin.x = positionController.arrowPosition + arrowImageView.transform = transformForArrowImageView() + if arrowDirection.isVertical { + arrowImageView.frame.origin.x = arrowPosition backgroundView.frame.size.height -= arrowImageView.frame.height } else { - arrowImageView.frame.origin.y = positionController.arrowPosition + arrowImageView.frame.origin.y = arrowPosition backgroundView.frame.size.width -= arrowImageView.frame.width } - switch positionController.arrowDirection { + switch arrowDirection { case .up: arrowImageView.frame.origin.y = 0.0 backgroundView.frame.origin.y = arrowImageView.frame.maxY @@ -129,20 +85,150 @@ class TooltipView: UIView, Shadowable { arrowImageView.frame.origin.x = bounds.width - arrowImageView.frame.width } - messageLabel.frame = backgroundView.frame.insetBy(dx: Constants.paddingHorizontal, dy: 0) - updateShadow() + updateTextContainerSize() + updateShadows() } - @objc private func themeDidChange(_ notification: Notification) { - guard let themeView = notification.object as? UIView, self.isDescendant(of: themeView) else { - return + func updateFonts() { + messageLabel.font = UIFont.fluent(tokenSet[.messageLabelTextStyle].fontInfo) + if let titleLabel = titleLabel { + titleLabel.font = UIFont.fluent(tokenSet[.titleLabelTextStyle].fontInfo) } + } + + func updateAppearance(tokenSet: TooltipTokenSet) { + // Update tokenSet + self.tokenSet = tokenSet + + updateFonts() + updateTooltipSizeAndOrigin() updateColors() - updateShadow() + } + + // MARK: - Accessibility + override var accessibilityLabel: String? { + get { + guard let title = titleMessage + else { + return message + } + + return title + message + } + set { } + } + + override var accessibilityHint: String? { + get { return "Accessibility.Dismiss.Hint".localized } + set { } + } + + var tooltipRect: CGRect { + return CGRect(origin: tooltipOrigin, size: tooltipSize) + } + + private func updateArrowDirectionAndTooltipRect(for message: String, title: String? = nil, tokenSet: TooltipTokenSet) { + let preferredBoundingRect = boundingRect.inset(by: anchorViewInset(for: preferredArrowDirection)) + let backupBoundingRect = boundingRect.inset(by: anchorViewInset(for: preferredArrowDirection.opposite)) + + guard let window = anchorView?.window else { + return + } + + let isAccessibilityContentSize = window.traitCollection.preferredContentSizeCategory.isAccessibilityCategory + let preferredSize = TooltipView.sizeThatFits(preferredBoundingRect.size, + message: message, + title: title, + isAccessibilityContentSize: isAccessibilityContentSize, + arrowDirection: preferredArrowDirection, + tokenSet: tokenSet) + let backupSize = TooltipView.sizeThatFits(backupBoundingRect.size, + message: message, + title: title, + isAccessibilityContentSize: isAccessibilityContentSize, + arrowDirection: preferredArrowDirection.opposite, + tokenSet: tokenSet) + var usePreferred = true + if (preferredArrowDirection.isVertical && + preferredBoundingRect.height < preferredSize.height && + backupBoundingRect.height >= backupSize.height) || + (!preferredArrowDirection.isVertical && + preferredBoundingRect.width < preferredSize.width && + backupBoundingRect.width >= backupSize.width) { + usePreferred = false + } + + if usePreferred { + arrowDirection = preferredArrowDirection + tooltipSize = preferredSize + } else { + arrowDirection = preferredArrowDirection.opposite + tooltipSize = backupSize + } + + tooltipOrigin = idealTooltipOrigin + if arrowDirection.isVertical { + tooltipOrigin.x = max(boundingRect.minX, min(tooltipOrigin.x, boundingRect.maxX - tooltipSize.width)) + } else { + tooltipOrigin.y = max(boundingRect.minY, min(tooltipOrigin.y, boundingRect.maxY - tooltipSize.height)) + } + tooltipOrigin.x += offset.x + tooltipOrigin.y += offset.y + } + + private func updateTextContainerSize() { + backgroundView.layer.cornerRadius = tokenSet[.backgroundCornerRadius].float + textContainer.frame = backgroundView.frame.insetBy(dx: TooltipTokenSet.paddingHorizontal, dy: (titleMessage != nil) ? TooltipTokenSet.paddingVerticalWithTitle : TooltipTokenSet.paddingVerticalWithoutTitle) + let isAccessibilityContentSize = traitCollection.preferredContentSizeCategory.isAccessibilityCategory + let preferredMessageSize = TooltipView.labelSizeThatFits(textContainer.frame.size, + text: message, + isAccessibilityContentSize: isAccessibilityContentSize, + tokenSet: tokenSet, + isMessage: true) + messageLabel.frame.size = preferredMessageSize + if let titleLabel = titleLabel, let title = titleMessage { + let preferredTitleSize = TooltipView.labelSizeThatFits(textContainer.frame.size, + text: title, + isAccessibilityContentSize: isAccessibilityContentSize, + tokenSet: tokenSet, + isMessage: false) + titleLabel.frame.size = preferredTitleSize + messageLabel.frame.origin = CGPoint(x: 0, y: titleLabel.frame.height + TooltipTokenSet.spacingVertical) + } + } + + private func updateColors() { + let textColor = UIColor(dynamicColor: tokenSet[.textColor].dynamicColor) + backgroundView.backgroundColor = UIColor(dynamicColor: tokenSet[.tooltipColor].dynamicColor) + arrowImageView.image = arrowImageView.image?.withTintColor(UIColor(dynamicColor: tokenSet[.tooltipColor].dynamicColor), renderingMode: .alwaysOriginal) + messageLabel.textColor = textColor + titleLabel?.textColor = textColor + } + + private func updateShadows() { + let backgroundCornerRadius = tokenSet[.backgroundCornerRadius].float + let shadowInfo = tokenSet[.shadowInfo].shadowInfo + if let ambientShadow = layer.sublayers?[1] { + ambientShadow.frame = bounds + ambientShadow.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: backgroundCornerRadius).cgPath + ambientShadow.shadowColor = UIColor(dynamicColor: shadowInfo.colorOne).cgColor + ambientShadow.shadowOpacity = 1 + ambientShadow.shadowOffset = CGSize(width: shadowInfo.xOne, height: shadowInfo.yOne) + ambientShadow.shadowRadius = shadowInfo.blurOne + } + + if let perimeterShadow = layer.sublayers?[0] { + perimeterShadow.frame = bounds + perimeterShadow.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: backgroundCornerRadius).cgPath + perimeterShadow.shadowColor = UIColor(dynamicColor: shadowInfo.colorTwo).cgColor + perimeterShadow.shadowOpacity = 1 + perimeterShadow.shadowOffset = CGSize(width: shadowInfo.xTwo, height: shadowInfo.yTwo) + perimeterShadow.shadowRadius = shadowInfo.blurTwo + } } private func transformForArrowImageView() -> CGAffineTransform { - switch positionController.arrowDirection { + switch arrowDirection { case .up: return CGAffineTransform.identity case .down: @@ -154,27 +240,204 @@ class TooltipView: UIView, Shadowable { } } - private func updateShadow() { - let shadowInfo = fluentTheme.aliasTokens.shadow[.shadow16] - shadowInfo.applyShadow(to: backgroundView) + /// Returns the tooltip size + private static func sizeThatFits(_ size: CGSize, + message: String, + title: String? = nil, + isAccessibilityContentSize: Bool, + arrowDirection: Tooltip.ArrowDirection, + tokenSet: TooltipTokenSet) -> CGSize { + let paddingVertical = (title != nil) ? TooltipTokenSet.paddingVerticalWithTitle : TooltipTokenSet.paddingVerticalWithoutTitle + let totalPaddingHorizontal = 2 * TooltipTokenSet.paddingHorizontal + let arrowHeight = tokenSet[.arrowHeight].float + var textBoundingSize = size + + textBoundingSize.width -= totalPaddingHorizontal + if !arrowDirection.isVertical { + textBoundingSize.width -= arrowHeight + } + + let messageLabelFittingSize = labelSizeThatFits(textBoundingSize, + text: message, + isAccessibilityContentSize: isAccessibilityContentSize, + tokenSet: tokenSet, + isMessage: true) + var width = messageLabelFittingSize.width + var height = messageLabelFittingSize.height + + if let title = title { + let titleLabelFittingSize = labelSizeThatFits(textBoundingSize, + text: title, + isAccessibilityContentSize: isAccessibilityContentSize, + tokenSet: tokenSet, + isMessage: false) + width = max(width, titleLabelFittingSize.width) + height += titleLabelFittingSize.height + } + + if arrowDirection.isVertical { + height += arrowHeight + } else { + width += arrowHeight + } + + height += (title != nil) ? (2 * paddingVertical) + TooltipTokenSet.spacingVertical : 2 * paddingVertical + width += totalPaddingHorizontal + + return CGSize(width: width, height: height) } - private func updateColors() { - let backgroundColor = UIColor(dynamicColor: fluentTheme.aliasTokens.colors[.backgroundDarkStatic]) - backgroundView.backgroundColor = backgroundColor - arrowImageView.image = arrowImageViewBaseImage?.withTintColor(backgroundColor, renderingMode: .alwaysOriginal) - messageLabel.textColor = UIColor(dynamicColor: fluentTheme.aliasTokens.colors[.foregroundLightStatic]) + private static func labelSizeThatFits(_ size: CGSize, + text: String, + isAccessibilityContentSize: Bool, + tokenSet: TooltipTokenSet, + isMessage: Bool) -> CGSize { + let boundingWidth = isAccessibilityContentSize ? size.width : min(tokenSet[.maximumWidth].float - (2 * TooltipTokenSet.paddingHorizontal), size.width) + return text.preferredSize(for: UIFont.fluent(tokenSet[isMessage ? .messageLabelTextStyle : .titleLabelTextStyle].fontInfo), width: boundingWidth) } - // MARK: - Accessibility + private var arrowPosition: CGFloat { + let minPosition = arrowMargin + let arrowWidth = tokenSet[.arrowWidth].float + var idealPosition: CGFloat + var maxPosition: CGFloat + if arrowDirection.isVertical { + idealPosition = sourcePointInWindow.x - tooltipRect.minX - arrowWidth / 2 + maxPosition = tooltipRect.width - arrowMargin - arrowWidth + } else { + idealPosition = sourcePointInWindow.y - tooltipRect.minY - arrowWidth / 2 + maxPosition = tooltipRect.height - arrowMargin - arrowWidth + } + return max(minPosition, min(idealPosition, maxPosition)) + } - override var accessibilityLabel: String? { - get { return messageLabel.text } - set { } + private var idealTooltipOrigin: CGPoint { + switch arrowDirection { + case .up: + return CGPoint(x: sourcePointInWindow.x - tooltipSize.width / 2, y: sourcePointInWindow.y) + case .down: + return CGPoint(x: sourcePointInWindow.x - tooltipSize.width / 2, y: sourcePointInWindow.y - tooltipSize.height) + case .left: + return CGPoint(x: sourcePointInWindow.x, y: sourcePointInWindow.y - tooltipSize.height / 2) + case .right: + return CGPoint(x: sourcePointInWindow.x - tooltipSize.width, y: sourcePointInWindow.y - tooltipSize.height / 2) + } } - override var accessibilityHint: String? { - get { return "Accessibility.Dismiss.Hint".localized } - set { } + private var sourcePointInAnchorView: CGPoint { + guard let anchorView = anchorView else { + assertionFailure("Can't find anchorView") + return CGPoint.zero + } + + switch arrowDirection { + case .up: + return CGPoint(x: anchorView.frame.width / 2, y: anchorView.frame.height) + case .down: + return CGPoint(x: anchorView.frame.width / 2, y: 0) + case .right: + return CGPoint(x: 0, y: anchorView.frame.height / 2) + case .left: + return CGPoint(x: anchorView.frame.width, y: anchorView.frame.height / 2) + } + } + + private var sourcePointInWindow: CGPoint { + guard let anchorView = anchorView else { + assertionFailure("Can't find anchorView") + return CGPoint.zero + } + + return anchorView.convert(sourcePointInAnchorView, to: window) } + + private var boundingRect: CGRect { + let screenMargin = TooltipTokenSet.screenMargin + guard let window = anchorView?.window else { + assertionFailure("Can't find anchorView's window") + return CGRect.zero + } + + return window.bounds.inset(by: window.safeAreaInsets).inset(by: UIEdgeInsets(top: screenMargin, + left: screenMargin, + bottom: screenMargin, + right: screenMargin)) + } + + private func anchorViewInset(for arrowDirection: Tooltip.ArrowDirection) -> UIEdgeInsets { + var inset = UIEdgeInsets.zero + guard let anchorView = anchorView else { + assertionFailure("Can't find anchorView") + return inset + } + + let anchorViewFrame = anchorView.convert(anchorView.bounds, to: window) + switch arrowDirection { + case .up: + inset.top = max(anchorViewFrame.maxY - boundingRect.minY, 0) + case .down: + inset.bottom = max(boundingRect.maxY - anchorViewFrame.minY, 0) + case .left: + inset.left = max(anchorViewFrame.maxX - boundingRect.minX, 0) + case .right: + inset.right = max(boundingRect.maxX - anchorViewFrame.minX, 0) + } + return inset + } + + private weak var anchorView: UIView? + private let message: String + private let titleMessage: String? + private let arrowImageView: UIImageView + private let arrowMargin: CGFloat + private let offset: CGPoint + private let preferredArrowDirection: Tooltip.ArrowDirection + private(set) var arrowDirection: Tooltip.ArrowDirection = .down + private var tooltipSize: CGSize = .zero + private var tooltipOrigin: CGPoint = .zero + private var tokenSet: TooltipTokenSet + + private lazy var backgroundView: UIView = { + let view = UIView() + view.layer.cornerRadius = tokenSet[.backgroundCornerRadius].float + view.layer.cornerCurve = .continuous + view.backgroundColor = UIColor(dynamicColor: tokenSet[.tooltipColor].dynamicColor) + + return view + }() + + private lazy var textContainer: UIView = { + let view = UIView() + view.addSubview(messageLabel) + + if let titleLabel = titleLabel { + view.addSubview(titleLabel) + } + + return view + }() + + private lazy var messageLabel: UILabel = { + let label = Label() + label.font = UIFont.fluent(tokenSet[.messageLabelTextStyle].fontInfo) + label.textColor = UIColor(dynamicColor: tokenSet[.textColor].dynamicColor) + label.numberOfLines = 0 + label.lineBreakStrategy = [] + label.isAccessibilityElement = false + return label + }() + + private lazy var titleLabel: UILabel? = { + if let title = titleMessage { + let label = Label() + label.font = UIFont.fluent(tokenSet[.titleLabelTextStyle].fontInfo) + label.textColor = UIColor(dynamicColor: tokenSet[.textColor].dynamicColor) + label.numberOfLines = 0 + label.lineBreakStrategy = [] + label.isAccessibilityElement = false + return label + } + + return nil + }() } diff --git a/ios/FluentUI/Tooltip/TooltipViewController.swift b/ios/FluentUI/Tooltip/TooltipViewController.swift new file mode 100644 index 0000000000..05a5a41faf --- /dev/null +++ b/ios/FluentUI/Tooltip/TooltipViewController.swift @@ -0,0 +1,71 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +import UIKit + +// MARK: TooltipViewController +class TooltipViewController: UIViewController { + + init(anchorView: UIView, + message: String, + title: String? = nil, + textAlignment: NSTextAlignment, + preferredArrowDirection: Tooltip.ArrowDirection, + offset: CGPoint, + arrowMargin: CGFloat, + tokenSet: TooltipTokenSet) { + tooltipView = TooltipView(anchorView: anchorView, + message: message, + title: title, + textAlignment: textAlignment, + preferredArrowDirection: preferredArrowDirection, + offset: offset, + arrowMargin: arrowMargin, + tokenSet: tokenSet) + self.tokenSet = tokenSet + + super.init(nibName: nil, bundle: nil) + + view.addSubview(tooltipView) + updateAppearance() + } + + required init?(coder aDecoder: NSCoder) { + preconditionFailure("init(coder:) has not been implemented") + } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + coordinator.animate(alongsideTransition: nil) { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.updateTooltipSizeAndOrigin() + } + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + if previousTraitCollection?.preferredContentSizeCategory != traitCollection.preferredContentSizeCategory { + tooltipView.updateFonts() + updateTooltipSizeAndOrigin() + } + } + + func updateAppearance() { + tooltipView.updateAppearance(tokenSet: tokenSet) + view.frame = tooltipView.tooltipRect + } + + private func updateTooltipSizeAndOrigin() { + tooltipView.updateTooltipSizeAndOrigin() + view.frame = tooltipView.tooltipRect + } + + private let tooltipView: TooltipView + private let tokenSet: TooltipTokenSet +} diff --git a/ios/docs/Controls/.attachments/Avatar-AccentIcon.png b/ios/docs/Controls/.attachments/Avatar-AccentIcon.png new file mode 100644 index 0000000000..0770a02a1b Binary files /dev/null and b/ios/docs/Controls/.attachments/Avatar-AccentIcon.png differ diff --git a/ios/docs/Controls/.attachments/Avatar-Icon.png b/ios/docs/Controls/.attachments/Avatar-Icon.png new file mode 100644 index 0000000000..b5e933ec76 Binary files /dev/null and b/ios/docs/Controls/.attachments/Avatar-Icon.png differ diff --git a/ios/docs/Controls/.attachments/Avatar-Image.png b/ios/docs/Controls/.attachments/Avatar-Image.png new file mode 100644 index 0000000000..e98d132b10 Binary files /dev/null and b/ios/docs/Controls/.attachments/Avatar-Image.png differ diff --git a/ios/docs/Controls/.attachments/Avatar-ImageSquare.png b/ios/docs/Controls/.attachments/Avatar-ImageSquare.png new file mode 100644 index 0000000000..e47e7fd4ce Binary files /dev/null and b/ios/docs/Controls/.attachments/Avatar-ImageSquare.png differ diff --git a/ios/docs/Controls/.attachments/Avatar-Initials.png b/ios/docs/Controls/.attachments/Avatar-Initials.png new file mode 100644 index 0000000000..ae01f93e90 Binary files /dev/null and b/ios/docs/Controls/.attachments/Avatar-Initials.png differ diff --git a/ios/docs/Controls/.attachments/Avatar-InitialsSquare.png b/ios/docs/Controls/.attachments/Avatar-InitialsSquare.png new file mode 100644 index 0000000000..2ecb0495f8 Binary files /dev/null and b/ios/docs/Controls/.attachments/Avatar-InitialsSquare.png differ diff --git a/ios/docs/Controls/.attachments/Avatar-OutlinedIcon.png b/ios/docs/Controls/.attachments/Avatar-OutlinedIcon.png new file mode 100644 index 0000000000..afd3a9f24d Binary files /dev/null and b/ios/docs/Controls/.attachments/Avatar-OutlinedIcon.png differ diff --git a/ios/docs/Controls/.attachments/Avatar-OutlinedPrimaryIcon.png b/ios/docs/Controls/.attachments/Avatar-OutlinedPrimaryIcon.png new file mode 100644 index 0000000000..06f7090986 Binary files /dev/null and b/ios/docs/Controls/.attachments/Avatar-OutlinedPrimaryIcon.png differ diff --git a/ios/docs/Controls/.attachments/Avatar-Overflow.png b/ios/docs/Controls/.attachments/Avatar-Overflow.png new file mode 100644 index 0000000000..72221c7e22 Binary files /dev/null and b/ios/docs/Controls/.attachments/Avatar-Overflow.png differ diff --git a/ios/docs/Controls/.attachments/Button-Borderless.png b/ios/docs/Controls/.attachments/Button-Borderless.png new file mode 100644 index 0000000000..03f6503976 Binary files /dev/null and b/ios/docs/Controls/.attachments/Button-Borderless.png differ diff --git a/ios/docs/Controls/.attachments/Button-Danger-Filled.png b/ios/docs/Controls/.attachments/Button-Danger-Filled.png new file mode 100644 index 0000000000..c7dbde9a4e Binary files /dev/null and b/ios/docs/Controls/.attachments/Button-Danger-Filled.png differ diff --git a/ios/docs/Controls/.attachments/Button-Danger-Outline.png b/ios/docs/Controls/.attachments/Button-Danger-Outline.png new file mode 100644 index 0000000000..d941e0ef47 Binary files /dev/null and b/ios/docs/Controls/.attachments/Button-Danger-Outline.png differ diff --git a/ios/docs/Controls/.attachments/Button-Primary-Filled.png b/ios/docs/Controls/.attachments/Button-Primary-Filled.png new file mode 100644 index 0000000000..2dcfc5f9eb Binary files /dev/null and b/ios/docs/Controls/.attachments/Button-Primary-Filled.png differ diff --git a/ios/docs/Controls/.attachments/Button-Primary-Outline.png b/ios/docs/Controls/.attachments/Button-Primary-Outline.png new file mode 100644 index 0000000000..a0185b1108 Binary files /dev/null and b/ios/docs/Controls/.attachments/Button-Primary-Outline.png differ diff --git a/ios/docs/Controls/.attachments/Button-Secondary-Outline.png b/ios/docs/Controls/.attachments/Button-Secondary-Outline.png new file mode 100644 index 0000000000..96d320c168 Binary files /dev/null and b/ios/docs/Controls/.attachments/Button-Secondary-Outline.png differ diff --git a/ios/docs/Controls/.attachments/Button-Tertiary-Outline.png b/ios/docs/Controls/.attachments/Button-Tertiary-Outline.png new file mode 100644 index 0000000000..e5becbee63 Binary files /dev/null and b/ios/docs/Controls/.attachments/Button-Tertiary-Outline.png differ diff --git a/ios/docs/Controls/.attachments/Tooltip-Down.png b/ios/docs/Controls/.attachments/Tooltip-Down.png new file mode 100644 index 0000000000..ecacebc9d1 Binary files /dev/null and b/ios/docs/Controls/.attachments/Tooltip-Down.png differ diff --git a/ios/docs/Controls/.attachments/Tooltip-Left.png b/ios/docs/Controls/.attachments/Tooltip-Left.png new file mode 100644 index 0000000000..dca99e85e7 Binary files /dev/null and b/ios/docs/Controls/.attachments/Tooltip-Left.png differ diff --git a/ios/docs/Controls/.attachments/Tooltip-MessageAndTitle.png b/ios/docs/Controls/.attachments/Tooltip-MessageAndTitle.png new file mode 100644 index 0000000000..474b5e4115 Binary files /dev/null and b/ios/docs/Controls/.attachments/Tooltip-MessageAndTitle.png differ diff --git a/ios/docs/Controls/.attachments/Tooltip-MessageOnly.png b/ios/docs/Controls/.attachments/Tooltip-MessageOnly.png new file mode 100644 index 0000000000..5999ada7ef Binary files /dev/null and b/ios/docs/Controls/.attachments/Tooltip-MessageOnly.png differ diff --git a/ios/docs/Controls/.attachments/Tooltip-Right.png b/ios/docs/Controls/.attachments/Tooltip-Right.png new file mode 100644 index 0000000000..7a20561f12 Binary files /dev/null and b/ios/docs/Controls/.attachments/Tooltip-Right.png differ diff --git a/ios/docs/Controls/.attachments/Tooltip-Up.png b/ios/docs/Controls/.attachments/Tooltip-Up.png new file mode 100644 index 0000000000..6c613b2392 Binary files /dev/null and b/ios/docs/Controls/.attachments/Tooltip-Up.png differ diff --git a/ios/docs/Controls/Avatar.md b/ios/docs/Controls/Avatar.md new file mode 100644 index 0000000000..a853d76f9d --- /dev/null +++ b/ios/docs/Controls/Avatar.md @@ -0,0 +1,47 @@ +# Avatar + +## Overview +`Avatar` is a graphical representation of a user, team, or entity. It can display an image, icon, or initials, and can be either circular or square. + +The different variations of `Avatar` are pictured below. + +#### Circle +| Image | Initials | Icon | Accent Icon | Outline Icon | Outlined Primary Icon | Overflow | +| - | - | - | - | - | - | - | +| ![Avatar-Image.png](.attachments/Avatar-Image.png) | ![Avatar-Initials.png](.attachments/Avatar-Initials.png) | ![Avatar-Icon.png](.attachments/Avatar-Icon.png) | ![Avatar-AccentIcon.png](.attachments/Avatar-AccentIcon.png) | ![Avatar-OutlinedIcon.png](.attachments/Avatar-OutlinedIcon.png) | ![Avatar-OutlinedPrimaryIcon.png](.attachments/Avatar-OutlinedPrimaryIcon.png) | ![Avatar-Overflow.png](.attachments/Avatar-Overflow.png) | + +#### Square +| Image | Initials | +| - | - | +| ![Avatar-ImageSquare.png](.attachments/Avatar-ImageSquare.png) | ![Avatar-InitialsSquare.png](.attachments/Avatar-InitialsSquare.png) | + +## Usage +### UIKit +```Swift +let avatar = MSFAvatar(style: avatarStyle, + size: avatarSize) +let avatarState = avatar.state +avatarState.primaryText = avatarPrimaryText +avatarState.image = avatarImage +``` + +### SwiftUI +```Swift +Avatar(style: style, + size: size, + image: image, + primaryText: primaryText, + secondaryText: secondaryText) +``` + +## Implementation +### Control Name +`Avatar` in Swift, `MSFAvatar` in Objective-C + +### Source Code +- [Avatar.swift](https://github.com/microsoft/fluentui-apple/blob/main/ios/FluentUI/Avatar/Avatar.swift) +- [MSFAvatar.swift](https://github.com/microsoft/fluentui-apple/blob/main/ios/FluentUI/Avatar/MSFAvatar.swift) + +### Sample Code +- [AvatarDemoController.swift](https://github.com/microsoft/fluentui-apple/blob/main/ios/FluentUI.Demo/FluentUI.Demo/Demos/AvatarDemoController.swift) +- [AvatarDemoController_SwiftUI.swift](https://github.com/microsoft/fluentui-apple/blob/main/ios/FluentUI.Demo/FluentUI.Demo/Demos/AvatarDemoController_SwiftUI.swift) diff --git a/ios/docs/Controls/Button.md b/ios/docs/Controls/Button.md new file mode 100644 index 0000000000..6c708a07dc --- /dev/null +++ b/ios/docs/Controls/Button.md @@ -0,0 +1,35 @@ +# Button +## Overview +A `Button` triggers a single action or event. +Use `Button`s for important actions like submitting a response, committing a change, or moving to the next step. + +### Styles +| | Style | +|---|---| +| Primary filled | ![Button-Primary-Filled.png](.attachments/Button-Primary-Filled.png) | +| Primary outline | ![Button-Primary-Outline.png](.attachments/Button-Primary-Outline.png) | +| Danger filled | ![Button-Danger-Filled.png](.attachments/Button-Danger-Filled.png) | +| Danger outline | ![Button-Danger-Outline.png](.attachments/Button-Danger-Outline.png) | +| Secondary outline | ![Button-Secondary-Outline.png](.attachments/Button-Secondary-Outline.png) | +| Tertiary outline | ![Button-Tertiary-Outline.png](.attachments/Button-Tertiary-Outline.png) | +| Borderless | ![Button-Borderless.png](.attachments/Button-Borderless.png) | + +## Usage +### UIKit +The `Button` can be initialized with a `ButtonStyle` parameter as follows: +```Swift +let button = Button(style: style) +``` +### SwiftUI +There is currently no SwiftUI implementation of the `Button` + +## Implementation +### Control Name +`Button` in Swift, `MSFButton` in Objective-C + +### Source Code + - [Button.swift](https://github.com/microsoft/fluentui-apple/blob/main/ios/FluentUI/Button/Button.swift) + - [ButtonTokenSet.swift](https://github.com/microsoft/fluentui-apple/blob/main/ios/FluentUI/Button/ButtonTokenSet.swift) +### Sample Code + - [ButtonDemoController.swift](https://github.com/microsoft/fluentui-apple/blob/main/ios/FluentUI.Demo/FluentUI.Demo/Demos/ButtonDemoController.swift) + diff --git a/ios/docs/Controls/Tooltip.md b/ios/docs/Controls/Tooltip.md new file mode 100644 index 0000000000..dbca4b41e7 --- /dev/null +++ b/ios/docs/Controls/Tooltip.md @@ -0,0 +1,45 @@ +# Tooltip +## Overview +Use tooltips to show small unobtrusive hints on top of your app's UI. These can appear as a result of user interaction, or be triggered thoughtfully to assist the user in learning the details of a new feature they have shown interest in. + +Variations: The tooltip can have an optional title and the tip of the tooltip can point up, down, left, or right. + +#### Text +| Message Only | Message and Title | +| - | - | +| ![Tooltip-MessageOnly.png](.attachments/Tooltip-MessageOnly.png) | ![Tooltip-MessageAndTitle.png](.attachments/Tooltip-MessageAndTitle.png) | + +#### Tooltip Direction +| Up | Down | Left | Right | +| - | - | - | - | +| ![Tooltip-Up.png](.attachments/Tooltip-Up.png) | ![Tooltip-Down.png](.attachments/Tooltip-Down.png) | ![Tooltip-Left.png](.attachments/Tooltip-Left.png) | ![Tooltip-Right.png](.attachments/Tooltip-Right.png) | + +## Usage +Displays a tooltip based on the current settings, pointing to the supplied anchorView. If another tooltip view is already showing, it will be dismissed and the new tooltip will be shown. +``` Swift +Tooltip.shared.show(with: "This is the message of the tooltip.", + title: "This is the title of the tooltip.", + for: sender, + preferredArrowDirection: .up, + offset: CGPoint(x: 0, y: 0), + dismissOn: .tapAnywhere, + onTap: { /* Action after Tapping */ }) +``` +##### Parameters +- message: The text to be displayed on the new tooltip view. +- title: The optional bolded text to be displayed above the message on the new tooltip view. +- anchorView: The view to point to with the new tooltip’s arrow. +- preferredArrowDirection: The preferred direction for the tooltip’s arrow. Only the arrow’s axis is guaranteed; the direction may be changed based on available space between the anchorView and the screen’s margins. Defaults to down. +- offset: An offset from the tooltip’s default position. +- dismissMode: The mode of tooltip dismissal. Defaults to tapping anywhere. +- onTap: An optional closure used to do work after the user taps. + +## Implementation +### Control Name +`Tooltip` in Swift, `MSFTooltip` in Objective-C/UIKit +### Source Code +[Tooltip.swift](https://github.com/microsoft/fluentui-apple/blob/fluent2-tokens/ios/FluentUI/Tooltip/Tooltip.swift) +[TooltipView.swift](https://github.com/microsoft/fluentui-apple/blob/fluent2-tokens/ios/FluentUI/Tooltip/TooltipView.swift) +[TooltipViewController.swift](https://github.com/microsoft/fluentui-apple/blob/fluent2-tokens/ios/FluentUI/Tooltip/TooltipViewController.swift) +### Sample Code +[TooltipDemoController.swift](https://github.com/microsoft/fluentui-apple/blob/fluent2-tokens/ios/FluentUI.Demo/FluentUI.Demo/Demos/TooltipDemoController.swift) diff --git a/macos/xcode/FluentUI.xcodeproj/project.pbxproj b/macos/xcode/FluentUI.xcodeproj/project.pbxproj index e93c4c1745..a6278ed5a2 100644 --- a/macos/xcode/FluentUI.xcodeproj/project.pbxproj +++ b/macos/xcode/FluentUI.xcodeproj/project.pbxproj @@ -8,6 +8,15 @@ /* Begin PBXBuildFile section */ 2F6759B8251E4C2600210B3C /* SimpleObjCTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F6759B7251E4C2600210B3C /* SimpleObjCTest.m */; }; + 3A42751229677C3700F36FBE /* BaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A42751129677C3700F36FBE /* BaseTest.swift */; }; + 3A42751429677C3700F36FBE /* AvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A42751329677C3700F36FBE /* AvatarView.swift */; }; + 3A42751B29677CA100F36FBE /* BadgeViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A42751A29677CA100F36FBE /* BadgeViewTest.swift */; }; + 3A42751D29677CB900F36FBE /* ButtonTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A42751C29677CB900F36FBE /* ButtonTest.swift */; }; + 3A42751F29677CCB00F36FBE /* DatePickerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A42751E29677CCB00F36FBE /* DatePickerTest.swift */; }; + 3A42752129677CE800F36FBE /* FilledTemplateImageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A42752029677CE800F36FBE /* FilledTemplateImageTest.swift */; }; + 3A42752329677CF800F36FBE /* LinkTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A42752229677CF800F36FBE /* LinkTest.swift */; }; + 3A42752529677D0800F36FBE /* SeparatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A42752429677D0800F36FBE /* SeparatorTest.swift */; }; + 3A42752729677D3700F36FBE /* ColorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A42752629677D3700F36FBE /* ColorTest.swift */; }; 3F2506DB25F2C7A90049ED54 /* DynamicColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F2506D825F2C7A90049ED54 /* DynamicColor.swift */; }; 3F2506DC25F2C7A90049ED54 /* Apperance+Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F2506DA25F2C7A90049ED54 /* Apperance+Theme.swift */; }; 53BCB0DB253A72AD00620960 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53BCB0DA253A72AD00620960 /* Colors.swift */; }; @@ -53,6 +62,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 3A42751529677C3700F36FBE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E61C96A92295E8D60006561F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8F5368012295F4BF0098AC8F; + remoteInfo = FluentUITestApp; + }; 8F41CC7B2447B8F40040B851 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E61C96A92295E8D60006561F /* Project object */; @@ -121,6 +137,16 @@ /* Begin PBXFileReference section */ 2F6759B6251E4C2600210B3C /* SimpleObjCTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SimpleObjCTest.h; sourceTree = ""; }; 2F6759B7251E4C2600210B3C /* SimpleObjCTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SimpleObjCTest.m; sourceTree = ""; }; + 3A42750F29677C3700F36FBE /* FluentUIDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FluentUIDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3A42751129677C3700F36FBE /* BaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTest.swift; sourceTree = ""; }; + 3A42751329677C3700F36FBE /* AvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarView.swift; sourceTree = ""; }; + 3A42751A29677CA100F36FBE /* BadgeViewTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeViewTest.swift; sourceTree = ""; }; + 3A42751C29677CB900F36FBE /* ButtonTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTest.swift; sourceTree = ""; }; + 3A42751E29677CCB00F36FBE /* DatePickerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerTest.swift; sourceTree = ""; }; + 3A42752029677CE800F36FBE /* FilledTemplateImageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilledTemplateImageTest.swift; sourceTree = ""; }; + 3A42752229677CF800F36FBE /* LinkTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTest.swift; sourceTree = ""; }; + 3A42752429677D0800F36FBE /* SeparatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorTest.swift; sourceTree = ""; }; + 3A42752629677D3700F36FBE /* ColorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorTest.swift; sourceTree = ""; }; 3F2506D825F2C7A90049ED54 /* DynamicColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicColor.swift; sourceTree = ""; }; 3F2506DA25F2C7A90049ED54 /* Apperance+Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Apperance+Theme.swift"; sourceTree = ""; }; 53BCB0DA253A72AD00620960 /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; @@ -255,6 +281,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 3A42750C29677C3700F36FBE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8F41CC6A2447B60F0040B851 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -302,6 +335,22 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3A42751029677C3700F36FBE /* FluentUIDemoTests */ = { + isa = PBXGroup; + children = ( + 3A42751129677C3700F36FBE /* BaseTest.swift */, + 3A42751329677C3700F36FBE /* AvatarView.swift */, + 3A42751A29677CA100F36FBE /* BadgeViewTest.swift */, + 3A42751C29677CB900F36FBE /* ButtonTest.swift */, + 3A42752629677D3700F36FBE /* ColorTest.swift */, + 3A42751E29677CCB00F36FBE /* DatePickerTest.swift */, + 3A42752029677CE800F36FBE /* FilledTemplateImageTest.swift */, + 3A42752229677CF800F36FBE /* LinkTest.swift */, + 3A42752429677D0800F36FBE /* SeparatorTest.swift */, + ); + path = FluentUIDemoTests; + sourceTree = ""; + }; 3F2506D725F2C7A90049ED54 /* DynamicColor */ = { isa = PBXGroup; children = ( @@ -430,6 +479,7 @@ A257F827251D988E002CAA6E /* FluentUI-apple.xcassets */, A257F825251D987E002CAA6E /* FluentUI-macos.xcassets */, E61C96B42295E8D60006561F /* FluentUI */, + 3A42751029677C3700F36FBE /* FluentUIDemoTests */, 8F41CC6E2447B60F0040B851 /* FluentUIResources-macos */, E6A92D2524BEA85900562BCA /* FluentUITestViewControllers */, 8F5368032295F4BF0098AC8F /* FluentUITestApp */, @@ -453,6 +503,7 @@ 8F41CC6D2447B60F0040B851 /* FluentUIResources-macos.bundle */, E6A92D2424BEA85900562BCA /* libFluentUITestViewControllers.a */, E6A92D3624BEA91F00562BCA /* FluentUISwiftUITestApp.app */, + 3A42750F29677C3700F36FBE /* FluentUIDemoTests.xctest */, ); name = Products; sourceTree = ""; @@ -576,6 +627,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 3A42750E29677C3700F36FBE /* FluentUIDemoTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3A42751929677C3700F36FBE /* Build configuration list for PBXNativeTarget "FluentUIDemoTests" */; + buildPhases = ( + 3A42750B29677C3700F36FBE /* Sources */, + 3A42750C29677C3700F36FBE /* Frameworks */, + 3A42750D29677C3700F36FBE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3A42751629677C3700F36FBE /* PBXTargetDependency */, + ); + name = FluentUIDemoTests; + productName = FluentUIDemoTests; + productReference = 3A42750F29677C3700F36FBE /* FluentUIDemoTests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; 8F41CC6C2447B60F0040B851 /* FluentUIResources-macos */ = { isa = PBXNativeTarget; buildConfigurationList = 8F41CC702447B60F0040B851 /* Build configuration list for PBXNativeTarget "FluentUIResources-macos" */; @@ -694,10 +763,14 @@ E61C96A92295E8D60006561F /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1200; + LastSwiftUpdateCheck = 1410; LastUpgradeCheck = 1340; ORGANIZATIONNAME = "Microsoft Corporation"; TargetAttributes = { + 3A42750E29677C3700F36FBE = { + CreatedOnToolsVersion = 14.1; + TestTargetID = 8F5368012295F4BF0098AC8F; + }; 8F41CC6C2447B60F0040B851 = { CreatedOnToolsVersion = 11.4; }; @@ -769,6 +842,7 @@ targets = ( E61C96B12295E8D60006561F /* FluentUI */, E61C96BA2295E8D60006561F /* FluentUITests */, + 3A42750E29677C3700F36FBE /* FluentUIDemoTests */, 8F5368012295F4BF0098AC8F /* FluentUITestApp */, 8F41CC6C2447B60F0040B851 /* FluentUIResources-macos */, E6A92D2324BEA85900562BCA /* FluentUITestViewControllers */, @@ -778,6 +852,13 @@ /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 3A42750D29677C3700F36FBE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8F41CC6B2447B60F0040B851 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -844,6 +925,22 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 3A42750B29677C3700F36FBE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3A42751229677C3700F36FBE /* BaseTest.swift in Sources */, + 3A42751F29677CCB00F36FBE /* DatePickerTest.swift in Sources */, + 3A42751429677C3700F36FBE /* AvatarView.swift in Sources */, + 3A42751B29677CA100F36FBE /* BadgeViewTest.swift in Sources */, + 3A42751D29677CB900F36FBE /* ButtonTest.swift in Sources */, + 3A42752129677CE800F36FBE /* FilledTemplateImageTest.swift in Sources */, + 3A42752329677CF800F36FBE /* LinkTest.swift in Sources */, + 3A42752729677D3700F36FBE /* ColorTest.swift in Sources */, + 3A42752529677D0800F36FBE /* SeparatorTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8F41CC692447B60F0040B851 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -921,6 +1018,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 3A42751629677C3700F36FBE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8F5368012295F4BF0098AC8F /* FluentUITestApp */; + targetProxy = 3A42751529677C3700F36FBE /* PBXContainerItemProxy */; + }; 8F41CC7C2447B8F40040B851 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 8F41CC6C2447B60F0040B851 /* FluentUIResources-macos */; @@ -1049,6 +1151,90 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 3A42751729677C3700F36FBE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = UBF8T346G9; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.FluentUIDemoTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = FluentUITestApp; + }; + name = Debug; + }; + 3A42751829677C3700F36FBE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = UBF8T346G9; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.FluentUIDemoTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = FluentUITestApp; + }; + name = Release; + }; 8F41CC712447B60F0040B851 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 8F41CC732447B66A0040B851 /* FluentUI_resources.xcconfig */; @@ -1209,6 +1395,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 3A42751929677C3700F36FBE /* Build configuration list for PBXNativeTarget "FluentUIDemoTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3A42751729677C3700F36FBE /* Debug */, + 3A42751829677C3700F36FBE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 8F41CC702447B60F0040B851 /* Build configuration list for PBXNativeTarget "FluentUIResources-macos" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/macos/xcode/FluentUI.xcodeproj/xcshareddata/xcschemes/FluentUITestApp-macOS.xcscheme b/macos/xcode/FluentUI.xcodeproj/xcshareddata/xcschemes/FluentUITestApp-macOS.xcscheme index 532a211494..cee2066982 100644 --- a/macos/xcode/FluentUI.xcodeproj/xcshareddata/xcschemes/FluentUITestApp-macOS.xcscheme +++ b/macos/xcode/FluentUI.xcodeproj/xcshareddata/xcschemes/FluentUITestApp-macOS.xcscheme @@ -67,6 +67,17 @@ ReferencedContainer = "container:FluentUI.xcodeproj"> + + + +