Skip to content

Commit e9b338f

Browse files
authored
Feature/db button (#7)
* added DBButton and DBLink * fixed preview titles * added DBCard * added behavior to DBCard * fixed linting * added DBAccordion * added DBSwitch * linting * refactoring * refactoring * refactoring * review comments
1 parent b84e943 commit e9b338f

20 files changed

Lines changed: 1491 additions & 104 deletions

File tree

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
//
2+
// Copyright 2024 by DB Systel GmbH
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
import SwiftUI
18+
import DBUXFoundation
19+
20+
enum DBAccordionVariant: CaseIterable {
21+
case divider
22+
case card
23+
24+
internal func previewName(def: DBAccordionVariant = .divider) -> String {
25+
var name = "\(def == self ? "(Def) " : "")"
26+
27+
switch self {
28+
case .divider:
29+
name.append("Divider")
30+
case .card:
31+
name.append("Card")
32+
}
33+
34+
return name
35+
}
36+
}
37+
38+
enum DBAccordionBehavior: CaseIterable {
39+
case multiple
40+
case single
41+
42+
internal func previewName(def: DBAccordionBehavior = .multiple) -> String {
43+
var name = "\(def == self ? "(Def) " : "")"
44+
45+
switch self {
46+
case .multiple:
47+
name.append("Multiple")
48+
case .single:
49+
name.append("Single")
50+
}
51+
52+
return name
53+
}
54+
}
55+
56+
struct DBAccordionItem: Equatable {
57+
var title: String
58+
var content: () -> any View
59+
let uuid = UUID()
60+
61+
static func ==(lhs: DBAccordionItem, rhs: DBAccordionItem) -> Bool {
62+
return lhs.uuid == rhs.uuid
63+
}
64+
}
65+
66+
struct DBAccordion: View {
67+
@Environment(\.theme) var theme
68+
69+
var items: [DBAccordionItem] = []
70+
var variant: DBAccordionVariant = .divider
71+
var behavior: DBAccordionBehavior = .multiple
72+
var disabled: Bool = false
73+
74+
@State var expandedStates: [UUID: Bool] = [:]
75+
76+
var body: some View {
77+
VStack(spacing: theme.dimensions.spacing.fixedSm) {
78+
ForEach(items, id: \.uuid) { item in
79+
DBAccordionSection(item: item, disabled: disabled, expanded: expandedState(for: item.uuid))
80+
.background(
81+
variant == .card
82+
?
83+
RoundedRectangle(cornerRadius: theme.dimensions.border.radiusSm)
84+
.stroke(theme.activeColor.onBgBasicEmphasis60Default, lineWidth: theme.dimensions.border.height3xs)
85+
.padding(0.5)
86+
: nil
87+
)
88+
.cornerRadius(theme.dimensions.border.radiusSm)
89+
90+
if variant == .divider && item != items.last {
91+
theme.activeColor.onBgBasicEmphasis60Default
92+
.frame(height: 1)
93+
.padding(.vertical, theme.dimensions.spacing.fixedSm)
94+
}
95+
}
96+
97+
}
98+
.opacity(disabled ? 0.4 : 1)
99+
.onChange(of: expandedStates) { oldValue, newValue in
100+
if behavior == .single {
101+
let newExpandedItems = newValue.filter { $0.value }.map { $0.key }
102+
let oldExpandedItems = oldValue.filter { $0.value }.map { $0.key }
103+
if newExpandedItems.count > oldExpandedItems.count {
104+
withAnimation(.snappy) {
105+
for uuid in newExpandedItems {
106+
expandedStates[uuid] = oldValue[uuid] ?? false ? false : true
107+
}
108+
}
109+
}
110+
}
111+
}
112+
}
113+
114+
func expandedState(for key: UUID) -> Binding<Bool> {
115+
return .init(
116+
get: {
117+
self.expandedStates[key, default: false]
118+
},
119+
set: {
120+
self.expandedStates[key] = $0
121+
})
122+
}
123+
}
124+
125+
struct DBAccordionSection: View {
126+
@Environment(\.theme) var theme
127+
128+
var item: DBAccordionItem
129+
var disabled: Bool = false
130+
@Binding var expanded: Bool
131+
@GestureState private var pressed: Bool = false
132+
133+
var body: some View {
134+
let pressGesture = DragGesture(minimumDistance: 0)
135+
.updating($pressed) { _, state, _ in
136+
if !disabled {
137+
state = true
138+
}
139+
}
140+
.onEnded { _ in
141+
if !disabled {
142+
withAnimation(.snappy) {
143+
expanded.toggle()
144+
}
145+
}
146+
}
147+
148+
VStack(spacing: 0) {
149+
ZStack {
150+
HStack {
151+
Text(item.title)
152+
.dsTextStyle(theme.fonts.bodyMd)
153+
Spacer()
154+
Image(expanded ? .chevronUp : .chevronDown)
155+
}
156+
.padding(theme.dimensions.spacing.fixedMd)
157+
}
158+
.contentShape(Rectangle())
159+
.foregroundColor(theme.activeColor.onBgBasicEmphasis100Default)
160+
.background(pressed ? theme.activeColor.basic.background.transparent.pressed : Color.clear)
161+
.cornerRadius(theme.dimensions.border.radiusSm)
162+
.gesture(pressGesture)
163+
164+
if expanded {
165+
AnyView(item.content())
166+
.padding(.horizontal, theme.dimensions.spacing.fixedMd)
167+
.padding(.top, theme.dimensions.spacing.fixedMd)
168+
.padding(.bottom, theme.dimensions.spacing.fixedLg)
169+
}
170+
}
171+
}
172+
}
173+
174+
#Preview(traits: .sizeThatFitsLayout) {
175+
var itemsVariants = [
176+
DBAccordionItem(title: "Accordion Item", content: { Text("Content") }),
177+
DBAccordionItem(title: "Accordion Item", content: { Text("Content") }),
178+
DBAccordionItem(title: "Accordion Item", content: { Text("Content") })
179+
]
180+
var itemsProperties = [
181+
DBAccordionItem(title: "Headline", content: { Text("Content") }),
182+
DBAccordionItem(title: "Headline", content: { Text("Content") }),
183+
DBAccordionItem(title: "Headline", content: { Text("Content") })
184+
]
185+
PreviewTemplate(
186+
title: "DBAccordion",
187+
previewVariants: DBAccordionVariant.allCases.map({ accordionVariant in
188+
[
189+
AnyView(DBAccordion(items: itemsVariants, variant: accordionVariant))
190+
]
191+
}),
192+
previewProperties: [
193+
PreviewPropertiesSection(
194+
name: "Variant",
195+
content: DBAccordionVariant.allCases.map({ accordionVariant in
196+
PreviewPropertiesElement(
197+
description: accordionVariant.previewName(),
198+
content: { DBAccordion(items: itemsProperties, variant: accordionVariant) }
199+
)
200+
})
201+
),
202+
PreviewPropertiesSection(
203+
name: "Disabled",
204+
content: [false, true].map({ accordionDisabled in
205+
PreviewPropertiesElement(
206+
description: "\(accordionDisabled ? "(Def) " : "")\(accordionDisabled.description.capitalized)",
207+
content: { DBAccordion(items: itemsProperties, disabled: accordionDisabled) }
208+
)
209+
})
210+
),
211+
PreviewPropertiesSection(
212+
name: "Behavior",
213+
content: DBAccordionBehavior.allCases.map({ accordionBehavior in
214+
PreviewPropertiesElement(
215+
description: accordionBehavior.previewName(),
216+
content: { DBAccordion(items: itemsProperties, behavior: accordionBehavior) }
217+
)
218+
})
219+
),
220+
],
221+
)
222+
}

