Skip to content

Commit

Permalink
ListItem and ListActionItem (#1871)
Browse files Browse the repository at this point in the history
* ListItem initial MVP (#1845)

Setup new control for ListItem that is a SwiftUI version of MSFTableViewCell

* List Item Demo Setup (#1853)

Setup demo app to show List Item.

* [ListItem] Add support for leading content size in tokens (#1855)

* support custom view size
* fix list item modifiers to not use an @ObservedObject
* rename file

* XCTests for ListItem (#1858)

* update test app to have controls to change swiftui view
* setup all XCTests

* MVP of List Action Item (#1857)

* add List Action Item

* ListActionItem XCTests and demo (#1861)

* add demo and XC tests for listactionitem

* [ListItem] Support VoiceOver (#1864)

* support VoiceOver

* add modifier for handling accessibility element of trailing content

* [ListItem] fix spacing and provide background color API (#1876)

* [ListItem] fix horizontal spacing

* Add static func for getting the list background color
  • Loading branch information
alexanderboswell authored Aug 25, 2023
1 parent 2fad550 commit c2a053a
Show file tree
Hide file tree
Showing 14 changed files with 1,463 additions and 0 deletions.
36 changes: 36 additions & 0 deletions ios/FluentUI.Demo/FluentUI.Demo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@
EC98E2B62992FE5000B9DF91 /* TextFieldObjCDemoController.m in Sources */ = {isa = PBXBuildFile; fileRef = EC98E2B52992FE5000B9DF91 /* TextFieldObjCDemoController.m */; };
ECA9D48A2979F5370048ADEC /* TextFieldDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA9D4892979F5370048ADEC /* TextFieldDemoController.swift */; };
ECD95F5E2A0B19FF00152742 /* BrandedSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD95F5D2A0B19FF00152742 /* BrandedSwitch.swift */; };
F30B74362A7DB168000F63A0 /* ListActionItemDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30B74352A7DB168000F63A0 /* ListActionItemDemoController.swift */; };
F30B74382A7DB177000F63A0 /* ListActionItemDemoController_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30B74372A7DB177000F63A0 /* ListActionItemDemoController_SwiftUI.swift */; };
F30B743A2A7DB7EC000F63A0 /* ListActionItemTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30B74392A7DB7EC000F63A0 /* ListActionItemTest.swift */; };
F30B74492A82DAE8000F63A0 /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30B74472A82DABB000F63A0 /* XCUIElement+Extensions.swift */; };
F362C8082A780EA500BB32BB /* ListItemDemoController_SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F362C8072A780EA500BB32BB /* ListItemDemoController_SwiftUI.swift */; };
F362C80A2A780EBF00BB32BB /* ListItemDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F362C8092A780EBF00BB32BB /* ListItemDemoController.swift */; };
F3DFD3632A7C358000014C6E /* ListItemTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DFD3622A7C358000014C6E /* ListItemTest.swift */; };
FC414E3725888BC300069E73 /* CommandBarDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC414E3625888BC300069E73 /* CommandBarDemoController.swift */; };
FDCF7C8321BF35680058E9E6 /* SegmentedControlDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCF7C8221BF35680058E9E6 /* SegmentedControlDemoController.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -263,6 +270,13 @@
EC98E2B72992FE6900B9DF91 /* TextFieldObjCDemoController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TextFieldObjCDemoController.h; sourceTree = "<group>"; };
ECA9D4892979F5370048ADEC /* TextFieldDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldDemoController.swift; sourceTree = "<group>"; };
ECD95F5D2A0B19FF00152742 /* BrandedSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrandedSwitch.swift; sourceTree = "<group>"; };
F30B74352A7DB168000F63A0 /* ListActionItemDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListActionItemDemoController.swift; sourceTree = "<group>"; };
F30B74372A7DB177000F63A0 /* ListActionItemDemoController_SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListActionItemDemoController_SwiftUI.swift; sourceTree = "<group>"; };
F30B74392A7DB7EC000F63A0 /* ListActionItemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListActionItemTest.swift; sourceTree = "<group>"; };
F30B74472A82DABB000F63A0 /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = "<group>"; };
F362C8072A780EA500BB32BB /* ListItemDemoController_SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemDemoController_SwiftUI.swift; sourceTree = "<group>"; };
F362C8092A780EBF00BB32BB /* ListItemDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemDemoController.swift; sourceTree = "<group>"; };
F3DFD3622A7C358000014C6E /* ListItemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemTest.swift; sourceTree = "<group>"; };
FC414E3625888BC300069E73 /* CommandBarDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandBarDemoController.swift; sourceTree = "<group>"; };
FD41C8F322E28EEB0086F899 /* NavigationControllerDemoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationControllerDemoController.swift; sourceTree = "<group>"; };
FD6AE76C225679A4002CFDFE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/LaunchScreen.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -334,6 +348,7 @@
3A83F7C0295110FF00EF6629 /* FluentUIDemoTests */ = {
isa = PBXGroup;
children = (
F30B744A2A82DAF3000F63A0 /* Utilities */,
3A83F8BC2953B75100EF6629 /* BaseTest.swift */,
3A83F8BA2953B73700EF6629 /* ActivityIndicatorTest.swift */,
3A83F8BE2953B83300EF6629 /* ActivityIndicatorTest_SwiftUI.swift */,
Expand All @@ -357,6 +372,8 @@
3A83F8E02953B99A00EF6629 /* IndeterminateProgressBarTest.swift */,
3A83F8E22953B9A800EF6629 /* IndeterminateProgressBarTest_SwiftUI.swift */,
3A83F8E42953B9B800EF6629 /* LabelTest.swift */,
F30B74392A7DB7EC000F63A0 /* ListActionItemTest.swift */,
F3DFD3622A7C358000014C6E /* ListItemTest.swift */,
3AC1024B29DB969D002BF27E /* MultilineCommandBarTest.swift */,
3A83F8E62953B9D100EF6629 /* NavigationControllerTest.swift */,
3A83F8E82953BA4500EF6629 /* NotificationViewTest.swift */,
Expand Down Expand Up @@ -511,6 +528,10 @@
5328D97926FBA3E900F3723B /* IndeterminateProgressBarDemoController.swift */,
5328D97826FBA3E900F3723B /* IndeterminateProgressBarDemoController_SwiftUI.swift */,
A589F855211BA71000471C23 /* LabelDemoController.swift */,
F30B74352A7DB168000F63A0 /* ListActionItemDemoController.swift */,
F30B74372A7DB177000F63A0 /* ListActionItemDemoController_SwiftUI.swift */,
F362C8092A780EBF00BB32BB /* ListItemDemoController.swift */,
F362C8072A780EA500BB32BB /* ListItemDemoController_SwiftUI.swift */,
3ADA2E1C29C515890020434A /* MultilineCommandBarDemoController.swift */,
FD41C8F322E28EEB0086F899 /* NavigationControllerDemoController.swift */,
A5B6617523A4227300E801DD /* NotificationViewDemoController.swift */,
Expand Down Expand Up @@ -562,6 +583,14 @@
name = Utilities;
sourceTree = "<group>";
};
F30B744A2A82DAF3000F63A0 /* Utilities */ = {
isa = PBXGroup;
children = (
F30B74472A82DABB000F63A0 /* XCUIElement+Extensions.swift */,
);
path = Utilities;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -722,6 +751,7 @@
3AC1024C29DB969D002BF27E /* MultilineCommandBarTest.swift in Sources */,
3A83F9032953BAF700EF6629 /* TabBarViewTest.swift in Sources */,
3A83F8BD2953B75100EF6629 /* BaseTest.swift in Sources */,
F30B74492A82DAE8000F63A0 /* XCUIElement+Extensions.swift in Sources */,
3A83F8F92953BABA00EF6629 /* PopupMenuControllerTest.swift in Sources */,
3A83F8CB2953B8E500EF6629 /* BottomCommandingControllerTest.swift in Sources */,
3A83F9092953BB1B00EF6629 /* TableViewCellShimmerTest.swift in Sources */,
Expand All @@ -739,6 +769,7 @@
3A83F8CD2953B8F200EF6629 /* BottomSheetControllerTest.swift in Sources */,
3A83F8D12953B90D00EF6629 /* CardTest.swift in Sources */,
3A83F8CF2953B90000EF6629 /* ButtonTest.swift in Sources */,
F3DFD3632A7C358000014C6E /* ListItemTest.swift in Sources */,
3A83F8DF2953B97900EF6629 /* HUDTest_SwiftUI.swift in Sources */,
3A83F9012953BAEB00EF6629 /* SideTabBarTest.swift in Sources */,
3A83F8FF2953BADF00EF6629 /* ShimmerViewTest.swift in Sources */,
Expand All @@ -756,6 +787,7 @@
3A83F8E52953B9B800EF6629 /* LabelTest.swift in Sources */,
3A83F8EB2953BA5400EF6629 /* NotificationViewTest_SwiftUI.swift in Sources */,
3A83F8E72953B9D100EF6629 /* NavigationControllerTest.swift in Sources */,
F30B743A2A7DB7EC000F63A0 /* ListActionItemTest.swift in Sources */,
3A83F8C32953B8A400EF6629 /* AvatarTest_SwiftUI.swift in Sources */,
3A83F8E92953BA4500EF6629 /* NotificationViewTest.swift in Sources */,
3A83F8F72953BAAD00EF6629 /* PillButtonBarTest.swift in Sources */,
Expand All @@ -778,6 +810,7 @@
6F453CA528AC536300ED91A4 /* ShadowTokensDemoController.swift in Sources */,
6FEED93B28A6E5520099D178 /* AliasColorTokensDemoController.swift in Sources */,
A5DCA760211E3B4C005F4CB7 /* DemoController.swift in Sources */,
F30B74382A7DB177000F63A0 /* ListActionItemDemoController_SwiftUI.swift in Sources */,
5328D97B26FBA3EA00F3723B /* IndeterminateProgressBarDemoController.swift in Sources */,
5373D55F2694C3070032A3B4 /* AvatarDemoController.swift in Sources */,
7D0931C124AAA3D30072458A /* SideTabBarDemoController.swift in Sources */,
Expand Down Expand Up @@ -807,10 +840,13 @@
92E977B726C7144F008E10A8 /* UIResponder+Extensions.swift in Sources */,
B4EF66542295F1A8007FEAB0 /* TableViewHeaderFooterViewDemoController.swift in Sources */,
2F0A96FC25CA047100EF9736 /* SearchBarDemoController.swift in Sources */,
F362C8082A780EA500BB32BB /* ListItemDemoController_SwiftUI.swift in Sources */,
CCC18C2F2501C75F00BE830E /* CardViewDemoController.swift in Sources */,
F30B74362A7DB168000F63A0 /* ListActionItemDemoController.swift in Sources */,
B4575C5122FB8B6900EBD0EB /* PeoplePickerDemoController.swift in Sources */,
92D5FDFD28AC57650087894B /* TypographyTokensDemoController.swift in Sources */,
A5CEC21220E436F10016922A /* DemoListViewController.swift in Sources */,
F362C80A2A780EBF00BB32BB /* ListItemDemoController.swift in Sources */,
53097D3E27028ADC00A6E4DC /* NotificationViewDemoController.swift in Sources */,
5328D97A26FBA3EA00F3723B /* IndeterminateProgressBarDemoController_SwiftUI.swift in Sources */,
B498141621E42C140077B48D /* TableViewCellDemoController.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions ios/FluentUI.Demo/FluentUI.Demo/Demos.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ struct Demos {
DemoDescriptor("HUD", HUDDemoController.self),
DemoDescriptor("IndeterminateProgressBar", IndeterminateProgressBarDemoController.self),
DemoDescriptor("Label", LabelDemoController.self),
DemoDescriptor("ListActionItem", ListActionItemDemoController.self),
DemoDescriptor("ListItem", ListItemDemoController.self),
DemoDescriptor("MultilineCommandBar", MultilineCommandBarDemoController.self),
DemoDescriptor("NavigationController", NavigationControllerDemoController.self),
DemoDescriptor("NotificationView", NotificationViewDemoController.self),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//

import FluentUI
import UIKit

class ListActionItemDemoController: DemoController {

override func viewDidLoad() {
super.viewDidLoad()
let hostingController = ListActionItemDemoControllerSwiftUI()
self.hostingController = hostingController
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)])

readmeString = "A list item displays a action in list.\n\nUse for displaying full width actions in a list."
}

override func didMove(toParent parent: UIViewController?) {
guard let parent,
let window = parent.view.window,
let hostingController else {
return
}

hostingController.rootView.fluentTheme = window.fluentTheme
}

var hostingController: ListActionItemDemoControllerSwiftUI?
}

extension ListActionItemDemoController: DemoAppearanceDelegate {
func themeWideOverrideDidChange(isOverrideEnabled: Bool) {
guard let fluentTheme = self.view.window?.fluentTheme else {
return
}

fluentTheme.register(tokenSetType: ListItemTokenSet.self,
tokenSet: isOverrideEnabled ? themeWideOverrideListItemTokens : nil)
}

func perControlOverrideDidChange(isOverrideEnabled: Bool) {
guard let fluentTheme = self.view.window?.fluentTheme else {
return
}

fluentTheme.register(tokenSetType: ListItemTokenSet.self,
tokenSet: isOverrideEnabled ? perControlOverrideListItemTokens : nil)
}

func isThemeWideOverrideApplied() -> Bool {
return self.view.window?.fluentTheme.tokens(for: ListItemTokenSet.self) != nil
}

// MARK: - Custom tokens
private var themeWideOverrideListItemTokens: [ListItemTokenSet.Tokens: ControlTokenValue] {
return [
.cellBackgroundGroupedColor: .uiColor {
// "Berry"
return UIColor(light: GlobalTokens.sharedColor(.berry, .tint50),
dark: GlobalTokens.sharedColor(.berry, .shade40))
}
]
}

private var perControlOverrideListItemTokens: [ListItemTokenSet.Tokens: ControlTokenValue] {
return [
.cellBackgroundGroupedColor: .uiColor {
// "Brass"
return UIColor(light: GlobalTokens.sharedColor(.brass, .tint50),
dark: GlobalTokens.sharedColor(.brass, .shade40))
},
.communicationTextColor: .uiColor {
// "Forest"
return UIColor(light: GlobalTokens.sharedColor(.forest, .tint10),
dark: GlobalTokens.sharedColor(.forest, .shade40))
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//

import FluentUI
import SwiftUI

class ListActionItemDemoControllerSwiftUI: UIHostingController<ListActionItemDemoView> {
override init?(coder aDecoder: NSCoder, rootView: ListActionItemDemoView) {
preconditionFailure("init(coder:) has not been implemented")
}

@objc required dynamic init?(coder aDecoder: NSCoder) {
preconditionFailure("init(coder:) has not been implemented")
}

init() {
super.init(rootView: ListActionItemDemoView())
}
}

struct ListActionItemDemoView: View {
@State var showingAlert: Bool = false
@ObservedObject var fluentTheme: FluentTheme = .shared
@State var showSecondaryAction: Bool = false
@State var primaryActionTitle: String = "Search"
@State var secondaryActionTitle: String = "Cancel"
@State var primaryActionType: ListActionItemActionType = .regular
@State var secondaryActionType: ListActionItemActionType = .destructive
@State var topSeparatorType: ListActionItemSeparatorType = .none
@State var bottomSeparatorType: ListActionItemSeparatorType = .inset
@State var backgroundStyleType: ListItemBackgroundStyleType = .grouped

public var body: some View {

@ViewBuilder
var textFields: some View {
TextField("Primary Action Title", text: $primaryActionTitle)
.autocapitalization(.none)
.disableAutocorrection(true)
.textFieldStyle(.roundedBorder)
.accessibilityIdentifier("primaryActionTitleTextField")
if showSecondaryAction {
TextField("Secondary Action Title", text: $secondaryActionTitle)
.autocapitalization(.none)
.disableAutocorrection(true)
.textFieldStyle(.roundedBorder)
.accessibilityIdentifier("secondaryActionTitleTextField")
}
}

@ViewBuilder
var pickers: some View {
let actionTypePickerOptions = Group {
Text(".regular").tag(ListActionItemActionType.regular)
Text(".destructive").tag(ListActionItemActionType.destructive)
Text(".communication").tag(ListActionItemActionType.communication)
}

Picker("Primary Action Type", selection: $primaryActionType) {
actionTypePickerOptions
}
if showSecondaryAction {
Picker("Secondary Action Type", selection: $secondaryActionType) {
actionTypePickerOptions
}
}

let separatorTypePickerOptions = Group {
Text(".none").tag(ListActionItemSeparatorType.none)
Text(".inset").tag(ListActionItemSeparatorType.inset)
Text(".full").tag(ListActionItemSeparatorType.full)
}

Picker("Top Separator Type", selection: $topSeparatorType) {
separatorTypePickerOptions
}

Picker("Bottom Separator Type", selection: $bottomSeparatorType) {
separatorTypePickerOptions
}
}

@ViewBuilder
var content: some View {
List {
Section {
if showSecondaryAction {
ListActionItem(primaryActionTitle: primaryActionTitle,
onPrimaryActionTapped: {
showingAlert.toggle()
},
primaryActionType: primaryActionType,
secondaryActionTitle: secondaryActionTitle,
onSecondaryActionTapped: {
showingAlert.toggle()
},
secondaryActionType: secondaryActionType)
.topSeparatorType(topSeparatorType)
.bottomSeparatorType(bottomSeparatorType)
.backgroundStyleType(backgroundStyleType)
} else {
ListActionItem(title: primaryActionTitle,
onTapped: {
showingAlert.toggle()
},
actionType: primaryActionType)
.topSeparatorType(topSeparatorType)
.bottomSeparatorType(bottomSeparatorType)
.backgroundStyleType(backgroundStyleType)
}

} header: {
Text("ListActionItem")
}
.alert("Action tapped", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
.accessibilityIdentifier("DismissAlertButton")
}

Section {
FluentUIDemoToggle(titleKey: "Show secondary action", isOn: $showSecondaryAction)
.accessibilityIdentifier("showSecondaryActionSwitch")
textFields
pickers
} header: {
Text("Settings")
}
}
.fluentTheme(fluentTheme)
.listStyle(.insetGrouped)
}

return content
}
}
Loading

0 comments on commit c2a053a

Please sign in to comment.