Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend ListItem to support overrideTokens #2047

Merged
merged 8 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,53 +36,3 @@ class ListItemDemoController: DemoController {

var hostingController: ListItemDemoControllerSwiftUI?
}

extension ListItemDemoController: 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))
},
.accessoryDisclosureIndicatorColor: .uiColor {
// "Forest"
return UIColor(light: GlobalTokens.sharedColor(.forest, .tint10),
dark: GlobalTokens.sharedColor(.forest, .shade40))
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,17 @@ struct ListItemDemoView: View {
@State var showTrailingContent: Bool = true
@State var isTappable: Bool = true
@State var isDisabled: Bool = false
@State var renderStandalone: Bool = false
@State var overrideTokens: Bool = false
@State var accessoryType: ListItemAccessoryType = .none
@State var leadingContentSize: ListItemLeadingContentSize = .default
@State var backgroundStyle: ListItemBackgroundStyleType = .grouped
@State var listStyle: FluentListStyle = .plain
@State var titleLineLimit: Int = 1
@State var subtitleLineLimit: Int = 1
@State var footerLineLimit: Int = 1
@State var trailingContentFocusableElementCount: Int = 0
@State var trailingContentToggleEnabled: Bool = true
@State var renderStandalone: Bool = false
@State var listStyle: FluentListStyle = .plain

public var body: some View {

Expand Down Expand Up @@ -80,6 +81,7 @@ struct ListItemDemoView: View {
FluentUIDemoToggle(titleKey: "Tappable", isOn: $isTappable)
FluentUIDemoToggle(titleKey: "Disabled", isOn: $isDisabled)
FluentUIDemoToggle(titleKey: "Render standalone", isOn: $renderStandalone)
FluentUIDemoToggle(titleKey: "Override tokens", isOn: $overrideTokens)
}

@ViewBuilder
Expand Down Expand Up @@ -148,58 +150,59 @@ struct ListItemDemoView: View {

@ViewBuilder
var listItem: some View {
ListItem(title: title,
subtitle: showSubtitle ? subtitle : "",
footer: showFooter ? footer : "",
leadingContent: {
if showLeadingContent {
leadingContent
}
},
trailingContent: {
if showTrailingContent {
switch trailingContentFocusableElementCount {
case 0:
Text("Spreadsheet")
case 1:
Toggle("", isOn: $trailingContentToggleEnabled)
default:
HStack {
Button {
showingSecondaryAlert = true
} label: {
Text("Button 1")
}
Button {
showingSecondaryAlert = true
} label: {
Text("Button 2")
}
}
}
}
},
action: !isTappable ? nil : {
showingPrimaryAlert = true
}
)
.backgroundStyleType(backgroundStyle)
.accessoryType(accessoryType)
.leadingContentSize(leadingContentSize)
.titleLineLimit(titleLineLimit)
.subtitleLineLimit(subtitleLineLimit)
.footerLineLimit(footerLineLimit)
.combineTrailingContentAccessibilityElement(trailingContentFocusableElementCount < 2)
.onAccessoryTapped {
showingSecondaryAlert = true
}
.disabled(isDisabled)
.alert("List Item tapped", isPresented: $showingPrimaryAlert) {
Button("OK", role: .cancel) { }
}
.alert("Detail button tapped", isPresented: $showingSecondaryAlert) {
Button("OK", role: .cancel) { }
}
var listItem = ListItem(title: title,
subtitle: showSubtitle ? subtitle : "",
footer: showFooter ? footer : "",
leadingContent: {
if showLeadingContent {
leadingContent
}
},
trailingContent: {
if showTrailingContent {
switch trailingContentFocusableElementCount {
case 0:
Text("Spreadsheet")
case 1:
Toggle("", isOn: $trailingContentToggleEnabled)
default:
HStack {
Button {
showingSecondaryAlert = true
} label: {
Text("Button 1")
}
Button {
showingSecondaryAlert = true
} label: {
Text("Button 2")
}
}
}
}
},
action: !isTappable ? nil : {
showingPrimaryAlert = true
})
.backgroundStyleType(backgroundStyle)
.accessoryType(accessoryType)
.leadingContentSize(leadingContentSize)
.titleLineLimit(titleLineLimit)
.subtitleLineLimit(subtitleLineLimit)
.footerLineLimit(footerLineLimit)
.combineTrailingContentAccessibilityElement(trailingContentFocusableElementCount < 2)
.onAccessoryTapped {
showingSecondaryAlert = true
}
listItem
.overrideTokens($overrideTokens.wrappedValue ? listItemTokenOverrides : [:])
.disabled(isDisabled)
joannaquu marked this conversation as resolved.
Show resolved Hide resolved
.alert("List Item tapped", isPresented: $showingPrimaryAlert) {
Button("OK", role: .cancel) { }
}
.alert("Detail button tapped", isPresented: $showingSecondaryAlert) {
Button("OK", role: .cancel) { }
}
}

@ViewBuilder
Expand All @@ -223,6 +226,22 @@ struct ListItemDemoView: View {

return content
}

private var listItemTokenOverrides: [ListItemToken: ControlTokenValue] {
return [
.titleColor: .uiColor {
GlobalTokens.sharedColor(.red, .primary)
},
.cellBackgroundGroupedColor: .uiColor {
UIColor(light: GlobalTokens.sharedColor(.brass, .tint50),
dark: GlobalTokens.sharedColor(.brass, .shade40))
},
.accessoryDisclosureIndicatorColor: .uiColor {
UIColor(light: GlobalTokens.sharedColor(.forest, .tint10),
dark: GlobalTokens.sharedColor(.forest, .shade40))
}
]
}
}

struct UIViewWrapper: UIViewRepresentable {
Expand Down
25 changes: 15 additions & 10 deletions ios/FluentUI/List/ListItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public typealias ListItemAccessoryType = TableViewCellAccessoryType
public typealias ListItemBackgroundStyleType = TableViewCellBackgroundStyleType
public typealias ListItemLeadingContentSize = MSFTableViewCellCustomViewSize
public typealias ListItemTokenSet = TableViewCellTokenSet
public typealias ListItemToken = TableViewCellToken

/// View that represents an item in a List.
public struct ListItem<LeadingContent: View,
Expand Down Expand Up @@ -39,11 +40,11 @@ public struct ListItem<LeadingContent: View,
self.leadingContent = leadingContent
self.trailingContent = trailingContent
self.action = action
let layoutType = ListItem.layoutType(subtitle: subtitle, footer: footer)
self.tokenSet = ListItemTokenSet(customViewSize: { layoutType.leadingContentSize })
}

public var body: some View {
let tokenSet = ListItemTokenSet(customViewSize: { leadingContentSize })
tokenSet.replaceAllOverrides(with: tokenOverrides)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, what's the perf impact observed or expected here? We can't guarantee when body runs so hopefully can guarantee this is dirt cheap?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We talked about this offline quite a bit, and yep, token set creation is dirt cheap. We'll work in the future to make it even cheaper, but performance should be totally acceptable right now.

tokenSet.update(fluentTheme)

@ViewBuilder
Expand Down Expand Up @@ -295,8 +296,8 @@ public struct ListItem<LeadingContent: View,
/// The truncation mode of the `footer`.
var footerTruncationMode: Text.TruncationMode = .tail

/// Tokens associated with the `ListItem`.
var tokenSet: ListItemTokenSet
/// The size of the `LeadingContent`.
var leadingContentSize: ListItemLeadingContentSize = .default

/// Whether or not the `TrailingContent` should be combined or be a separate accessibility element.
var combineTrailingContentAccessibilityElement: Bool = true
Expand Down Expand Up @@ -331,6 +332,8 @@ public struct ListItem<LeadingContent: View,
private let footer: Footer
private let subtitle: Subtitle
private let title: Title

private var tokenOverrides: [ListItemToken: ControlTokenValue]?
}

// MARK: Internal structs
Expand Down Expand Up @@ -381,8 +384,6 @@ public extension ListItem where LeadingContent == EmptyView, TrailingContent ==
self.subtitle = subtitle
self.footer = footer
self.action = action
let layoutType = ListItem.layoutType(subtitle: subtitle, footer: footer)
self.tokenSet = ListItemTokenSet(customViewSize: { layoutType.leadingContentSize })
}
}

Expand All @@ -397,8 +398,6 @@ public extension ListItem where TrailingContent == EmptyView {
self.footer = footer
self.leadingContent = leadingContent
self.action = action
let layoutType = ListItem.layoutType(subtitle: subtitle, footer: footer)
self.tokenSet = ListItemTokenSet(customViewSize: { layoutType.leadingContentSize })
}
}

Expand All @@ -413,8 +412,6 @@ public extension ListItem where LeadingContent == EmptyView {
self.footer = footer
self.trailingContent = trailingContent
self.action = action
let layoutType = ListItem.layoutType(subtitle: subtitle, footer: footer)
self.tokenSet = ListItemTokenSet(customViewSize: { layoutType.leadingContentSize })
}
}

Expand All @@ -436,3 +433,11 @@ public extension ListItem where LeadingContent == EmptyView, TrailingContent ==
}
}
}

public extension ListItem {
/// Provide override values for various `ListItem` values.
mutating func overrideTokens(_ overrides: [ListItemToken: ControlTokenValue]) -> Self {
tokenOverrides = overrides
return self
}
}
2 changes: 1 addition & 1 deletion ios/FluentUI/List/ListItemModifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public extension ListItem {
/// - Returns: The modified `ListItem` with the property set.
func leadingContentSize(_ size: ListItemLeadingContentSize) -> ListItem {
var listItem = self
listItem.tokenSet = ListItemTokenSet(customViewSize: { size })
listItem.leadingContentSize = size
return listItem
}

Expand Down
Loading