Skip to content

Commit 88e9824

Browse files
ludovic35pylapp
andcommitted
feat: add bullet list component (#513) (#1279)
Closes #513 Add bullet list component WARNING: Missing a11y review Assisted-by: GitHub Copilot Acked-by: Ahmed Amine Zribi <ahmedamine.zribi@sofrecom.com> Tested-by: Hamza Amarir <hamza.amarir@orange.com> Tested-by: Jérôme Regnier <jerome.regnier@orange.com> Reviewed-by: Pierre-Yves Lapersonne <pierreyves.lapersonne@orange.com> Reviewed-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Ludovic Pinel <ludovic.pinel@orange.com> Co-authored-by: Pierre-Yves Lapersonne <pierreyves.lapersonne@orange.com> Signed-off-by: Pierre-Yves Lapersonne <pierreyves.lapersonne@orange.com>
1 parent 77463e0 commit 88e9824

File tree

64 files changed

+1769
-20
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1769
-20
lines changed

AGENTS.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ Everything is available on [our guidelines](https://a11y-guidelines.orange.com/f
241241

242242
- Minimum Swift 6.2 (e.g. 6.2.3)
243243
- Xcode 26.2 or later
244-
- Minimum deployment: iOS 15.0, iPad0S 15.0, macOS 15.0, visionOS 1.0, watchOS 11.6, tvOS 16.6
244+
- Minimum deployment: iOS 15.0, iPad0S 15.0, macOS 13.0, visionOS 1.0, watchOS 11.6, tvOS 16.6
245245
- Apple Developer account for device testing
246246

247247
## 10. Building commands
@@ -515,6 +515,40 @@ or only with an image:
515515
OUDSButton(icon: Image("image_name"), accessibilityLabel: "Some label") { /* the action to process */ }
516516
```
517517

518+
### Content Display
519+
520+
#### Bullet list
521+
522+
A bullet list is a list of elements which can be ordered or not. OUDS bullet lists can have up to 3 levels of depth.
523+
A bullet list can be unordered (default), ordered or bare (i.e. without leading chips not indices).
524+
525+
Its documentation is [available online](https://ios.unified-design-system.orange.com/documentation/oudscomponents/oudsbulletlist/).
526+
527+
A bullet list by default is not ordered:
528+
```swift
529+
OUDSBulletList {
530+
OUDSBulletList.Item("Label 1")
531+
OUDSBulletList.Item("Label 2")
532+
OUDSBulletList.Item("Label 3")
533+
}
534+
```
535+
536+
A bullet lsit can have several levels of depth by nesting items
537+
```swift
538+
OUDSBulletList(type: .ordered) {
539+
OUDSBulletList.Item("Label 1") {
540+
OUDSBulletList.Item("Label 1.1") {
541+
OUDSBulletList.Item("Label 1.1.1")
542+
OUDSBulletList.Item("Label 1.1.2")
543+
}
544+
}
545+
OUDSBulletList.Item("Label 2")
546+
OUDSBulletList.Item("Label 3")
547+
}
548+
```
549+
550+
### Controls
551+
518552
##### Checkbox
519553

520554
A checkbox can be alone or can have texts. It is not a native iOS component. It is a square button which can be selected or not.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010

1111
- `alert` component tokens
12+
- Bullet List component (Orange-OpenSource/ouds-ios#513)
1213

1314
### Changed
1415

NOTICE.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ Any use or displaying shall constitute an infringement under intellectual proper
3333
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/Alert/ic_alert_warning_external_shape.imageset/ic_alert_warning_external_shape.svg
3434
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/Alert/ic_alert_warning_internal_shape.imageset/ic_alert_warning_internal_shape.svg
3535
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/Alert/ic_alert_warning.imageset/ic_alert_warning.svg
36+
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_level0.imageset/ic_bullet_list_level0.svg
37+
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_level0.imageset/ic_bullet_list_level0_rtl.svg
38+
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_level1.imageset/ic_bullet_list_level1.svg
39+
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_level0.imageset/ic_bullet_list_level1_rtl.svg
40+
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_level2.imageset/ic_bullet_list_level2.svg
41+
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_level0.imageset/ic_bullet_list_level2_rtl.svg
42+
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_tick.imageset/ic_bullet_list_tick.svg
3643
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/Checkbox/ic_checkbox_selected.imageset/ic_checkbox_selected.svg
3744
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/Checkbox/ic_checkbox_undeterminate.imageset/ic_checkbox_undeterminate.svg
3845
./OUDS/Core/Themes/Orange/Sources/Resources/Icons.xcassets/Components/Link/ic_link_next.imageset/ic_link_next.svg
@@ -57,6 +64,10 @@ Any use or displaying shall constitute an infringement under intellectual proper
5764
./OUDS/Core/Themes/Sosh/Sources/Resources/Icons.xcassets/Components/Alert/ic_alert_warning_external_shape.imageset/ic_alert_warning_external_shape.svg
5865
./OUDS/Core/Themes/Sosh/Sources/Resources/Icons.xcassets/Components/Alert/ic_alert_warning_internal_shape.imageset/ic_alert_warning_internal_shape.svg
5966
./OUDS/Core/Themes/Sosh/Sources/Resources/Icons.xcassets/Components/Alert/ic_alert_warning.imageset/ic_alert_warning.svg
67+
./OUDS/Core/Themes/Sosh/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_level0.imageset/ic_bullet_list_level0.svg
68+
./OUDS/Core/Themes/Sosh/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_level1.imageset/ic_bullet_list_level1.svg
69+
./OUDS/Core/Themes/Sosh/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_level2.imageset/ic_bullet_list_level2.svg
70+
./OUDS/Core/Themes/Sosh/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_tick.imageset/ic_bullet_list_tick.svg
6071
./OUDS/Core/Themes/Sosh/Sources/Resources/Icons.xcassets/Components/Checkbox/ic_checkbox_selected.imageset/ic_checkbox_selected.svg
6172
./OUDS/Core/Themes/Sosh/Sources/Resources/Icons.xcassets/Components/Checkbox/ic_checkbox_undeterminate.imageset/ic_checkbox_undeterminate.svg
6273
./OUDS/Core/Themes/Sosh/Sources/Resources/Icons.xcassets/Components/Chip/ic_chip_tick.imageset/ic_chip_tick.svg
@@ -85,6 +96,10 @@ Any use or displaying shall constitute an infringement under intellectual proper
8596
./OUDS/Core/Themes/Wireframe/Sources/Resources/Icons.xcassets/Components/Alert/ic_alert_warning_external_shape.imageset/ic_alert_warning_external_shape.svg
8697
./OUDS/Core/Themes/Wireframe/Sources/Resources/Icons.xcassets/Components/Alert/ic_alert_warning_internal_shape.imageset/ic_alert_warning_internal_shape.svg
8798
./OUDS/Core/Themes/Wireframe/Sources/Resources/Icons.xcassets/Components/Alert/ic_alert_warning.imageset/ic_alert_warning.svg
99+
./OUDS/Core/Themes/Wireframe/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_level0.imageset/ic_bullet_list_level0.svg
100+
./OUDS/Core/Themes/Wireframe/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_level1.imageset/ic_bullet_list_level1.svg
101+
./OUDS/Core/Themes/Wireframe/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_level2.imageset/ic_bullet_list_level2.svg
102+
./OUDS/Core/Themes/Wireframe/Sources/Resources/Icons.xcassets/Components/BulletList/ic_bullet_list_tick.imageset/ic_bullet_list_tick.svg
88103
./OUDS/Core/Themes/Wireframe/Sources/Resources/Icons.xcassets/Components/Checkbox/ic_checkbox_selected.imageset/ic_checkbox_selected.svg
89104
./OUDS/Core/Themes/Wireframe/Sources/Resources/Icons.xcassets/Components/Checkbox/ic_checkbox_undeterminate.imageset/ic_checkbox_undeterminate.svg
90105
./OUDS/Core/Themes/Wireframe/Sources/Resources/Icons.xcassets/Components/Chip/ic_chip_tick.imageset/ic_chip_tick.svg

OUDS/Core/Components/Sources/Actions/Button/OUDSButton.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ public struct OUDSButton: View {
143143
}
144144

145145
/// Represents the appearance of an OUDS button, i.e. a kind of type
146+
/// - Since: 0.10.0
146147
public enum Appearance {
147148
/// Default button is used for action
148149
case `default`
@@ -161,6 +162,7 @@ public struct OUDSButton: View {
161162
}
162163

163164
/// Defines the style of the button, e.g. loading or not
165+
/// - Since: 0.10.0
164166
public enum Style {
165167
/// The default style, the button could be in prossed, hover, disabled or enabled internal state
166168
case `default`
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
//
2+
// Software Name: OUDS iOS
3+
// SPDX-FileCopyrightText: Copyright (c) Orange SA
4+
// SPDX-License-Identifier: MIT
5+
//
6+
// This software is distributed under the MIT license,
7+
// the text of which is available at https://opensource.org/license/MIT/
8+
// or see the "LICENSE" file for more details.
9+
//
10+
// Authors: See CONTRIBUTORS.txt
11+
// Software description: A SwiftUI components library with code examples for Orange Unified Design System
12+
//
13+
14+
import OUDSFoundations
15+
import OUDSTokensSemantic
16+
import SwiftUI
17+
18+
// MARK: - Bullet
19+
20+
struct Bullet: View {
21+
22+
// MARK: Properties
23+
24+
let type: OUDSBulletList.`Type`
25+
let level: OUDSBulletList.NestedLevel
26+
let textStyle: OUDSBulletList.TextStyle
27+
let isBold: Bool
28+
let index: UInt8
29+
30+
@Environment(\.theme) private var theme
31+
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
32+
@Environment(\.verticalSizeClass) private var verticalSizeClass
33+
@Environment(\.dynamicTypeSize) private var dynamicTypeSize: DynamicTypeSize
34+
35+
// MARK: Body
36+
37+
var body: some View {
38+
HStack(alignment: .center) {
39+
switch type {
40+
case let .unordered(icon, isBranded):
41+
UnorderedBullet(icon: icon, isBranded: isBranded, level: level, textStyle: textStyle)
42+
case .ordered:
43+
OrderedBullet(level: level, textStyle: textStyle, isBold: isBold, index: index)
44+
case .bare:
45+
Rectangle().fill(.clear)
46+
}
47+
}
48+
.frame(minWidth: minWidth, alignment: .trailing)
49+
.frame(maxHeight: maxHeight, alignment: .center)
50+
}
51+
52+
// MARK: Private helpers
53+
54+
private var minWidth: CGFloat {
55+
let token = switch textStyle {
56+
case .bodyLarge:
57+
theme.sizes.iconWithBodyLargeSizeMedium
58+
case .bodyMedium:
59+
theme.sizes.iconWithBodyMediumSizeMedium
60+
}
61+
62+
let rawSize = token.dimension(for: horizontalSizeClass ?? .regular)
63+
64+
// Ordered type, means bullet is a text, so for dynamic font the
65+
// width of the container must be adjusted.
66+
if case .ordered = type {
67+
return rawSize * dynamicTypeSize.percentageRate / 100
68+
} else {
69+
return rawSize
70+
}
71+
}
72+
73+
private var maxHeight: CGFloat {
74+
let token = switch textStyle {
75+
case .bodyLarge:
76+
theme.fonts.lineHeightBodyLarge
77+
case .bodyMedium:
78+
theme.fonts.lineHeightBodyMedium
79+
}
80+
81+
let rawSize = token.lineHeight(for: verticalSizeClass ?? .regular)
82+
return rawSize * dynamicTypeSize.percentageRate / 100
83+
}
84+
}
85+
86+
// MARK: - Unordered Bullet
87+
88+
struct UnorderedBullet: View {
89+
90+
// MARK: Properties
91+
92+
let icon: OUDSBulletList.UnorderedIcon
93+
let isBranded: Bool
94+
let level: OUDSBulletList.NestedLevel
95+
let textStyle: OUDSBulletList.TextStyle
96+
97+
@Environment(\.theme) private var theme
98+
@Environment(\.verticalSizeClass) private var verticalSizeClass
99+
100+
// MARK: Body
101+
102+
var body: some View {
103+
asset
104+
.resizable()
105+
.renderingMode(.template)
106+
.oudsForegroundColor(color)
107+
.frame(width: assetSize, height: assetSize, alignment: .center)
108+
}
109+
110+
// MARK: Private helpers
111+
112+
private var asset: Image {
113+
switch icon {
114+
case .bullet:
115+
Image(decorative: bulletAssetName, bundle: theme.resourcesBundle)
116+
case .tick:
117+
Image(decorative: "ic_bullet_list_tick", bundle: theme.resourcesBundle)
118+
case let .free(image, _):
119+
image
120+
}
121+
}
122+
123+
private var bulletAssetName: String {
124+
switch level {
125+
case .zero:
126+
"ic_bullet_list_level0"
127+
case .one:
128+
"ic_bullet_list_level1"
129+
case .two:
130+
"ic_bullet_list_level2"
131+
}
132+
}
133+
134+
private var color: MultipleColorSemanticToken {
135+
isBranded ? theme.colors.contentBrandPrimary : theme.colors.contentDefault
136+
}
137+
138+
private var assetSize: CGFloat {
139+
switch textStyle {
140+
case .bodyLarge:
141+
theme.sizes.iconWithBodyLargeSizeSmall.dimension(for: verticalSizeClass ?? .regular)
142+
case .bodyMedium:
143+
theme.sizes.iconWithBodyMediumSizeSmall.dimension(for: verticalSizeClass ?? .regular)
144+
}
145+
}
146+
}
147+
148+
// MARK: - Ordered Bullet
149+
150+
struct OrderedBullet: View {
151+
152+
// MARK: Properties
153+
154+
let level: OUDSBulletList.NestedLevel
155+
let textStyle: OUDSBulletList.TextStyle
156+
let isBold: Bool
157+
let index: UInt8
158+
159+
// MARK: Body
160+
161+
var body: some View {
162+
Group {
163+
switch level {
164+
case .zero:
165+
Text(Self.levelZeroBullet(for: index))
166+
case .one:
167+
Text(Self.levelOneBullet(for: index))
168+
case .two:
169+
Text(Self.levelTwoBullet(for: index))
170+
}
171+
}
172+
.modifier(BulletTextModifier(textStyle: textStyle, isBold: isBold))
173+
}
174+
175+
// MARK: Helpers
176+
177+
static func levelZeroBullet(for index: UInt8) -> String {
178+
if OUDSUtils.isArabicLanguageInUse() {
179+
".\(index + 1)"
180+
} else {
181+
"\(index + 1)."
182+
}
183+
}
184+
185+
static func levelOneBullet(for index: UInt8) -> String {
186+
if OUDSUtils.isArabicLanguageInUse() {
187+
OUDSUtils.cyclicArabicLetter(at: index) + "." // NOTE: . won't be leading but trailing, arabic alphabet in use
188+
} else {
189+
OUDSUtils.cyclicLatinLetter(at: index, isUppercase: true) + "."
190+
}
191+
}
192+
193+
static func levelTwoBullet(for index: UInt8) -> String {
194+
if OUDSUtils.isArabicLanguageInUse() {
195+
"(" + OUDSUtils.cyclicArabicLetter(at: index) + ")" + "." // NOTE: . won't be leading but trailing, arabic alphabet in use
196+
} else {
197+
OUDSUtils.cyclicLatinLetter(at: index, isUppercase: false) + "."
198+
}
199+
}
200+
}

0 commit comments

Comments
 (0)