Sources/DBUXComponents/DBBadge.swift

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -111,49 +111,49 @@ struct DBBadge: View {
111111

112112
#Preview(traits: .sizeThatFitsLayout) {
113113
PreviewTemplate(
114-
title: "DB Badge",
115-
previewVariants: DBSemantic.allCases.map({ semantic in
116-
DBEmphasis.allCases.map({ emphasis in
117-
AnyView(DBBadge(content: .text("Badge"), emphasis: emphasis, semantic: semantic))
114+
title: "DBBadge",
115+
previewVariants: DBSemantic.allCases.map({ badgeSemantic in
116+
DBEmphasis.allCases.map({ badgeEmphasis in
117+
AnyView(DBBadge(content: .text("Badge"), emphasis: badgeEmphasis, semantic: badgeSemantic))
118118
})
119119
}),
120120
previewProperties: [
121121
PreviewPropertiesSection(
122122
name: "Size",
123-
content: DBSize.allCases.reversed().map({ size in
123+
content: DBSize.allCases.reversed().map({ badgeSize in
124124
PreviewPropertiesElement(
125-
description: size.previewName(def: .small),
126-
content: { DBBadge(size: size, content: .text("Text")) }
125+
description: badgeSize.previewName(def: .small),
126+
content: { DBBadge(size: badgeSize, content: .text("Text")) }
127127
)
128128
})
129129
),
130130
PreviewPropertiesSection(
131131
name: "Content",
132-
content: DBBadge.DBBadgeVariant.previewCases.map({ variant in
132+
content: DBBadge.DBBadgeVariant.previewCases.map({ badgeVariant in
133133
PreviewPropertiesElement(
134-
description: variant.previewName,
135-
content: { DBBadge(content: variant) }
134+
description: badgeVariant.previewName,
135+
content: { DBBadge(content: badgeVariant) }
136136
)
137137
})
138138
),
139139
PreviewPropertiesSection(
140140
name: "Emphasis",
141-
content: DBEmphasis.allCases.map({ emphasis in
141+
content: DBEmphasis.allCases.map({ badgeEmphasis in
142142
PreviewPropertiesElement(
143-
description: emphasis.previewName(),
144-
content: { DBBadge(content: .text("Text"), emphasis: emphasis) }
143+
description: badgeEmphasis.previewName(),
144+
content: { DBBadge(content: .text("Text"), emphasis: badgeEmphasis) }
145145
)
146146
})
147147
),
148148
PreviewPropertiesSection(
149149
name: "Semantic",
150-
content: DBSemantic.allCases.map({ semantic in
150+
content: DBSemantic.allCases.map({ badgeSemantic in
151151
PreviewPropertiesElement(
152-
description: semantic.previewName(),
152+
description: badgeSemantic.previewName(),
153153
content: {
154154
HStack {
155-
DBBadge(content: .text("Text"), emphasis: .weak, semantic: semantic)
156-
DBBadge(content: .text("Text"), emphasis: .strong, semantic: semantic)
155+
DBBadge(content: .text("Text"), emphasis: .weak, semantic: badgeSemantic)
156+
DBBadge(content: .text("Text"), emphasis: .strong, semantic: badgeSemantic)
157157
}
158158
}
159159
)

0 commit comments

Comments
 (0)