From 06a8fa5e062808f051345c60cf493fabba1bdb02 Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:24:15 +0300 Subject: [PATCH 01/19] fix(menu-item): render the toggle accessory with the Dash switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `MenuItemAccessory.toggle` drew a system `Toggle`. The library ships its own `SwitchView` — blue track, its own thumb and sizing, its own accessibility value — and every screen that wanted the design system's switch on a menu row had to skip the accessory and compose the two by hand, which is the opposite of what the accessory is for. It now renders `SwitchView`, so a row asking for a toggle gets the same switch as everywhere else. `SwitchView` reads `isEnabled` from the environment, which `.disabled` already sets, so the disabled state carries over unchanged. Both types are `iOS 14, macOS 11`, so the availability of `MenuItem` is untouched. --- Sources/DashUIKit/Components/MenuItem.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/DashUIKit/Components/MenuItem.swift b/Sources/DashUIKit/Components/MenuItem.swift index 10e9b26..969026e 100644 --- a/Sources/DashUIKit/Components/MenuItem.swift +++ b/Sources/DashUIKit/Components/MenuItem.swift @@ -110,8 +110,11 @@ public struct MenuItem: View { case .none: EmptyView() case .toggle(let isOn): - Toggle("", isOn: isOn) - .labelsHidden() + // `SwitchView`, not `Toggle`: the system switch is green and sized + // by UIKit, so a menu row rendered here did not match the switch + // the same design system hands out everywhere else. It reads + // `isEnabled` from the environment, which `.disabled` sets. + SwitchView(isOn: isOn) .disabled(!isEnabled) case .text(let value): Text(value) From 22172824f136d59d03c6675c8776be7e0f67b47a Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:01:52 +0300 Subject: [PATCH 02/19] feat(icons): add InfoRoundIcon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "there is more to say about this" affordance, drawn rather than shipped as an asset for the same reasons `XmarkIcon` is: it stays crisp at any size, and the disc reads the `Blue` token instead of baking `#008DE4` into a PDF that would then sit out the next palette change. Geometry is normalized from the 19-unit source viewBox, so the stem and the dot keep their positions and weight at any size. The dot is a 0.01-long segment with a round cap rather than a circle — the source draws it that way, and it is what guarantees the dot matches the stem's weight without a second constant to keep in sync. --- .../Components/Icons/InfoRoundIcon.swift | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 Sources/DashUIKit/Components/Icons/InfoRoundIcon.swift diff --git a/Sources/DashUIKit/Components/Icons/InfoRoundIcon.swift b/Sources/DashUIKit/Components/Icons/InfoRoundIcon.swift new file mode 100644 index 0000000..d3ac845 --- /dev/null +++ b/Sources/DashUIKit/Components/Icons/InfoRoundIcon.swift @@ -0,0 +1,116 @@ +// +// Copyright © 2026 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +// MARK: - InfoRoundIcon + +/// A filled disc carrying a white "i" — the affordance for "there is more to +/// say about this". +/// +/// Drawn rather than shipped as an asset, for the same reason `XmarkIcon` is: +/// it stays crisp at any size, and the disc takes the `Blue` token instead of +/// baking `#008DE4` into a PDF that would then miss a palette change. +@available(iOS 14, macOS 11, *) +public struct InfoRoundIcon: View { + public var size: CGFloat = 19 + public var color: Color = Color.dash.blue + public var glyphColor: Color = Color.white + + /// A public struct gets no public memberwise initializer, so this one is + /// written out to keep the call site available to app code. + public init( + size: CGFloat = 19, + color: Color = Color.dash.blue, + glyphColor: Color = Color.white + ) { + self.size = size + self.color = color + self.glyphColor = glyphColor + } + + public var body: some View { + ZStack { + Circle() + .fill(color) + InfoGlyphShape() + .stroke( + glyphColor, + style: StrokeStyle( + lineWidth: size * Self.strokeRatio, + lineCap: .round, + lineJoin: .round)) + } + .frame(width: size, height: size) + } + + /// 1.6 of the 19-unit source viewBox. + private static let strokeRatio: CGFloat = 1.6 / 19 +} + +// MARK: - InfoGlyphShape + +/// The "i": a stem below the middle and a dot above it. Both are normalized +/// from the 19-unit source viewBox so the glyph keeps its proportions at any +/// size. +/// +/// The dot is a 0.01-long segment rather than a circle — with a round cap that +/// renders as a dot of exactly the stem's weight, which is how the source +/// draws it and what keeps the two visually matched at every size. +@available(iOS 14, macOS 11, *) +private struct InfoGlyphShape: Shape { + private let centerXRatio: CGFloat = 9.2998 / 19 + private let stemTopRatio: CGFloat = 9.30005 / 19 + private let stemBottomRatio: CGFloat = 12.6056 / 19 + private let dotTopRatio: CGFloat = 5.99451 / 19 + private let dotBottomRatio: CGFloat = 6.00451 / 19 + + func path(in rect: CGRect) -> Path { + let x = rect.minX + rect.width * centerXRatio + + var path = Path() + path.move(to: CGPoint(x: x, y: rect.minY + rect.height * stemTopRatio)) + path.addLine(to: CGPoint(x: x, y: rect.minY + rect.height * stemBottomRatio)) + path.move(to: CGPoint(x: x, y: rect.minY + rect.height * dotTopRatio)) + path.addLine(to: CGPoint(x: x, y: rect.minY + rect.height * dotBottomRatio)) + return path + } +} + +#if DEBUG + +@available(iOS 17, macOS 14, *) +#Preview("Sizes") { + HStack(alignment: .center, spacing: 12) { + InfoRoundIcon(size: 14) + InfoRoundIcon() + InfoRoundIcon(size: 28) + InfoRoundIcon(size: 44) + } + .padding() +} + +@available(iOS 17, macOS 14, *) +#Preview("Recoloured") { + HStack(spacing: 12) { + InfoRoundIcon(size: 32, color: Color.dash.gray300) + InfoRoundIcon(size: 32, color: Color.dash.orange) + } + .padding() + .background(Color.dash.primaryBackground) +} + +#endif From 4f19f994ce62399a3fea291df04a27c0b819c45a Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:45:34 +0300 Subject: [PATCH 03/19] feat(menu-item): give the info glyph a type, and draw the design system's mark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `infoIcon: DashIconSource?` could only ever be an `Image`, so the round info mark the design system now owns could not be shown there at all — a caller wanting it had to fall back to `.system("info.circle.fill")`, which is the system glyph in the system's colour. It becomes `info: MenuItemInfo?`: - `.round(color:)` renders `InfoRoundIcon`, recoloured to suit the row — muted beside a settings title, or the Blue token where it should carry weight; - `.icon(DashIconSource)` keeps the old behaviour for anything else. A source break, deliberately: the parameter had one caller outside this package's previews, and leaving both spellings would have meant two ways to say the same thing. Docs follow the change — `MenuItemInfo` in the row reference, `InfoRoundIcon` in the icon catalog and the index, and the toggle accessory's entry corrected to say `SwitchView` rather than `Toggle`. --- CLAUDE.md | 2 +- .../Components/Icons/InfoRoundIcon.swift | 4 +-- Sources/DashUIKit/Components/MenuItem.swift | 32 ++++++++++++++----- docs/README.md | 5 +-- docs/feedback.md | 19 +++++++++++ docs/lists-and-rows.md | 12 +++++-- 6 files changed, 58 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c04fc98..d2cbddc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,7 +37,7 @@ Sources/DashUIKit/ Components/ Most components (one type per file) EnterAmount/ Amount-entry suite (EnterAmountView, SwapAmountView, …) Geometry/ Layout readers + scale-to-fit helpers - Icons/ Code-drawn icons (XmarkIcon) + Icons/ Code-drawn icons (XmarkIcon, InfoRoundIcon) Illustrations/ Loading / success / error illustrations Table List/ List1View (label/value row) ViewModifiers/ MenuViewModifier (card chrome) diff --git a/Sources/DashUIKit/Components/Icons/InfoRoundIcon.swift b/Sources/DashUIKit/Components/Icons/InfoRoundIcon.swift index d3ac845..e6c4fa5 100644 --- a/Sources/DashUIKit/Components/Icons/InfoRoundIcon.swift +++ b/Sources/DashUIKit/Components/Icons/InfoRoundIcon.swift @@ -28,14 +28,14 @@ import SwiftUI public struct InfoRoundIcon: View { public var size: CGFloat = 19 public var color: Color = Color.dash.blue - public var glyphColor: Color = Color.white + public var glyphColor: Color = Color.dash.white /// A public struct gets no public memberwise initializer, so this one is /// written out to keep the call site available to app code. public init( size: CGFloat = 19, color: Color = Color.dash.blue, - glyphColor: Color = Color.white + glyphColor: Color = Color.dash.white ) { self.size = size self.color = color diff --git a/Sources/DashUIKit/Components/MenuItem.swift b/Sources/DashUIKit/Components/MenuItem.swift index 969026e..e079b4a 100644 --- a/Sources/DashUIKit/Components/MenuItem.swift +++ b/Sources/DashUIKit/Components/MenuItem.swift @@ -32,6 +32,16 @@ public enum MenuItemAccessory { case balance(dash: Int64, sign: DashAmountSign = .negativeOnly, fiat: String? = nil) } +/// The glyph beside a row's title that says there is more to explain. +/// +/// `.round` is the design system's own info mark; `.icon` stays open for a row +/// that needs to flag something else entirely. +@available(iOS 14, macOS 11, *) +public enum MenuItemInfo { + case round(color: Color) + case icon(DashIconSource) +} + @available(iOS 14, macOS 11, *) public struct MenuItem: View { @@ -40,7 +50,7 @@ public struct MenuItem: View { public var disabledLeadingIcon: DashIconSource? public var title: String public var helpText: String? - public var infoIcon: DashIconSource? + public var info: MenuItemInfo? public var accessory: MenuItemAccessory public init( @@ -49,7 +59,7 @@ public struct MenuItem: View { disabledLeadingIcon: DashIconSource? = nil, title: String, helpText: String? = nil, - infoIcon: DashIconSource? = nil, + info: MenuItemInfo? = nil, accessory: MenuItemAccessory = .none ) { self.leadingIcon = leadingIcon @@ -57,7 +67,7 @@ public struct MenuItem: View { self.disabledLeadingIcon = disabledLeadingIcon self.title = title self.helpText = helpText - self.infoIcon = infoIcon + self.info = info self.accessory = accessory } @@ -89,9 +99,15 @@ public struct MenuItem: View { .dashFont(.subheadMedium) .foregroundColor(isEnabled ? Color.dash.primaryText : Color.dash.secondaryText) - if let icon = infoIcon { - Image(dash: icon) - .frame(width: 20, height: 20, alignment: .center) + if let info { + switch info { + case .round(let color): + InfoRoundIcon(size: 19, color: color) + .frame(width: 20, height: 20, alignment: .center) + case .icon(let icon): + Image(dash: icon) + .frame(width: 20, height: 20, alignment: .center) + } } } @@ -155,11 +171,11 @@ public struct MenuItem: View { } @available(iOS 17, macOS 14, *) -#Preview("Title + infoIcon + helpText") { +#Preview("Title + info + helpText") { MenuItem( title: "Network fee", helpText: "Estimated cost for this transaction", - infoIcon: .system("info.circle"), + info: .round(color: Color.dash.gray300Alpha70), accessory: .text("0.0001 DASH") ) .padding(.horizontal) diff --git a/docs/README.md b/docs/README.md index a41ec75..083bd0c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,7 +23,7 @@ and callbacks; they render and report intent. They require `import DashUIKit` an `NavigationBarElement`, `TopIntroView`, `BottomSheet`, `MenuViewModifier`. - **[Feedback](feedback.md)** — `Toast`, `SystemMessageView`, `LoadingIllustration` / `LoadingSpinner`, `SuccessIllustration`, `ErrorIllustration`, - `XmarkIcon`. + `XmarkIcon`, `InfoRoundIcon`. - **[Utilities](utilities.md)** — geometry readers (`readingFrame`, `readingLocation`), `ScrollViewWithOnScrollChanged`, `scaleToFitWidth`. @@ -52,7 +52,7 @@ and callbacks; they render and report intent. They require `import DashUIKit` an | `ErrorIllustration` | [Feedback](feedback.md#successillustration--errorillustration) | | `List1View` | [Lists & rows](lists-and-rows.md#list1view) | | `LoadingIllustration` / `LoadingSpinner` | [Feedback](feedback.md#loadingillustration--loadingspinner) | -| `MenuItem` (`MenuItemAccessory`) | [Lists & rows](lists-and-rows.md#menuitem) | +| `MenuItem` (`MenuItemAccessory`, `MenuItemInfo`) | [Lists & rows](lists-and-rows.md#menuitem) | | `MenuViewModifier` | [Navigation & containers](navigation-and-containers.md#menuviewmodifier) | | `NavigationBar` / `NavigationBarElement` | [Navigation & containers](navigation-and-containers.md#navigationbar) | | `NumericKeyboardView` | [Buttons & inputs](buttons-and-inputs.md#numerickeyboardview) | @@ -69,3 +69,4 @@ and callbacks; they render and report intent. They require `import DashUIKit` an | `TopIntroView` | [Navigation & containers](navigation-and-containers.md#topintroview) | | `TransactionView` | [Lists & rows](lists-and-rows.md#transactionview) | | `XmarkIcon` | [Feedback](feedback.md#xmarkicon) | +| `InfoRoundIcon` | [Feedback](feedback.md#inforoundicon) | diff --git a/docs/feedback.md b/docs/feedback.md index cd08d4a..baeb4ea 100644 --- a/docs/feedback.md +++ b/docs/feedback.md @@ -96,6 +96,25 @@ ErrorIllustration() --- +## InfoRoundIcon + +File `Components/Icons/InfoRoundIcon.swift` · `@available(iOS 14, macOS 11, *)` · public + +A code-drawn round "i" — a filled disc with a white glyph — for the "there is more to say +about this" affordance. Geometry is normalized from a 19×19 SVG, so stem and dot keep +their weight and position at any `size`. + +```swift +InfoRoundIcon() // 19pt, Blue disc +InfoRoundIcon(size: 20, color: Color.dash.gray300Alpha70) // muted, beside a menu title +``` + +The disc reads `Color.dash.blue` by default and the glyph `Color.dash.white`, so both +follow the palette instead of a baked-in hex. `MenuItem` renders it for +`MenuItemInfo.round`. + +--- + ## XmarkIcon File `Components/Icons/XmarkIcon.swift` · `@available(iOS 14, macOS 11, *)` · public diff --git a/docs/lists-and-rows.md b/docs/lists-and-rows.md index 4574a39..1f8c6bd 100644 --- a/docs/lists-and-rows.md +++ b/docs/lists-and-rows.md @@ -38,7 +38,7 @@ is tappable when you wrap it in a `Button`. File `Components/MenuItem.swift` · `@available(iOS 14, macOS 11, *)` -A settings/menu row: optional leading icon, title with optional inline info icon, optional +A settings/menu row: optional leading icon, title with optional inline info glyph, optional help text, and a flexible trailing **accessory**. ```swift @@ -46,16 +46,22 @@ MenuItem( leadingIcon: .custom("menu-send", bundle: .dashUIKit), // DashIconSource? title: "Network fee", helpText: "Estimated cost for this transaction", - infoIcon: .system("info.circle"), + info: .round(color: Color.dash.gray300Alpha70), accessory: .text("0.0001 DASH") ) ``` +**`MenuItemInfo`** — the glyph beside the title, when the row has something to explain: + +- `.round(color: Color)` — the design system's `InfoRoundIcon`, recoloured to suit the row +- `.icon(DashIconSource)` — any other glyph + **`MenuItemAccessory`** — the trailing look (extend this enum rather than overriding per-call fonts/colors, to keep rows consistent): - `.none` -- `.toggle(isOn: Binding)` — a `Toggle` +- `.toggle(isOn: Binding)` — a `SwitchView` (the design system's switch, not the + system green `Toggle`); it takes the row's enabled state from the environment - `.text(String)` - `.button(DashButton)` — embed a `DashButton` - `.balance(dash: Int64, sign: DashAmountSign = .negativeOnly, fiat: String? = nil)` — From 37d03f875e141a1a319f48fc1aa784615b0f8a3d Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:05:27 +0300 Subject: [PATCH 04/19] feat(icons): add the Features icon set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three icons from the Figma export — identity, platform and shield — assembled with the repo's own `normalize-icons.py` / `build-imagesets.py` pair into a new `Features` group. Light only: the export ships no dark variants for these. Each imageset carries `template-rendering-intent: template`, which the builder does not write. Without it the PNG renders exactly as exported and ignores the tint the caller sets, and these are meant to be recoloured at the call site. --- .../Features/Contents.json | 6 ++++ .../feature-identity.imageset/Contents.json | 26 ++++++++++++++++++ .../feature-identity.png | Bin 0 -> 627 bytes .../feature-identity@2x.png | Bin 0 -> 1156 bytes .../feature-identity@3x.png | Bin 0 -> 1713 bytes .../feature-platform.imageset/Contents.json | 26 ++++++++++++++++++ .../feature-platform.png | Bin 0 -> 683 bytes .../feature-platform@2x.png | Bin 0 -> 1161 bytes .../feature-platform@3x.png | Bin 0 -> 1638 bytes .../feature-shield.imageset/Contents.json | 26 ++++++++++++++++++ .../feature-shield.png | Bin 0 -> 697 bytes .../feature-shield@2x.png | Bin 0 -> 1168 bytes .../feature-shield@3x.png | Bin 0 -> 1676 bytes 13 files changed, 84 insertions(+) create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/feature-identity.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/feature-identity@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/feature-identity@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/feature-platform.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/feature-platform@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/feature-platform@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/feature-shield.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/feature-shield@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/feature-shield@3x.png diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/Contents.json new file mode 100644 index 0000000..62166d1 --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "feature-identity.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "feature-identity@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "feature-identity@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/feature-identity.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/feature-identity.png new file mode 100644 index 0000000000000000000000000000000000000000..2d1f21a7852d88ad135edfeeb71fdc9c4be29a15 GIT binary patch literal 627 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 Hu0mjfWv?UU literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/Contents.json new file mode 100644 index 0000000..7227d9b --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "feature-platform.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "feature-platform@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "feature-platform@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/feature-platform.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/feature-platform.png new file mode 100644 index 0000000000000000000000000000000000000000..28d7946e5ee53397e05679898e34e5ed152c710d GIT binary patch literal 683 zcmV;c0#yBpP)OV|0ag-DblYqgGno{V7GK~>T6g5l3D(AfbO z9^G~CU1zZ&Fx|u8O@b1X6a0GY(szhFvD?J-3GjJs1Woh=qJf#pWH2JW$g6+4@@|L(4 zvGXYwOlN(Rp!d>7Z`DS3C1)4$)IkBog3MAqsaH6Zz5c6qcI|Jjq6#e-Vu$velycTP zl)3A$axZReJViX2BRZNQIGjKSwL_x7JCfVlcydc_fh_exUKe8-bPxUl2NyCf9S;BU z1n>Vi!SL{ZmccR@%iw}|l9JE_$tln7<#ky(iE+UN(eV_?8P9~PV64Q;{GzOl|5R_5 zY7_LLfBFA`iquwONpdFg7YJ_PU2NGA>cVc1iMLvW9Kqbl+RrXnb0UY*4bwb4Gj&C$T<{ZuW-sy RR+j((002ovPDHLkV1h>6FE0Q9 literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/feature-platform@2x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/feature-platform@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4a56d8b111a875b1069f916196b9e3add6368b6a GIT binary patch literal 1161 zcmV;41a|w0P)`|5JCtcgb+dqAw+H{WKH*Z1yNYnS5`xeO7fb*P(L5S^E+t%IzonI zC&2ELGNQRI5U(8bC;M3n(tUsw9OmkX;)edNJmpUX@Kg`Y-#T#2ZGhLG)u23Gb$hKk zyODl$@Zb499B~t1ccY3}we-4H1qX!zlz-JlTiXpVY1d*45gIVV`ub=mz;x?eS|j~f z)otB_1?B^!c8%0cq+jRK8o|Q)Xf8mS=i=}t+d7w?(YEFf(EK`=j;Got#va}PcRxkk zh5dG&)<{?5WzK5!z{O&C^^2#`t)4|?-p7n~uQBNo9tCiTq82QKx4ck9PlrdSeHw$; zn2`=~jyAP_z<{R$srrup49lWi`gF-DDsWF+(!q0T=j!t-o$#^g2wwnwGf$TbItDt*dHQ)ZUf(Q>guU>fp7}R)`C?UmzIAFeP&>yu5IN z(d3ZybK>+eSAoqzfTvrh^^8~CtE$;9F&IZlD3`SFZqJ*vu; zzkb3M{W$t(cCh;JE!G~rPoCS9$xUPuRnTB_oIEI8S|cY>U>9z`G+t?6ldCzf{a_2h z`{=XRI``i=3yQjKUVYsY5lDhzD9o%PQP(DuX0;KU| zAAQ^?lg)55U|@PlIdFa2=l zUyV5zCr3X{jHv>>A@XM(40rFiy;J%(HffWo8xK+;K!^^meJ_X1Cz)>R zI9`Rn)8=01zQxgvHp~amX`Ku8CSkZ;kA7>HPP#r?TIarf30bf^_S7-$O*qLqr`y%Q zSTd^&-R?T|`qy_>Fs+n3E7t|>?lOS-jpPrg#CM)4i^gNRRz_kx%E)Dx12Cv zpdpuD1D^m;#M$PY62rX`^x6yb69A{4nz)DDQe|6cQ;{AJO40=IZs)(o5ihnWW_NaG zcz%+FZG6Cf_|My}X8|H2A|fIpA|fIpA|fIpA|fIpA|e7jxUTwq2*?-JTP+}r3XLbd zUHDNAvm+aDoVx*6Ulf4wz6y0lJsN8BszBW~;OFKs`$+%?xfgI;vAxqO*a|LY#l@iA z3b^`b`J9S9PoQo~crd&ON4ODib*qHXTUPf9NJgM;^D5{SSkCT%tJ_6HsH$5D>9D@p zwlmYqnp;L9&Bno1q9@)j}s={z!&UBI#m6=D_HPkYjq zX|wGk3y)r?!+2PS`{4@J{nVet?>PhdEBL!VCgAz2Fx157*HFBb$I8<;2=?koR2Yq8 zPVWMV-hku5RNdIho%#AYs-?mB{_eXFqu>9nenv=;>X#2jb$y#K`qNQI`)gcYn*OYK`s z3BFzUHn`FOOXaS9^RZgc=1&eRjv6h2> zKY&S`4R}%wjw-6+(k7udTLSr+!U2j)akdYZs?|-xF5c{gNt_LM?b%!C7VI6eiPl9v zXlX9xke`f}Wg(+cVaZgs^tp0EfDE>NSHa5twP*jrG)@n)J3<`IE?P$s;bLQ@p`v3 z)ISey?DRH;t5nIJdarf$p6B#m1|Y-DNaL;g*VTx!0GG*>$jjZ!YBSR8w-bK2Re|qK z!?o>%8&x}xxooeZX3mD-^?F79HJR+lnt~Ph2eT$z$HA~I{Vi{{J#46f)O;3wZ98UO zlw)CgQvGFRYl4>PPsEFIT#Rhkd6=J3{c{i6riE?QY-76Xpqta4xseJS;a0$++h*J1ezp_$ z0&Z#s?PIKMMZaID$gkMk&vxQ&z{klP+V_e;baceZ3sl7hs$y%dRV)z^5fKp)5fKp) k5fKp)5fKp)5fQcWA6`rwQ6u{BbN~PV07*qoM6N<$g7~8&RR910 literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/Contents.json new file mode 100644 index 0000000..21f2e96 --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "feature-shield.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "feature-shield@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "feature-shield@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/feature-shield.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/feature-shield.png new file mode 100644 index 0000000000000000000000000000000000000000..b843d6a662284135e2c796b64b88adb5de860fde GIT binary patch literal 697 zcmV;q0!ICbP)F7TXZz|2AB)LHk62!2tqOxMna&l_D znt9`W1C*4Mlq?kwwzR(1fH0=6BIGKXb~}Wx5@3{|xjBRd#ROR@U)iVDJ5bod;t~23 z)5T%Eg94Zd>e;$-Kr_{#Up1JK!uL98uD>v8Bo}NQ$H?)HzD2Mj#Ta{dR!=0sz9a~( zACzBs4+FXQ`pUdV5d!Kf}U@bZ?(## z+Xb^IMkZ*;&aH~R#%uP1Asi7^LlO${fR=av&Q*Qy=zbM9>3;X~Ukp!jBtJ8l;zW>_ zl5=hrEPEz=_szgqQYNVKA?4xR$2{ov+h3@y`q;f)!K0}V)BgBJKTihPdAVGra3&bg zt1Dj|(my`Y*WQIn|ut z1nv{yJ^?aS&uPw8ad?85C#ZXaQ~U(r9N<=EqZlC(q&gLX26uOu-#U)n^pEQGZ)Rt! z-$&Yb?W)Q6oB8dGH#2|`LI@#*5JCtcgb;=g zTF4&S%a}0HLlEwu{eA>T+yqF$X|-+2uK^bn`sQe(T^+zOy8(9os6oej&VqYh*LIH# zU`yxsZ6xt8Ifh4CrVWQ_TP)%4f%*5k7gJ{RR^biq-pJnrlX%~|@Q60kBOTwL%mDcC z26%7SGGA-3sP*yN>5&PAn3P&zv;s~_o8Pj%ajE&y8umWlNYA6w82#ZHGS~tm<`Pz{ zjk~N?4rk}r|9l%8jv=P7*(M!W%P6~VHlM_!$-U|_?2OP{rI zb|{|%g#Q8RBmyJe00UTGi?XyZh`@;dZ;6yD2IDS#6@)!7;}5WXZDgZVmPT!|uujmR?21}iMF~%+VUdv!wPh1KV8_E2aBaUW%qty zIwFTR&UYEPML*MG>CS$P3zLQC)DvrPVH%!;4L9y~D!B^LVR*#dEq!`J^u}yXveO+( z<4@katzMViJ-&lLeFLt$Hp<+GjIo2;)m=E2-*#6j={UY)TJSZN({;VUa9;lVg{MIV zxc=JLN9u8j{sH02ZeGF7n&OkY#|_h>rYUdQE>S<i0000nd0P literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/feature-shield@3x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/feature-shield@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9b394ce6ab763bbd5ea6b7ba91874d0ae4941bc3 GIT binary patch literal 1676 zcmV;726Op|P)z0Pk=4jYy;a$)bIqDZ8w2kcbFGwoJF&0)QNCfsi{;GwJ8KV({skq8V4g2Jag{7 zbAFN)+XUp9zwgJ~J2MwhN-3q3Qc5YMlu}A5rIb=iDW#NBN-3q3Qh*0nm4C|v%~5(b z9ls8v{Dq(UUO;a)Xcy39AVBGZ01k2^;8vIggqWf&Dj)*A#_09_2paUeHTvBe{BR!_ ztho@ny#bdGC{S;XwqX7&&?E%(sKSr7QJSp6LUsjw9xAYi>zB}*3J`~lO_U~Tu!Q*m zmwy>WI5@0_MY7h3YRU?Htk`( zz6fEmCAeHK)8IrAF7v%rls;U85V?TM&mT}nY8BbALG5x00%QVib*A=^C1Es+Z_*zP zlWBuPe*uPB{`_4$oO>UOpT2`c_@2l%4?uz!O;e}J7#!+G;l23z{3o{%95zv{AEVC- z5Fk^6qq8YILrjkiUU*%Y96|!}5RiVr@ko_frp7M@{PFFCDmH;_7bw9a#sxe&d~rA2 z8BVJ2Ig*8d&C^*$PmL#1MT^LMz@6c;{Ouf-YBx@H(SXMXFMof`jZ&QXfM+I#FMWUh z-UwWf>43Q^?l!QoK=1dznm%eEW$6cO$eD^9?%tPUU4PC!YyLU^zrHEmO+8?lkt`ux zA`6qlakab{m(Z)f-`oLv^wl_aem=tP(|=GsI7Z{xpdq*($aEFg>xu8B0)@yOs3{7G z|84b*Z(dDD1LB><5JCMaOAI;8sspSerO~y`JbB1j%4wn4u^M@+3)|b zI$W0Np~~|TeVNKnuj2OAcA1}S;I{QL+Z3=1G9PfY%OO?m#cQ}A^8s^P+=+)HZ5wm$ zMo-y6tGC?lve0d%0TUjyXvgP8yFvpdhg2oEd)85+ZKeU^%__NO@YwQoWop8|8y?x> z5v`fYq4=oBP*IzaYhDB#Ew+1MC$OX(-+IgtLb-ay&S5(T*CPmnV>mqDJkZi!M zYTDglOFqHhKM`wxnQXxE1<}zaTorNkd{^`fEI;AzpKid_Qp`66m;MNl=4vSpBfaDM ze_SiY-1ZI(RA2xz0_IX2g-dWn1S3i>GwXJ4>(3w3-kFEpDm3nF%$L=yvnu`9OL6sP ztN5@;zGKzSyc;rpTc1V_e7k?UW}5hOg zG;@{AJ!L~XQ{C9PFacND&C?=0Zy8DBV4E}3 z&$+R*2nQWD3w^1&J=7@JUE6~_=SINo;-OBOu<*Q@o)(>MUAN1j@@c$Pi4{VV Date: Tue, 18 Aug 2026 13:59:47 +0300 Subject: [PATCH 05/19] feat(bottom-sheet): group the sheet with SheetFeature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `BottomSheet.swift` moves into a folder of its own and is joined by `SheetFeature` — the icon/name/description line a sheet stacks to say what something gives you. It arrived in dashwallet-ios, where a component describing sheet content had no reason to live. The icon is a `ViewBuilder` slot rather than a `DashIconSource`: the leading mark in this position is not always an image — a badge or a coloured container belongs there too — and a convenience initializer still covers the common case of a template asset with a tint. The title takes `.subheadMedium` instead of `.subhead` plus `.fontWeight`, which is macOS 13 and would have raised the component's floor; the weight is a type token here anyway. --- .../{ => BottomSheet}/BottomSheet.swift | 0 .../Components/BottomSheet/SheetFeature.swift | 125 ++++++++++++++++++ docs/README.md | 1 + docs/navigation-and-containers.md | 26 +++- 4 files changed, 151 insertions(+), 1 deletion(-) rename Sources/DashUIKit/Components/{ => BottomSheet}/BottomSheet.swift (100%) create mode 100644 Sources/DashUIKit/Components/BottomSheet/SheetFeature.swift diff --git a/Sources/DashUIKit/Components/BottomSheet.swift b/Sources/DashUIKit/Components/BottomSheet/BottomSheet.swift similarity index 100% rename from Sources/DashUIKit/Components/BottomSheet.swift rename to Sources/DashUIKit/Components/BottomSheet/BottomSheet.swift diff --git a/Sources/DashUIKit/Components/BottomSheet/SheetFeature.swift b/Sources/DashUIKit/Components/BottomSheet/SheetFeature.swift new file mode 100644 index 0000000..d58dedf --- /dev/null +++ b/Sources/DashUIKit/Components/BottomSheet/SheetFeature.swift @@ -0,0 +1,125 @@ +// +// Created by Roman Chornyi +// Copyright © 2026 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +// MARK: - SheetFeature + +/// One "here is what this gives you" line inside a `BottomSheet`: an icon +/// beside a name and a sentence explaining it. Stack several to describe what a +/// feature unlocks. +/// +/// The icon slot is a `ViewBuilder` rather than a `DashIconSource` because the +/// leading mark is not always an image — a tinted glyph, a badge or a coloured +/// container all appear in this position. It is sized to 40×40 here so a column +/// of features stays aligned whatever each row puts in it. +@available(iOS 14, macOS 11, *) +public struct SheetFeature: View { + public var title: String + public var description: String + @ViewBuilder public var icon: () -> Icon + + public init( + title: String, + description: String, + @ViewBuilder icon: @escaping () -> Icon + ) { + self.title = title + self.description = description + self.icon = icon + } + + public var body: some View { + HStack(alignment: .top, spacing: 16) { + icon() + .frame(width: 40, height: 40) + + VStack(alignment: .leading, spacing: 2) { + // `.subheadMedium`, not `.subhead` + `.fontWeight`: the + // modifier is macOS 13, and the weight belongs to a type token + // anyway — the library carries both weights of this size. + Text(title) + .dashFont(.subheadMedium) + .foregroundColor(Color.dash.primaryText) + + Text(description) + .dashFont(.subhead) + .foregroundColor(Color.dash.primaryText) + } + .padding(.top, 10) + .frame(maxWidth: .infinity, alignment: .topLeading) + } + } +} + +@available(iOS 14, macOS 11, *) +public extension SheetFeature where Icon == AnyView { + /// Convenience for the common case: a template asset tinted to `iconColor`. + init( + title: String, + description: String, + icon source: DashIconSource, + iconColor: Color = Color.dash.blue + ) { + self.init(title: title, description: description) { + AnyView( + Image(dash: source) + .resizable() + .renderingMode(.template) + .scaledToFit() + .foregroundColor(iconColor) + ) + } + } +} + +#if DEBUG + +@available(iOS 17, macOS 14, *) +#Preview("Feature list") { + VStack(alignment: .leading, spacing: 16) { + SheetFeature( + title: "Identity", + description: "Register a username and be paid by name instead of an address.", + icon: .custom("feature-identity", bundle: .dashUIKit)) + SheetFeature( + title: "Platform", + description: "Store contacts and profile data on Dash Platform.", + icon: .custom("feature-platform", bundle: .dashUIKit)) + SheetFeature( + title: "Shield", + description: "Move funds into shielded balances that stay off the public ledger.", + icon: .custom("feature-shield", bundle: .dashUIKit)) + } + .padding(20) + .background(Color.dash.primaryBackground) +} + +@available(iOS 17, macOS 14, *) +#Preview("Custom icon slot") { + SheetFeature( + title: "Anything in the slot", + description: "The icon is a ViewBuilder, so a badge or a coloured container fits too." + ) { + Circle().fill(Color.dash.blueAlpha10) + .overlay(InfoRoundIcon(size: 20)) + } + .padding(20) + .background(Color.dash.primaryBackground) +} + +#endif diff --git a/docs/README.md b/docs/README.md index 083bd0c..ca36fa1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,6 +32,7 @@ and callbacks; they render and report intent. They require `import DashUIKit` an | Symbol | Page | |---|---| | `AddressFieldView` | [Buttons & inputs](buttons-and-inputs.md#addressfieldview) | +| `SheetFeature` | [Navigation & containers](navigation-and-containers.md#sheetfeature) | | `BottomSheet` | [Navigation & containers](navigation-and-containers.md#bottomsheet) | | `Bundle.dashUIKit` | [Foundation](foundation.md#bundle) | | `CoinSelector` | [Lists & rows](lists-and-rows.md#coinselector) | diff --git a/docs/navigation-and-containers.md b/docs/navigation-and-containers.md index 6b71640..72469e1 100644 --- a/docs/navigation-and-containers.md +++ b/docs/navigation-and-containers.md @@ -59,7 +59,7 @@ Use it for screen headers that sit above the main content rather than inside a n ## BottomSheet -File `Components/BottomSheet.swift` · `@available(iOS 14, macOS 11, *)` +File `Components/BottomSheet/BottomSheet.swift` · `@available(iOS 14, macOS 11, *)` Sheet chrome to put **inside** a SwiftUI `.sheet { }`: a grabber, a `NavigationBar` header (optional back button + title + close), and your content. Two height modes. @@ -107,6 +107,30 @@ a **no-op below iOS 16**. The measured content must have a finite intrinsic heig greedy `Spacer`/`maxHeight: .infinity`), or the measurement is wrong. `BottomSheetHeightPreferenceKey` is exposed for advanced cases. + +## SheetFeature + +File `Components/BottomSheet/SheetFeature.swift` · `@available(iOS 14, macOS 11, *)` · public + +One "here is what this gives you" line for a `BottomSheet`: an icon beside a name and a +sentence. Stack several to describe what a feature unlocks. + +```swift +SheetFeature( + title: "Identity", + description: "Register a username and be paid by name instead of an address.", + icon: .custom("feature-identity", bundle: .dashUIKit)) // tinted .blue by default + +SheetFeature(title: "Custom", description: "…") { // or any view in the slot + Circle().fill(Color.dash.blueAlpha10) +} +``` + +The icon slot is a `ViewBuilder`, not a `DashIconSource`, because the leading mark is not +always an image — a badge or a coloured container belongs there too. It is framed to 40×40 +so a column of features stays aligned whatever each row puts in it. The convenience +initializer covers the common case of a template asset with a tint. + --- ## MenuViewModifier From 9729d247050784489579c6c81024b361ce3ea777 Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:20:48 +0300 Subject: [PATCH 06/19] feat(icons): add the instant and timer feature icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two more from the same Figma export, assembled with `normalize-icons.py` / `build-imagesets.py` into the existing `Features` group. Light only, as the export ships no dark variants. Both carry `template-rendering-intent: template`, which the builder does not write — without it the PNG ignores the caller's tint, and these stand next to the three already there that are recoloured per screen. --- .../feature-instant.imageset/Contents.json | 26 ++++++++++++++++++ .../feature-instant.png | Bin 0 -> 460 bytes .../feature-instant@2x.png | Bin 0 -> 685 bytes .../feature-instant@3x.png | Bin 0 -> 967 bytes .../feature-timer.imageset/Contents.json | 26 ++++++++++++++++++ .../feature-timer.imageset/feature-timer.png | Bin 0 -> 656 bytes .../feature-timer@2x.png | Bin 0 -> 1135 bytes .../feature-timer@3x.png | Bin 0 -> 1548 bytes 8 files changed, 52 insertions(+) create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-instant.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-instant.imageset/feature-instant.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-instant.imageset/feature-instant@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-instant.imageset/feature-instant@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/feature-timer.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/feature-timer@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/feature-timer@3x.png diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-instant.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-instant.imageset/Contents.json new file mode 100644 index 0000000..3c64342 --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-instant.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "feature-instant.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "feature-instant@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "feature-instant@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-instant.imageset/feature-instant.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-instant.imageset/feature-instant.png new file mode 100644 index 0000000000000000000000000000000000000000..9a6fd961571e1b138195225a6a3b281a9ad20db3 GIT binary patch literal 460 zcmV;-0W&MYk{maJ5MCG8U_9%g(r>Nk zV)+gMA_2L+%0S(h9?phaiss8R2snWR>S~PP@NXO>I3TYtAM_Ru7Cv^!n_hrXL*0?q-qrH$No`<(Vct`{@V#_V?){IxC{x!VfT#(WeR z!eqmVW5qh4KFaR&GFWw z?^aXr;Ys~D*rk850GhhVoSf4^srxefNC}Pw@jXN}tm1oFb-e->p6n zAm$3TzJ0e(GNAf1=01l2xq#zFuFN>3?~)`*lF<)r4P&l9oeTp200005rvZ=dW?##2Re=A?dnZNtWR{b}p>$}jY z72F$m-wXQ|{Jy4lWZyP{^$U7$>U^-bxia5$b&zN%_Xn(f9``*u{Eb_*$$@RTHO}l4rE@pxuGz4v^G?R;vkr4Zb$|43^JwT= z9rb~^<^#jj@U4P5+wYJ=L0`ge+H~dR~yRCnfUFq3dD^n^&Ywh)-_;i~Lyp3mw z3oLto+%<8^TMfbK=?#M0-ucxrUVB)3M!++Vahl#9PNh?t2Ue}>v^V*8O5I`Ms%MkE z8|9JCRzXh literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-instant.imageset/feature-instant@3x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-instant.imageset/feature-instant@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..22bf9f10050aaee76ca5e3dabca58e1d9ff96c05 GIT binary patch literal 967 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q{^b&H|6fVg?393lL^>oo1K-6l5$8 za(7}_cTVOdki(Mh=yS~@z{kd|_?(3Rwlha&)Hlct6+Z4_nJ-OU`Q)#J!VaUt#Ss(8g zXny=wEfGJbqj(*2-{gK}&d-;>-2TQr{fbb0|KT6$x_-Hx=?|+msoXiXV&}5CMa%E7 z$)sD~U&3jiA6xv@bxx6F!JId5mR7%ewXpSr-<%@x50Caq@AERBYVkC`>F`(n$lOdh zmT<=7H-D!UX&;o&W9pmCe_QDB!Uv4vjQa0(Z(rV|UgJ}+BX8QXi4QnG1J%8A{IyZN zV%p1w+e(;;F1hS(y0@-e&Hh?2Q2A8&5d2-^sSm31)^>Y=X|#7q8v^L?KI@70;Fc0W5FazDlN zbi|5t_m_qL-IsQIv*OEr*7v6$+F`re$gh0yAODxGQI8%jvr)@+nv&$dPW8dprCOi% z+|cI``cQRc^$8iKICsX-6h6l9;Vz;xoe%sK33h!ZP$175I?+ z^OIMAv>opO8TzMTFG%daQKaiL3%(tXuSqNpP3gPeusf&M5NMpmya!XaADgjGsrU}t zoN2dr&)OJQbam-hEu-Ua&$a!&^;qeaFzY+BxyyeS?`}EYe_NahC3yom3nKP2Udq*; T?;>HB1CsM}^>bP0l+XkKj!dds literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/Contents.json new file mode 100644 index 0000000..f5d0e50 --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "feature-timer.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "feature-timer@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "feature-timer@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/feature-timer.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/feature-timer.png new file mode 100644 index 0000000000000000000000000000000000000000..7afaa7f389113987fd1a4493f60b10a8306585ab GIT binary patch literal 656 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 Date: Tue, 18 Aug 2026 18:07:32 +0300 Subject: [PATCH 07/19] feat(icons): add purple variants of the two-colour feature icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Template rendering draws the alpha channel in one tint, so `shield`, `platform` and `timer` — each a coloured shape with white detail inside — lost that detail wherever they were recoloured. The other two icons in the group are single colour and are unaffected. The fix is a second asset rather than a flag: an icon that must keep two colours cannot also be tinted. These carry `#5957D6` (the Purple token) with the white intact, and deliberately have no `template-rendering-intent` — a tint applied to them would flatten them again. `shield` is the designer's export. `platform` and `timer` are derived from the blue originals: each pixel is a blend of white and `#008DE4`, so the blend factor is recoverable from one channel and the blue end swapped. Checked against the exported purple shield — 24 of 14400 pixels differ by more than 8, all on anti-aliased edges. --- .../Contents.json | 23 ++++++++++++++++++ .../feature-platform-purple.png | Bin 0 -> 393 bytes .../feature-platform-purple@2x.png | Bin 0 -> 665 bytes .../feature-platform-purple@3x.png | Bin 0 -> 958 bytes .../Contents.json | 23 ++++++++++++++++++ .../feature-shield-purple.png | Bin 0 -> 760 bytes .../feature-shield-purple@2x.png | Bin 0 -> 1211 bytes .../feature-shield-purple@3x.png | Bin 0 -> 1723 bytes .../Contents.json | 23 ++++++++++++++++++ .../feature-timer-purple.png | Bin 0 -> 351 bytes .../feature-timer-purple@2x.png | Bin 0 -> 613 bytes .../feature-timer-purple@3x.png | Bin 0 -> 855 bytes 12 files changed, 69 insertions(+) create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/feature-platform-purple.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/feature-platform-purple@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/feature-platform-purple@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield-purple.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield-purple.imageset/feature-shield-purple.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield-purple.imageset/feature-shield-purple@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield-purple.imageset/feature-shield-purple@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/feature-timer-purple.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/feature-timer-purple@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/feature-timer-purple@3x.png diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/Contents.json new file mode 100644 index 0000000..4b192b4 --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "feature-platform-purple.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "feature-platform-purple@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "feature-platform-purple@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/feature-platform-purple.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/feature-platform-purple.png new file mode 100644 index 0000000000000000000000000000000000000000..3da58ea36d16783278860468f969d3ccece61d9d GIT binary patch literal 393 zcmV;40e1e0P)fe+wlMF$|Ut5?4Q)8wmxb0ElZU%&kMj|+h0K#m;nD9+2d4bHtV0CG4;Y*3{^ z5Qc>&9>)!=13|?M$dLrp4V(kFZ+nX^%?%z0f-r$HaNr$C1P&Oms*XskK&|?C0Sz2C zpW4l>2~>341e&z6q0xPx0*8SbyAJef@{{7w_jGCUV~O?#A{^F0_f8X*XkUOMItN|< nxB=REcVHL=qhJ(_f}sHbW$(~Exf;`(00000NkvXXu0mjf^be-T literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/feature-platform-purple@2x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/feature-platform-purple@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4fda13aa9059945ecf8baa5a6344c084acb5aebb GIT binary patch literal 665 zcmV;K0%rY*P)j+?{vL0=l8)`sk8XFzjKc>cMJmn0000000000001D$Zmyg0s^pd9mFrjAD~mpZ zq2=OCUy{3~Aaa4Yf>)yra`Ey8xX7n(n_PAw+QNs5Exo% z7w2#8)#dwi-V4We5hScRdqFM+g*;)I<*iZ6{Soz7%w z&sznJHWc}X`_E|$V84mb3Ze~!gsh4xZ6p!@8{6#07}_8r_BjX)7e#cQH8F+2b(@Uf z>fBo6uf#pM`7)z^%N@fBD>UBaPVKKH0-SAhTlK^=*ACpRXA(2H5d+UH zeE^+~*>3TOZ`Mc){;a4%d%^$!000000000007&r%hsu$60H!*y00000NkvXXu0mjf6gefT literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/feature-platform-purple@3x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform-purple.imageset/feature-platform-purple@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..03329d5d5b9a6bc6a670af3105abddd800458d9a GIT binary patch literal 958 zcmV;v13~DXr}+XLydT|_g7?s-0& z1t6CG9LRjucgWv3##kJEmj3La&t?&cy+|}=dqtn!k60}IS#fzW)R6yaKJ- zcJU8y8O_?JY~C7*+^ZqWc=Yg#+uJ>Mw{LyYc>Sq$xs#B~@{8x++&QN|f4iGEK5At0 z*s|cAkhAyj@YGd#`sAz12-|Y@2Hh(qYpY19R(h|oMZrnPa-|&CdGq?a*88Ov1qZ!C zb?@%y{{$c9zqH;jvnY5O`slfilx(eOlSRQn_up3sC+;GYrb`aN8y-O`kEES7xj~Ph zB}-Q!;+5Waf?!Ya?(GlP#Dn`s2#!&(WbQh-=g2A-f&&*ULS?jk@bYA3&uv0R)$)N5 z*^<>AmogFDHTHHFe}ya-XHE-|E*}!! zpL$oGC_7Za)6ozpA2T5374%sHbLga$nU+0!O2z^J000000000000000 g00000000byKM?<;Z$`lxGXMYp07*qoM6N<$f~fYuSO5S3 literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield-purple.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield-purple.imageset/Contents.json new file mode 100644 index 0000000..ab8214c --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield-purple.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "feature-shield-purple.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "feature-shield-purple@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "feature-shield-purple@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield-purple.imageset/feature-shield-purple.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield-purple.imageset/feature-shield-purple.png new file mode 100644 index 0000000000000000000000000000000000000000..e88ade574b5d883b5e5f2bffe7ec2a1eec7861c9 GIT binary patch literal 760 zcmVGI(f=S}C}}%16O3(GVaU?3QQwM$6oix}p*dq;ilC52&*w|0`z$`4 z-`U^u^RwRrA|fIpCJKWgrPASyZFlqX(SheV{-BJt1!K)_*KXG9b8Q$9NT3oml{|VZ zfV&V_!1x*V;fZQ(XTE{}XbJRJ>q7SlgIJN`W}pX`(^YEQ*>#rINWMKLOuKRe=>?dgciILWiR{oL)HUcs2f5tcwzi>_`GD^&4$JuL}v8LXi_$dXNawKu8F|HmJ5_fp)+DTK>6F`hEgOjP4DxK;>qS zK`R>`jZFV*Sx&Xqu1Ov}Z0!mGQZD!3WDNDUTe(G@Z?q5UWUsd@YZ6ifoS(gqt)SwDLxg}Ts)luo q$onrWi_3$!Zd$ZQL_|cK1HSh@aR|;1W zJ^`)-!Snz=7A^=O&P~2Sxd1l|hZ5j`dx0Yf*G9q|nn+x_z97_85L6PJrry_?RqVR1 zQq}gG-P!8>q>WZdl&t@LZ)bMB11P1GQc5YMlu}A5l^FyNT)Ol}m3#MB7`(_#RU*t0 zV~&Bj!7mX)^EG5fJ0NtJsJ*dqeiJE6CBRzk)?A^`t%0LuVyZn7)ELac@0%~19P#yy z9~PQOL81ZH>$@xF#l9H;jY&g0jSZ8DTcBXwRAz{TBod$v4$N1;SV0m@RR+14kFQ;O zwGGFd255pSo8Z1mEVxm`P1h9}z>)gu?iGsox75P3m>P)qmOU0YB3^;1SKTShIk!y# zU6igy`3hwI#kcS*Qz=z5fcmR>uH4?cW3-oLYVjGdiBjEg~w5F}cG90Z~VH)?+VxxxO6L;K^2UVtEHqp%CZ z0nSexAWhc)r^r-*rE+1gKY4(%G8JHXD8R}>5kAOFfOh1aoqc$?wgM}aS)?r!0iHKO zz4i9|W8c4QM&zZXvq+QZG{(#kI+#+aoJXlth}$);GSjNVl{d`({F6^#!Je7Ybiyt| zv)O&S?VdWtp%8*(1Duj=)BZef95zV1*?D5$7oY!$U$_2+YhJZ*v6zFDq;7P2Re?YM z^mp8TrBXg_;aL;t(+m`chY@byzK2eyYg+jzZs$p^QYPV5fY$bnyYbdnEC%sF@m}Ml z$IgW|xNn+&VSD>74h|xu!MgxQ*{tpv_jdXQ2oepj*MswC zQKm&RvbVFuPgw7+?))SDj=M_&|2MYq1|eo^Z(-mrIb=iDW#NBN-3rM Z@&M}ag}Wd2FF*hQ002ovPDHLkV1n)IGXwwt literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield-purple.imageset/feature-shield-purple@3x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield-purple.imageset/feature-shield-purple@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..af1d2477665233ee93fac5b3c9c41234628c6466 GIT binary patch literal 1723 zcmV;s21NOZP)_D2vTmC^5C230aC^B{XE}5AbND!bP0_HI7(=esSRzQvJYU+244*!bZ-N;p>m)f8G{iJV&o%%p$beiWy7w(LtX{k4Aqvie zdiGzRo_?naPdFd&$jC2;OQmI7O3fKtQitIH`^!}|MAv6-9WBFg&IX(Xd!TX7fX)k) z)@PM~Xwv1NoeS8LU|TsGv{M0d8*5M0jT}2od2=pkCj!ofIZuKeMhFa*ZO?nsg<)0% zVW&_1e9okFKbUYCnF*wHXYAt#6A&g^g2zW|S8Y3c9zio#W^N8lLWo!i9vhv%9DcZr zu8g0mU4;;l5}b9WY!!wgiOflf*)W+BOz9ao=7SGk!G()&;QbRXAr}OZ&%6%;3}J}i z4i5E`Gp}c_-#h*S9xgsct+tGgA{vGOkrGTWLvTnOm$tkg80e@BN*v$Jn?Jz$ZUEGe(ruwvV(;_CPK@fWuZk6LkW`in2$#@)M* zaPRKln76ZAOG_zy5^TUc8^yyP?j^^z!*t}e1OFT7PWb z`SpCKr!_k$$bflXnHLZ8z=03iU!K8D)0y8){f6(q`vWctvXhn5Tyk43_qc-$xZ71G zSJ$ozvWn&^lTSLgzy0x{tS#P4``Zq;y-(WURr^?Twt8kV0UN%&G~ea3xw*gWAOF4Q zF)hzdanG2m)WKa;RAc)UEvoSxNY6Z58Q?apzs0L zYD+sFUs!N==(8Jz518BHR?AE4-px6;qnGRe+uGV>VFjVkfU&(gUi^I(q0oS>rGM79 zybq!G+@t}6OYqhzd8c;SSA|;eZ--~Lct(p?p?>7W{dCadB9zu6hA?LOuz=-+zPN$jG+Rx!Bo69*kGT?FSSe?9GT*k z;C&{60I}OBjgQv;^ypTF&xMCb-`*U283M#+vQkh51wn@9AVe(S$QZE?-F0a+3n5|w zA6!`}qbPb%fe^8P%d^MowsEQ;boMh!exp)!HOY29ZX|E`vT}Bk`adw0;<(XRpWjMmAt19iV>1_7hVSgnqbgVXW zZey&|rymT_nz`fKN>jFbX=IMMwqvPt*F{fHAFbLG^(j2FZu<}}Zb$3nh41!6mF;qq zKb$ zqUyH2^{N5g_C469AOhZur#jO$jMz@uiD%h#IxDICT`8hDyTn(K*da7e!3ErEV{SXP zpEM?-Y%Fbzv7wrJ6s1+38ii6yDW#NBN-3q3Qc5YMlu}A5rIb=iDW#MO+`p=bQqC+{ RrV0Q6002ovPDHLkV1kO%It%~+ literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/Contents.json new file mode 100644 index 0000000..fd8b195 --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "feature-timer-purple.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "feature-timer-purple@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "feature-timer-purple@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/feature-timer-purple.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/feature-timer-purple.png new file mode 100644 index 0000000000000000000000000000000000000000..54824ed275915d2dd5ad70a02f17acf1b529b197 GIT binary patch literal 351 zcmV-l0igbgP);8R0EHG1&xhg$ zsF=V&I!poTnD+$IAV`e@y&acNgu~Er0X>VA|77C&!R$ENh9or2{igzcPm?qWP3%NF zq@w)Z|I?>F{qOC0j7@(3fQBB(abLgu`40kDuYSd)PJo&YOd!W`Ab=wTK{$ch)fvf- zn?CI+KJ^o*>A(eq9G91Kn-s@^3vjwR5EObOIF9}fgoR>r;{#$d6IG+QfJ$W`%`%Aq z4IHRI?Xqw`6&<&q7Bz{(pbb4FM>bHwakT9Q5SycM;05$*6Ot2>6By_Tpj}R4G7dRB!U=$2)004b$4;NZ9j@$qM002ovPDHLkV1gB4j?Dl7 literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/feature-timer-purple@2x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/feature-timer-purple@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1a42d2772167b69d765115181a106adb36052032 GIT binary patch literal 613 zcmV-r0-F7aP)IP1_tKdO0ssI2000000002s5j*2WD3XXwH51EwA#l7XM5--Q zcr8)<_)!aabz+|d#s&nAe1HD6n!l$HF(6LA^ zQm!c)rMgC58=-iN7wJF3Hff|Ma>PPhyGW6#FH^NJDDE$j16SouEQ)I;bxmH{p**)F z_9V{@i9O5XEm0MWibx`E@#Qrkvhmt;*Qjy|2zrpH?~8f7tZClEo+# zUox{PPYPQuKdKt}SwrHKgTSIIc{Nv;FK&!NLyJUO@-zj~uE@5g?>c$!Z=X6-SSF(^ zaNf1Nqr*ox7NS(|M_pc>kfOkdi9DI1(*;EKc^2i-T#Prg%U~7RAX}72b32Ghffwh` zHs!f5P(J^pFiY~tPGRFk0xR9Iw$wd=zE@7gSGJV)VIFkrS@F6J`YDodjG_ZOyz# zd2{xAt9z8lUtU9Em#=vxy09=00000NkvXXu0mjf?{EsE literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/feature-timer-purple@3x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer-purple.imageset/feature-timer-purple@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..3ff45c7caf3be75e75f03fa4f8b4876e485e4554 GIT binary patch literal 855 zcmV-d1E~CoP))ZeKfRmJi#Xc1W#${c!HY% zf@@kjp5T$4Cw<8!(aJc{no%xM2Lk{A0H{m2%qrKhS*Tb=YqR1$I{Uj*`!}YZu4p;g z^>AaQ4Xuk?`{tnj6(F{ z^XavJ>&;}|1LYnF6ni@kFqN-dB;Ql+G$Fa1{K&ZH-i~7o<|~)s`X6$~8OZHpjL~1^ zmO@LRA?K;7lQ#sn8Ogm_$Hr_C?C^T5Xc(IF8k+g&OA1-@-tPJG@uQFHpAKH{klrgO zUOb;KufP3R_Pl*_HB_|DUMz9$H8Stw?ChFed=J616y)}pA$!2%|+{{Hv z^R=P`zl5*f+GkH zDHp+B=HJg^Pr;4TFRtuXFJ<7Se&#BbVIDv zP_dHLC>Gpk5L_u1tm9NijO9uRXQVZq=xCXL^A=ny6kKZ(JW@7H>pT1&`(y{#JCIGg zjIz*(l4MKoaQvEG-OLSk=ZgN``@6LMG{+KAgt&X zu{wtC#_=kck$Ol1B~x~7Gw=WXCenp{b4=|2A~nHMtF%M Date: Tue, 18 Aug 2026 18:17:03 +0300 Subject: [PATCH 08/19] fix(sheet-feature): stop forcing the icon into template mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The convenience initializer applied `.renderingMode(.template)` unconditionally and tinted with a `Color.dash.blue` default, so an asset authored in more than one colour was flattened to flat blue no matter what it contained — exactly the case the purple `shield` and `platform` variants exist for. `iconColor` becomes optional. Given one, the behaviour is what it was: template plus tint, right for a single-colour glyph. Omitted, the asset renders as authored and keeps its own colours. The view is built before it reaches the slot: `Icon == AnyView` there, and a `@ViewBuilder` if/else would produce `_ConditionalContent`. `.renderingMode` likewise has to be applied to `Image` before the layout modifiers erase it. --- .../Components/BottomSheet/SheetFeature.swift | 30 ++++++++++++++----- docs/navigation-and-containers.md | 8 +++-- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/Sources/DashUIKit/Components/BottomSheet/SheetFeature.swift b/Sources/DashUIKit/Components/BottomSheet/SheetFeature.swift index d58dedf..c0c1bd8 100644 --- a/Sources/DashUIKit/Components/BottomSheet/SheetFeature.swift +++ b/Sources/DashUIKit/Components/BottomSheet/SheetFeature.swift @@ -68,22 +68,38 @@ public struct SheetFeature: View { @available(iOS 14, macOS 11, *) public extension SheetFeature where Icon == AnyView { - /// Convenience for the common case: a template asset tinted to `iconColor`. + /// Convenience for an asset in the icon slot. + /// + /// `iconColor` is optional on purpose. Given one, the image is drawn as a + /// template in that colour — right for a single-colour glyph. Left `nil`, + /// the asset renders as authored, which is the only way an icon carrying + /// more than one colour keeps them: a template draws the alpha channel in + /// a single tint and flattens the rest away. init( title: String, description: String, icon source: DashIconSource, - iconColor: Color = Color.dash.blue + iconColor: Color? = nil ) { - self.init(title: title, description: description) { - AnyView( + // Built before the call so the slot receives exactly `AnyView`; a + // `@ViewBuilder` if/else would hand back `_ConditionalContent`. + // `.renderingMode` also lives on `Image`, so it is applied before the + // layout modifiers erase the type. + let rendered: AnyView + if let iconColor { + rendered = AnyView( Image(dash: source) - .resizable() .renderingMode(.template) + .resizable() .scaledToFit() - .foregroundColor(iconColor) - ) + .foregroundColor(iconColor)) + } else { + rendered = AnyView( + Image(dash: source) + .resizable() + .scaledToFit()) } + self.init(title: title, description: description) { rendered } } } diff --git a/docs/navigation-and-containers.md b/docs/navigation-and-containers.md index d87d30d..961e6ef 100644 --- a/docs/navigation-and-containers.md +++ b/docs/navigation-and-containers.md @@ -125,7 +125,8 @@ sentence. Stack several to describe what a feature unlocks. SheetFeature( title: "Identity", description: "Register a username and be paid by name instead of an address.", - icon: .custom("feature-identity", bundle: .dashUIKit)) // tinted .blue by default + icon: .custom("feature-identity", bundle: .dashUIKit), + iconColor: Color.dash.purple) // omit to keep the asset's own colours SheetFeature(title: "Custom", description: "…") { // or any view in the slot Circle().fill(Color.dash.blueAlpha10) @@ -134,8 +135,9 @@ SheetFeature(title: "Custom", description: "…") { // or any view in The icon slot is a `ViewBuilder`, not a `DashIconSource`, because the leading mark is not always an image — a badge or a coloured container belongs there too. It is framed to 40×40 -so a column of features stays aligned whatever each row puts in it. The convenience -initializer covers the common case of a template asset with a tint. +so a column of features stays aligned whatever each row puts in it. The convenience initializer takes an optional `iconColor`: given one, the asset is drawn +as a template in that colour; omitted, it renders as authored — the only way an icon with +more than one colour keeps them, since a template flattens everything to a single tint. --- From 3980c93a7c6eb6bee43b5228c3d808cadc40dc42 Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Wed, 19 Aug 2026 11:13:56 +0300 Subject: [PATCH 09/19] feat(icons): name the Features group in DashIcon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The eight `feature-*` assets were the only group in the catalog with no entry in `DashIcon`, so callers reached them by raw string — `.custom("feature-instant", bundle: .dashUIKit)` — which a typo turns into a blank image at runtime rather than a build error. Four of them also still carried `template-rendering-intent: template`. Template rendering discards an asset's own colours and takes the ambient foreground, so anywhere the host does not tint — `MenuItem` sizes its leading icon and nothing more — the blue glyphs drew black. `shield`, `platform`, `timer` and `identity` become `original`. `instant` deliberately stays template: `SheetFeature` takes an `iconColor` and the transfer timing sheet passes `.dash.yellow`, which only works on a template asset. The `-purple` variants are `automatic` and already keep their colours. Co-Authored-By: Claude Opus 5 --- Sources/DashUIKit/Foundation/Icon_DashUI.swift | 17 +++++++++++++++++ .../feature-identity.imageset/Contents.json | 2 +- .../feature-platform.imageset/Contents.json | 2 +- .../feature-shield.imageset/Contents.json | 2 +- .../feature-timer.imageset/Contents.json | 2 +- docs/foundation.md | 7 +++++-- 6 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Sources/DashUIKit/Foundation/Icon_DashUI.swift b/Sources/DashUIKit/Foundation/Icon_DashUI.swift index 3f5e278..03f203d 100644 --- a/Sources/DashUIKit/Foundation/Icon_DashUI.swift +++ b/Sources/DashUIKit/Foundation/Icon_DashUI.swift @@ -73,6 +73,23 @@ public enum DashIcon { case dashDex = "illustration-dash-dex" } + // MARK: - Features + /// `Features` + /// + /// The illustrated rows of an explainer sheet (`SheetFeature`). Several + /// come in a plain and a `-purple` variant — the plain one is tinted by + /// the caller, the purple one carries its own colour. + public enum Features: String, CaseIterable, DashIconAsset { + case identity = "feature-identity" + case instant = "feature-instant" + case platform = "feature-platform" + case platformPurple = "feature-platform-purple" + case shield = "feature-shield" + case shieldPurple = "feature-shield-purple" + case timer = "feature-timer" + case timerPurple = "feature-timer-purple" + } + // MARK: - Checkbox /// `Components/Checkbox` public enum Checkbox: String, CaseIterable, DashIconAsset { diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/Contents.json index 62166d1..e036d3b 100644 --- a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/Contents.json +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-identity.imageset/Contents.json @@ -21,6 +21,6 @@ "version" : 1 }, "properties" : { - "template-rendering-intent" : "template" + "template-rendering-intent" : "original" } } diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/Contents.json index 7227d9b..c989bf6 100644 --- a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/Contents.json +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-platform.imageset/Contents.json @@ -21,6 +21,6 @@ "version" : 1 }, "properties" : { - "template-rendering-intent" : "template" + "template-rendering-intent" : "original" } } diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/Contents.json index 21f2e96..b6b4eea 100644 --- a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/Contents.json +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-shield.imageset/Contents.json @@ -21,6 +21,6 @@ "version" : 1 }, "properties" : { - "template-rendering-intent" : "template" + "template-rendering-intent" : "original" } } diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/Contents.json index f5d0e50..f76355e 100644 --- a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/Contents.json +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Features/feature-timer.imageset/Contents.json @@ -21,6 +21,6 @@ "version" : 1 }, "properties" : { - "template-rendering-intent" : "template" + "template-rendering-intent" : "original" } } diff --git a/docs/foundation.md b/docs/foundation.md index 86d0b34..7e2ed8d 100644 --- a/docs/foundation.md +++ b/docs/foundation.md @@ -114,8 +114,11 @@ Bundled custom assets (under `Media.xcassets/Icons & Illustrations/`) include na (`navigationbar-back/close/plus/info`), search icons, currency glyphs (`icon_dash_currency`, `enter-amount-dash`, `chevron-down-currency-select`), checkbox icons, text-field icons (`text-field-qr`, `text-field-clear`), menu icons -(`menu-send/receive`, `support`, …), and illustrations (`illustration-dash-dex`, -`illustration-xmark`, `checkmark`, `crowdnode.warning`). +(`menu-send/receive`, `support`, …), explainer-sheet feature icons +(`feature-shield`, `feature-timer`, `feature-identity`, `feature-platform`, each +with a `-purple` variant where one exists), and illustrations +(`illustration-dash-dex`, `illustration-xmark`, `checkmark`, +`crowdnode.warning`). --- From 247a950c6a69340296554cb2eb774ae6a8cbde14 Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Thu, 20 Aug 2026 19:19:15 +0300 Subject: [PATCH 10/19] feat(keyboard): give NumericKeyboardView the panel it always sits on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every host drew the same chrome around it by hand — horizontal and bottom padding, a `secondaryBackground` fill, a rounded top, and a second fill run into the bottom safe area — and eight copies had already drifted: radius 20 in some, `bottomPanelStyle()` in others, height caps of 320 or 290 sprinkled where the keypad was expected to fit. The component owns it now, so a caller is just the keypad. Two API notes, both about the iOS 14 floor rather than taste. `background(alignment:content:)` is iOS 15 and `UnevenRoundedRectangle` is 16; the panel is instead a plain `RoundedRectangle` pushed below its own frame by the corner radius, so only the top corners are ever on screen. `.continuous` because the circular default kinks visibly where the arc meets the top edge at this radius. Co-Authored-By: Claude Opus 5 --- .../Components/NumericKeyboardView.swift | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/Sources/DashUIKit/Components/NumericKeyboardView.swift b/Sources/DashUIKit/Components/NumericKeyboardView.swift index b49dfe4..1950fa4 100644 --- a/Sources/DashUIKit/Components/NumericKeyboardView.swift +++ b/Sources/DashUIKit/Components/NumericKeyboardView.swift @@ -84,6 +84,11 @@ enum NumericKeyboardLocaleSupport { public struct NumericKeyboardView: View { private enum Layout { + /// The panel the keypad sits on: inset from the screen edges, + /// clear of the home indicator, and rounded at the top. + static let panelHorizontalPadding: CGFloat = 20 + static let panelVerticalPadding: CGFloat = 20 + static let panelCornerRadius: CGFloat = 32 static let rootSpacing: CGFloat = 20 static let rowsSpacing: CGFloat = 10 static let rowSpacing: CGFloat = 10 @@ -135,7 +140,26 @@ public struct NumericKeyboardView: View { helperTextRow(helperText) actionButtonView } - .background(Color.dash.secondaryBackground) + .padding(.horizontal, Layout.panelHorizontalPadding) + .padding(.vertical, Layout.panelVerticalPadding) + .frame(maxWidth: .infinity) + // Rounded at the top only, and run down into the bottom safe area. + // + // Drawn as a fully rounded rectangle pushed below its own frame by the + // radius, so the bottom corners are never on screen. + // `UnevenRoundedRectangle` says that in one line but is iOS 16, and + // this library ships to 14; `background(alignment:content:)` is 15. + // Both are avoidable, the deployment target is not. + // + // `.continuous` because the circular default kinks visibly where the + // arc meets the top edge at this radius. + .background( + RoundedRectangle(cornerRadius: Layout.panelCornerRadius, style: .continuous) + .fill(Color.dash.secondaryBackground) + .padding(.bottom, -Layout.panelCornerRadius) + .ignoresSafeArea(edges: .bottom), + alignment: .top + ) } private var keyboardRowsView: some View { @@ -221,8 +245,8 @@ public struct NumericKeyboardView: View { @available(iOS 17, macOS 14, *) #Preview { - VStack { - Spacer() + ZStack { + Color.red.opacity(0.3) NumericKeyboardView( value: .constant(""), @@ -233,8 +257,6 @@ public struct NumericKeyboardView: View { inProgress: false, actionHandler: { print("Action button tapped") } ) - .padding(.horizontal, 20) - .background(.red.opacity(0.3)) } } From 971ce133a33c9f65acdd1a5de14372b3780805c3 Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:35:55 +0300 Subject: [PATCH 11/19] feat(enter-amount): say a rejected amount where the converted one goes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An amount the host refuses had nowhere to be said inside the component, so callers put a warning row under the whole field — which pushes everything below it down the moment it appears, for the one state where the layout should stay still. `errorMessage` takes the B row instead. The value, its currency symbol and the chevron all give way to red text, so the line that was showing the converted figure now says why there will not be one. Pinned to `subhead` rather than the row's own font: that turns into `largeTitle` when the secondary slot is the large one, and the message must not grow with it. `lineLimit(1)` for the same reason — `scaleToFitWidth` shrinks a long message rather than wrapping it and moving the cards below. Co-Authored-By: Claude Opus 5 --- .../EnterAmount/DualSwapAmountView.swift | 3 ++ .../EnterAmount/EnterAmountView.swift | 9 +++- .../EnterAmount/SwapAmountView.swift | 43 ++++++++++++++----- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/Sources/DashUIKit/Components/EnterAmount/DualSwapAmountView.swift b/Sources/DashUIKit/Components/EnterAmount/DualSwapAmountView.swift index fce30a6..2830e6c 100644 --- a/Sources/DashUIKit/Components/EnterAmount/DualSwapAmountView.swift +++ b/Sources/DashUIKit/Components/EnterAmount/DualSwapAmountView.swift @@ -39,6 +39,8 @@ internal struct DualSwapAmountView: View { /// When `true`, primary amount occupies the large/top slot; when `false`, secondary does. let isPrimarySelected: Bool let isCurrencySelectorHidden: Bool + /// Stands in for the B row's value when the amount is rejected. + var secondaryErrorMessage: String? = nil let onSwap: () -> Void let onSelectCurrency: () -> Void let onPaste: (() -> Void)? @@ -54,6 +56,7 @@ internal struct DualSwapAmountView: View { secondarySymbol: secondaryCurrency.symbol, showSecondaryDashLogo: secondaryCurrency == .dash, showSecondaryCurrencyButton: !isCurrencySelectorHidden, + secondaryErrorMessage: secondaryErrorMessage, onSecondaryCurrencyTap: onSelectCurrency, swapAnimationKey: isPrimarySelected ) diff --git a/Sources/DashUIKit/Components/EnterAmount/EnterAmountView.swift b/Sources/DashUIKit/Components/EnterAmount/EnterAmountView.swift index ff92126..d984248 100644 --- a/Sources/DashUIKit/Components/EnterAmount/EnterAmountView.swift +++ b/Sources/DashUIKit/Components/EnterAmount/EnterAmountView.swift @@ -60,6 +60,10 @@ public struct EnterAmountView: View { public var primaryCurrency: CurrencyOption = .dash /// Currency for the secondary logical value. public var secondaryCurrency: CurrencyOption = .fiat("USD") + /// Shown in red in place of the secondary amount. Keep it short: it sits + /// in a slot sized for a converted figure. + public var errorMessage: String? = nil + /// When `true`, `primaryAmount` occupies the large slot; when `false`, `secondaryAmount` does. public var isPrimarySelected: Bool = true /// Hides the currency-select chevron when `true`. @@ -122,7 +126,8 @@ public struct EnterAmountView: View { onSwap: (() -> Void)? = nil, onCurrencyTap: (() -> Void)? = nil, onPaste: (() -> Void)? = nil, - onSelectInputType: ((String) -> Void)? = nil + onSelectInputType: ((String) -> Void)? = nil, + errorMessage: String? = nil ) { self._value = .constant("") self._selectedCurrency = .constant(.dash) @@ -130,6 +135,7 @@ public struct EnterAmountView: View { self.style = .dualSwap self.primaryAmount = primaryAmount self.secondaryAmount = secondaryAmount + self.errorMessage = errorMessage self.primaryCurrency = primaryCurrency self.secondaryCurrency = secondaryCurrency self.isPrimarySelected = isPrimarySelected @@ -194,6 +200,7 @@ public struct EnterAmountView: View { secondaryCurrency: secondaryCurrency, isPrimarySelected: isPrimarySelected, isCurrencySelectorHidden: isCurrencySelectorHidden, + secondaryErrorMessage: errorMessage, onSwap: onSwap ?? {}, onSelectCurrency: onCurrencyTap ?? {}, onPaste: onPaste diff --git a/Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift b/Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift index 4cb8d36..b7d1c64 100644 --- a/Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift +++ b/Sources/DashUIKit/Components/EnterAmount/SwapAmountView.swift @@ -45,6 +45,10 @@ public struct SwapAmountView: View { public var showSecondaryDashLogo: Bool = false /// Show a currency-select chevron beside the B row's amount. public var showSecondaryCurrencyButton: Bool = false + /// Replaces the B row's value, symbol and chevron with a red message. + /// A rejected amount has to say something where the converted value + /// would be, so it takes that slot instead of adding a line under it. + public var secondaryErrorMessage: String? = nil /// Action fired when the secondary-row chevron is tapped. public var onSecondaryCurrencyTap: (() -> Void)? = nil /// When non-nil, enables the animated dual-swap ZStack layout and acts as the animation key. @@ -67,6 +71,7 @@ public struct SwapAmountView: View { secondarySymbol: String? = nil, showSecondaryDashLogo: Bool = false, showSecondaryCurrencyButton: Bool = false, + secondaryErrorMessage: String? = nil, onSecondaryCurrencyTap: (() -> Void)? = nil, swapAnimationKey: Bool? = nil ) { @@ -81,7 +86,7 @@ public struct SwapAmountView: View { self.onPaste = onPaste self.secondarySymbol = secondarySymbol self.showSecondaryDashLogo = showSecondaryDashLogo - self.showSecondaryCurrencyButton = showSecondaryCurrencyButton + self.secondaryErrorMessage = secondaryErrorMessage self.onSecondaryCurrencyTap = onSecondaryCurrencyTap self.swapAnimationKey = swapAnimationKey } @@ -147,6 +152,7 @@ public struct SwapAmountView: View { showSecondaryDashLogo: showSecondaryDashLogo, showSecondaryCurrencyButton: showSecondaryCurrencyButton, onSecondaryCurrencyTap: onSecondaryCurrencyTap, + secondaryErrorMessage: secondaryErrorMessage, isPrimaryLarge: isPrimaryLarge ) } @@ -275,6 +281,7 @@ private struct AnimatedSwapLayout: View { let showSecondaryDashLogo: Bool let showSecondaryCurrencyButton: Bool let onSecondaryCurrencyTap: (() -> Void)? + let secondaryErrorMessage: String? let isPrimaryLarge: Bool // MARK: Phase state @@ -309,6 +316,7 @@ private struct AnimatedSwapLayout: View { showSecondaryDashLogo: Bool, showSecondaryCurrencyButton: Bool, onSecondaryCurrencyTap: (() -> Void)?, + secondaryErrorMessage: String?, isPrimaryLarge: Bool ) { self.amount = amount @@ -321,6 +329,7 @@ private struct AnimatedSwapLayout: View { self.secondarySymbol = secondarySymbol self.showSecondaryDashLogo = showSecondaryDashLogo self.showSecondaryCurrencyButton = showSecondaryCurrencyButton + self.secondaryErrorMessage = secondaryErrorMessage self.onSecondaryCurrencyTap = onSecondaryCurrencyTap self.isPrimaryLarge = isPrimaryLarge _fontPrimary = State(initialValue: isPrimaryLarge) @@ -353,17 +362,29 @@ private struct AnimatedSwapLayout: View { .scaleEffect(scaleA, anchor: .center) .offset(y: offsetPrimary ? SwapAnimLayout.primaryOffset : SwapAnimLayout.secondaryOffset) - // B row — always carries the secondary logical value; shows optional chevron + // B row — the secondary logical value, or the message standing in + // for it; shows optional chevron HStack(spacing: 6) { - rowContent( - font: bFont, - displayText: displaySecondary, - symbol: secondarySymbol, - showLogo: showSecondaryDashLogo, - dashSize: dashSize - ) - - if showSecondaryCurrencyButton { + if let secondaryErrorMessage { + // `subhead` outright, not `bFont`: that one turns into + // `largeTitle` when the secondary slot is the large one, + // and the message must stay the size of the converted + // figure it replaces however the slots are arranged. + Text(secondaryErrorMessage) + .font(Font.dash.subhead) + .foregroundColor(Color.dash.red) + .lineLimit(1) + } else { + rowContent( + font: bFont, + displayText: displaySecondary, + symbol: secondarySymbol, + showLogo: showSecondaryDashLogo, + dashSize: dashSize + ) + } + + if showSecondaryCurrencyButton && secondaryErrorMessage == nil { Button { onSecondaryCurrencyTap?() } label: { DashIcon.Common.chevronDownCurrencySelect.image .resizable() From ca870f9734d804d4c5d5077dd27b22646de1d34d Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:44:24 +0300 Subject: [PATCH 12/19] feat(converter-card): let a row carry its own tap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A ConverterCard row could show an endpoint but not offer to change it, so screens that needed both drew their own picker affordance elsewhere. `ConverterCardItem.onTap` makes the whole row a button. The chrome around it swallowed touches from the day the component landed — deliberately, since the seam badge was the card's only control — so `ConverterCardRow` gains `isInteractive` and `ConverterCard` sets it for a row that has an action. It defaults to false, leaving every existing row inert exactly as before; a button placed inside without it would silently never fire. Co-Authored-By: Claude Opus 5 --- .../ConverterCard/ConverterCard.swift | 42 ++++++++++++++++++- .../ConverterCard/ConverterCardItem.swift | 8 +++- .../ConverterCard/ConverterCardRow.swift | 11 +++-- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift b/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift index 79bbd41..d28217a 100644 --- a/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift +++ b/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift @@ -52,7 +52,10 @@ public struct ConverterCard: View { public var body: some View { VStack(spacing: Layout.cardSpacing) { ForEach(Array(orderedItems.enumerated()), id: \.element.id) { index, item in - ConverterCardRow(slot: index == 0 ? .top : .bottom) { + ConverterCardRow( + slot: index == 0 ? .top : .bottom, + isInteractive: item.onTap != nil + ) { row(item: item) } } @@ -75,9 +78,25 @@ public struct ConverterCard: View { /// spacing — never on the bottom row — so it stays correct when the rows differ in height. private var seamY: CGFloat { topRowHeight + Layout.cardSpacing / 2 } + /// A row with an `onTap` becomes a plain button over the whole row area + /// (`contentShape` keeps the padding and the spacer tappable); without + /// one it stays inert, exactly as before the action existed. + @ViewBuilder + private func row(item: ConverterCardItem) -> some View { + if let onTap = item.onTap { + Button(action: onTap) { + rowContent(item: item) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + } else { + rowContent(item: item) + } + } + /// Row layout mirrors `MenuItem` (icon 30pt, 10pt padding) but stays flexible enough for a /// custom icon/trailing view and a multi-line subtitle. - private func row(item: ConverterCardItem) -> some View { + private func rowContent(item: ConverterCardItem) -> some View { HStack(spacing: 10) { leading(item) @@ -175,6 +194,25 @@ struct ConverterCard_Previews: PreviewProvider { ) .previewDisplayName("Static (no swap)") + // Tappable from-row: the whole row is a plain button (no chrome + // change at rest); the bottom row stays inert. + ConverterCard( + fromItem: ConverterCardItem( + icon: .system("d.circle.fill"), + title: "Tap to pick a source", + subtitle: "From", + dashBalance: 245_000_000, + onTap: {} + ), + toItem: ConverterCardItem( + icon: .system("person.crop.circle.fill"), + title: "Identity", + subtitle: "To", + showsBalance: false + ) + ) + .previewDisplayName("Tappable from-row") + // Unequal rows — badge must stay on the seam when the bottom row is taller. ConverterCard( fromItem: ConverterCardItem( diff --git a/Sources/DashUIKit/Components/ConverterCard/ConverterCardItem.swift b/Sources/DashUIKit/Components/ConverterCard/ConverterCardItem.swift index 1dd5519..c12f97c 100644 --- a/Sources/DashUIKit/Components/ConverterCard/ConverterCardItem.swift +++ b/Sources/DashUIKit/Components/ConverterCard/ConverterCardItem.swift @@ -48,6 +48,10 @@ public struct ConverterCardItem: Identifiable { public let trailingView: AnyView? /// When `false` (and no `trailingView`), the row hides its trailing balance. public let showsBalance: Bool + /// Invoked when the whole row is tapped (e.g. to open an endpoint picker). + /// `nil` — the default — leaves the row inert, exactly as before this + /// parameter existed. + public let onTap: (() -> Void)? public init( id: AnyHashable? = nil, @@ -59,7 +63,8 @@ public struct ConverterCardItem: Identifiable { dashBalance: Int64 = 0, fiat: String? = nil, trailingView: AnyView? = nil, - showsBalance: Bool = true + showsBalance: Bool = true, + onTap: (() -> Void)? = nil ) { self.id = id ?? AnyHashable(title) self.icon = icon @@ -71,5 +76,6 @@ public struct ConverterCardItem: Identifiable { self.fiat = fiat self.trailingView = trailingView self.showsBalance = showsBalance + self.onTap = onTap } } diff --git a/Sources/DashUIKit/Components/ConverterCard/ConverterCardRow.swift b/Sources/DashUIKit/Components/ConverterCard/ConverterCardRow.swift index a2fd207..4b61fe2 100644 --- a/Sources/DashUIKit/Components/ConverterCard/ConverterCardRow.swift +++ b/Sources/DashUIKit/Components/ConverterCard/ConverterCardRow.swift @@ -40,16 +40,21 @@ struct ConverterRowHeightKey: PreferenceKey { // MARK: - ConverterCardRow -/// One card in the conversion stack: wraps arbitrary content with the shared, non-interactive -/// card chrome and reports its height via `ConverterRowHeightKey` for its `slot`. +/// One card in the conversion stack: wraps arbitrary content with the shared card chrome and +/// reports its height via `ConverterRowHeightKey` for its `slot`. +/// +/// The chrome swallows touches by default: a row is display-only, and the badge drawn over the +/// seam is the card's only control. A row that carries its own action opts back in with +/// `isInteractive` — without it the button inside the content would never see the tap. @available(iOS 14, macOS 11, *) struct ConverterCardRow: View { let slot: ConverterRowSlot + var isInteractive: Bool = false @ViewBuilder var content: () -> Content var body: some View { content() - .allowsHitTesting(false) + .allowsHitTesting(isInteractive) .padding(6) .background(Color.dash.secondaryBackground) .clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous)) From 0dc75baf258cadcc0076c9bd0db37839e59c2a24 Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:44:36 +0300 Subject: [PATCH 13/19] feat(menu-item): mark the chosen row of a picker list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Picker rows had no design-system way to say which one is selected, so callers reached for their own radio circles. `CheckmarkIcon` draws the tick the way `XmarkIcon` draws the cross — a Shape stroking the source SVG's polyline, crisp at any size and free of an asset. Its artwork is 15x12 rather than square, so `size` sets the width and the height follows the aspect ratio; the default colour names `Color.dash.blue`, which is the #008DE4 the SVG strokes with, so the mark follows the palette. `MenuItemAccessory.selection` puts it in the trailing slot, keeping that slot occupied while unselected so nothing in the row shifts as the selection moves down a list. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 2 +- README.md | 1 + .../Components/Icons/CheckmarkIcon.swift | 103 ++++++++++++++++++ Sources/DashUIKit/Components/MenuItem.swift | 34 ++++++ docs/README.md | 3 +- docs/feedback.md | 18 +++ docs/lists-and-rows.md | 3 + 7 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 Sources/DashUIKit/Components/Icons/CheckmarkIcon.swift diff --git a/CLAUDE.md b/CLAUDE.md index d2cbddc..9c82529 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,7 +37,7 @@ Sources/DashUIKit/ Components/ Most components (one type per file) EnterAmount/ Amount-entry suite (EnterAmountView, SwapAmountView, …) Geometry/ Layout readers + scale-to-fit helpers - Icons/ Code-drawn icons (XmarkIcon, InfoRoundIcon) + Icons/ Code-drawn icons (XmarkIcon, CheckmarkIcon, InfoRoundIcon) Illustrations/ Loading / success / error illustrations Table List/ List1View (label/value row) ViewModifiers/ MenuViewModifier (card chrome) diff --git a/README.md b/README.md index e10c9aa..7db09b2 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ a component already exists before building your own. | `LoadingIllustration` / `LoadingSpinner` | iOS-style activity spinner | [Feedback](docs/feedback.md) | | `SuccessIllustration` / `ErrorIllustration` | 90×90 status badges | [Feedback](docs/feedback.md) | | `XmarkIcon` | Code-drawn close (✕) icon | [Feedback](docs/feedback.md) | +| `CheckmarkIcon` | Code-drawn selection (✓) mark | [Feedback](docs/feedback.md) | | Geometry helpers | `readingFrame`, `readingLocation`, `ScrollViewWithOnScrollChanged`, `scaleToFitWidth` | [Utilities](docs/utilities.md) | | Foundation | `Color.dash.*`, `Font.dash.*`, `.dashFont`, `DashIconSource` | [Foundation](docs/foundation.md) | diff --git a/Sources/DashUIKit/Components/Icons/CheckmarkIcon.swift b/Sources/DashUIKit/Components/Icons/CheckmarkIcon.swift new file mode 100644 index 0000000..375e588 --- /dev/null +++ b/Sources/DashUIKit/Components/Icons/CheckmarkIcon.swift @@ -0,0 +1,103 @@ +// +// Created by Roman Chornyi +// Copyright © 2026 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +// MARK: - CheckmarkIcon + +/// A code-drawn "✓" (selected) mark. Mirrors the source SVG (15×12 viewBox, the +/// polyline 1,5.85 → 5.48,10.7 → 14,1, round caps/joins). Scales cleanly to any +/// `size`, which sets the WIDTH — the height follows the source aspect ratio, so +/// the tick never squares off into something the design did not draw. +/// +/// The default colour is `Color.dash.blue`, which is the `#008DE4` the SVG +/// strokes with; naming the token rather than the hex keeps it following the +/// palette. +@available(iOS 14, macOS 11, *) +public struct CheckmarkIcon: View { + public var size: CGFloat = 15 + public var color: Color = Color.dash.blue + public var lineWidth: CGFloat = 2 + + /// A public struct gets no public memberwise initializer, so this one is + /// written out to keep the call site unchanged for module-internal users + /// and available to app code. + public init( + size: CGFloat = 15, + color: Color = Color.dash.blue, + lineWidth: CGFloat = 2 + ) { + self.size = size + self.color = color + self.lineWidth = lineWidth + } + + public var body: some View { + CheckmarkShape() + .stroke(color, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round)) + .frame(width: size, height: size * CheckmarkShape.aspectRatio) + } +} + +// MARK: - CheckmarkShape + +/// The tick polyline, normalized from the 15×12 source viewBox so it keeps its +/// proportions at any size. +@available(iOS 14, macOS 11, *) +private struct CheckmarkShape: Shape { + private static let viewBox = CGSize(width: 15, height: 12) + + /// Height per unit of width — `CheckmarkIcon` sizes by width. + static let aspectRatio: CGFloat = viewBox.height / viewBox.width + + /// Start, elbow and end of the tick, in source-viewBox units. + private static let points = [ + CGPoint(x: 1, y: 5.85), + CGPoint(x: 5.48, y: 10.7), + CGPoint(x: 14, y: 1), + ] + + func path(in rect: CGRect) -> Path { + let scaled = Self.points.map { point in + CGPoint( + x: rect.minX + rect.width * point.x / Self.viewBox.width, + y: rect.minY + rect.height * point.y / Self.viewBox.height) + } + + var path = Path() + path.move(to: scaled[0]) + path.addLine(to: scaled[1]) + path.addLine(to: scaled[2]) + return path + } +} + +#if DEBUG + +@available(iOS 17, macOS 14, *) +#Preview { + VStack(spacing: 24) { + CheckmarkIcon() + CheckmarkIcon(size: 24, color: .white, lineWidth: 3) + .padding(20) + .background(Circle().fill(Color.dash.blue)) + CheckmarkIcon(size: 40, color: Color.dash.green, lineWidth: 4) + } + .padding() +} + +#endif diff --git a/Sources/DashUIKit/Components/MenuItem.swift b/Sources/DashUIKit/Components/MenuItem.swift index e079b4a..6c915e1 100644 --- a/Sources/DashUIKit/Components/MenuItem.swift +++ b/Sources/DashUIKit/Components/MenuItem.swift @@ -30,6 +30,11 @@ public enum MenuItemAccessory { /// The caller converts the fiat value via its own exchange infrastructure; /// the library only renders the string it receives. case balance(dash: Int64, sign: DashAmountSign = .negativeOnly, fiat: String? = nil) + /// A picker row: the design system's tick on the chosen one. + /// + /// The mark keeps its slot while unselected, so nothing in the row shifts + /// horizontally as the selection moves down a list. + case selection(isSelected: Bool) } /// The glyph beside a row's title that says there is more to explain. @@ -149,6 +154,10 @@ public struct MenuItem: View { .foregroundColor(Color.dash.secondaryText) } } + case .selection(let isSelected): + CheckmarkIcon() + .opacity(isSelected ? 1 : 0) + .accessibilityHidden(!isSelected) } } } @@ -264,6 +273,31 @@ public struct MenuItem: View { .padding(.horizontal) } +/// A picker list: one row marked, the rest holding the tick's slot empty. +@available(iOS 17, macOS 14, *) +#Preview("Accessory: selection") { + VStack(spacing: 0) { + MenuItem( + leadingIcon: DashIcon.Menu.dashLogoSquare.source, + title: "Transparent", + accessory: .selection(isSelected: true) + ) + + MenuItem( + leadingIcon: DashIcon.Features.platform.source, + title: "Platform", + accessory: .selection(isSelected: false) + ) + + MenuItem( + leadingIcon: DashIcon.Features.shield.source, + title: "Shielded", + accessory: .selection(isSelected: false) + ) + } + .padding(.horizontal) +} + @available(iOS 17, macOS 14, *) #Preview("Enabled vs disabled") { VStack(spacing: 0) { diff --git a/docs/README.md b/docs/README.md index ca36fa1..99c03c6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,7 +23,7 @@ and callbacks; they render and report intent. They require `import DashUIKit` an `NavigationBarElement`, `TopIntroView`, `BottomSheet`, `MenuViewModifier`. - **[Feedback](feedback.md)** — `Toast`, `SystemMessageView`, `LoadingIllustration` / `LoadingSpinner`, `SuccessIllustration`, `ErrorIllustration`, - `XmarkIcon`, `InfoRoundIcon`. + `XmarkIcon`, `CheckmarkIcon`, `InfoRoundIcon`. - **[Utilities](utilities.md)** — geometry readers (`readingFrame`, `readingLocation`), `ScrollViewWithOnScrollChanged`, `scaleToFitWidth`. @@ -70,4 +70,5 @@ and callbacks; they render and report intent. They require `import DashUIKit` an | `TopIntroView` | [Navigation & containers](navigation-and-containers.md#topintroview) | | `TransactionView` | [Lists & rows](lists-and-rows.md#transactionview) | | `XmarkIcon` | [Feedback](feedback.md#xmarkicon) | +| `CheckmarkIcon` | [Feedback](feedback.md#checkmarkicon) | | `InfoRoundIcon` | [Feedback](feedback.md#inforoundicon) | diff --git a/docs/feedback.md b/docs/feedback.md index baeb4ea..9212519 100644 --- a/docs/feedback.md +++ b/docs/feedback.md @@ -131,3 +131,21 @@ XmarkIcon(size: 24, color: .white, lineWidth: 2) > `Shape`, it stays crisp at any `size` and honours `color` / `lineWidth`. The > asset remains the right choice where the navigation bar's exact artwork is > wanted. + +--- + +## CheckmarkIcon + +File `Components/Icons/CheckmarkIcon.swift` · `@available(iOS 14, macOS 11, *)` · public + +A code-drawn "✓" selection mark (a `Shape` stroking the polyline of a 15×12 SVG, round +caps and joins). Backs `MenuItemAccessory.selection`. + +```swift +CheckmarkIcon(size: 24, color: .white, lineWidth: 3) +``` + +> `size` sets the **width**; the height follows the source aspect ratio (12/15), unlike +> `XmarkIcon`, whose artwork is square. The default colour is `Color.dash.blue` — the +> token the source SVG's `#008DE4` corresponds to — so the mark follows the palette +> rather than a frozen hex. diff --git a/docs/lists-and-rows.md b/docs/lists-and-rows.md index 1f8c6bd..8db289b 100644 --- a/docs/lists-and-rows.md +++ b/docs/lists-and-rows.md @@ -68,6 +68,9 @@ per-call fonts/colors, to keep rows consistent): a `DashAmount` (duffs) with an optional pre-formatted fiat sub-line. The fiat line is hidden for zero / `.max` / `.min` amounts. The library only renders the fiat string you pass — it does no conversion. +- `.selection(isSelected: Bool)` — a `CheckmarkIcon` on the chosen row of a picker list. + Unselected rows keep the mark's slot (drawn at zero opacity), so nothing shifts + horizontally as the selection moves. --- From 1a6dd4cf3b6ea255c631bcb1742af71b2efe2f54 Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Sun, 23 Aug 2026 16:50:24 +0300 Subject: [PATCH 14/19] feat(icons): draw the chevron in all four directions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A row that opens something needs a chevron, and the library had no general one — only `chevron-down-currency-select`, a raster imageset named after the single place it is used. Anything else reached for an SF Symbol, which does not follow the palette and does not match the design's stroke. `ChevronIcon` draws it the way `CheckmarkIcon` and `XmarkIcon` draw theirs: a Shape stroking the source SVG's polyline, crisp at any size and free of an asset. Only the right-pointing glyph is drawn and the other three rotate it, so all four keep one geometry and one line weight — a set of four assets would have to be kept in agreement by hand. `size` sets the LONG side and the short one follows the 7x12 aspect ratio, so the glyph never squares off into something the design did not draw. The frame swaps its axes with the rotation: a `.down` chevron still measuring 7x12 would leave a gap beside it and clip above. The default colour names `Color.dash.gray300Alpha90`, which is the #B0B6BC at 90% the SVG strokes with, so the chevron follows the palette rather than a frozen hex. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 2 +- README.md | 1 + .../Components/Icons/ChevronIcon.swift | 177 ++++++++++++++++++ docs/README.md | 3 +- docs/feedback.md | 27 +++ 5 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 Sources/DashUIKit/Components/Icons/ChevronIcon.swift diff --git a/CLAUDE.md b/CLAUDE.md index 9c82529..e7f2e39 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,7 +37,7 @@ Sources/DashUIKit/ Components/ Most components (one type per file) EnterAmount/ Amount-entry suite (EnterAmountView, SwapAmountView, …) Geometry/ Layout readers + scale-to-fit helpers - Icons/ Code-drawn icons (XmarkIcon, CheckmarkIcon, InfoRoundIcon) + Icons/ Code-drawn icons (XmarkIcon, CheckmarkIcon, ChevronIcon, InfoRoundIcon) Illustrations/ Loading / success / error illustrations Table List/ List1View (label/value row) ViewModifiers/ MenuViewModifier (card chrome) diff --git a/README.md b/README.md index 7db09b2..7f558dd 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ a component already exists before building your own. | `SuccessIllustration` / `ErrorIllustration` | 90×90 status badges | [Feedback](docs/feedback.md) | | `XmarkIcon` | Code-drawn close (✕) icon | [Feedback](docs/feedback.md) | | `CheckmarkIcon` | Code-drawn selection (✓) mark | [Feedback](docs/feedback.md) | +| `ChevronIcon` | Code-drawn chevron, in any of the four directions | [Feedback](docs/feedback.md) | | Geometry helpers | `readingFrame`, `readingLocation`, `ScrollViewWithOnScrollChanged`, `scaleToFitWidth` | [Utilities](docs/utilities.md) | | Foundation | `Color.dash.*`, `Font.dash.*`, `.dashFont`, `DashIconSource` | [Foundation](docs/foundation.md) | diff --git a/Sources/DashUIKit/Components/Icons/ChevronIcon.swift b/Sources/DashUIKit/Components/Icons/ChevronIcon.swift new file mode 100644 index 0000000..8539ab1 --- /dev/null +++ b/Sources/DashUIKit/Components/Icons/ChevronIcon.swift @@ -0,0 +1,177 @@ +// +// Created by Roman Chornyi +// Copyright © 2026 Dash Core Group. All rights reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +// MARK: - ChevronIcon + +/// A code-drawn chevron, in any of the four directions. +/// +/// Mirrors the source SVG (7×12 viewBox, the polyline 0.8,10.8 → 5.8,5.8 → +/// 0.8,0.8, 1.6 stroke, round caps/joins) — that is the `.right` chevron, and +/// the other three are the same glyph rotated, so all four keep one geometry +/// and one line weight. +/// +/// `size` sets the LONG side; the short one follows the source aspect ratio, so +/// a chevron never squashes into something the design did not draw. A `.up` / +/// `.down` chevron is therefore wider than it is tall, which is what rotating +/// the drawn one gives you. +/// +/// The default colour is `Color.dash.gray300Alpha90` — the palette entry for +/// the `#B0B6BC` at 90% the SVG strokes with. +@available(iOS 14, macOS 11, *) +public struct ChevronIcon: View { + + /// Where the chevron points. + public enum Direction { + case right + case left + case up + case down + + /// Rotation applied to the drawn `.right` chevron. + var angle: Angle { + switch self { + case .right: return .degrees(0) + case .down: return .degrees(90) + case .left: return .degrees(180) + case .up: return .degrees(270) + } + } + + /// True when the rotation exchanges the glyph's width and height. + var isVertical: Bool { + switch self { + case .up, .down: return true + case .left, .right: return false + } + } + } + + public var direction: Direction = .right + /// The chevron's long side — its height when pointing left/right, its + /// width when pointing up/down. + public var size: CGFloat = 12 + public var color: Color = Color.dash.gray300Alpha90 + public var lineWidth: CGFloat = 1.6 + + /// A public struct gets no public memberwise initializer, so this one is + /// written out to keep the call site unchanged for module-internal users + /// and available to app code. + public init( + direction: Direction = .right, + size: CGFloat = 12, + color: Color = Color.dash.gray300Alpha90, + lineWidth: CGFloat = 1.6 + ) { + self.direction = direction + self.size = size + self.color = color + self.lineWidth = lineWidth + } + + public var body: some View { + ChevronShape() + .stroke(color, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round)) + // Drawn at the `.right` orientation, then rotated. The outer frame + // swaps the axes for the vertical directions so the rotated glyph + // reports the space it actually occupies — a `.down` chevron that + // still measured 7×12 would leave a gap beside it and clip above. + .frame(width: size * ChevronShape.aspectRatio, height: size) + .rotationEffect(direction.angle) + .frame( + width: direction.isVertical ? size : size * ChevronShape.aspectRatio, + height: direction.isVertical ? size * ChevronShape.aspectRatio : size) + } +} + +// MARK: - ChevronShape + +/// The chevron polyline, normalized from the 7×12 source viewBox so it keeps +/// its proportions at any size. Points right; `ChevronIcon` rotates it. +@available(iOS 14, macOS 11, *) +private struct ChevronShape: Shape { + private static let viewBox = CGSize(width: 7, height: 12) + + /// Width per unit of height — `ChevronIcon` sizes by the long side, which + /// for the drawn orientation is the height. + static let aspectRatio: CGFloat = viewBox.width / viewBox.height + + /// Top, point and bottom of the chevron, in source-viewBox units. + private static let points = [ + CGPoint(x: 0.8, y: 0.8), + CGPoint(x: 5.8, y: 5.8), + CGPoint(x: 0.8, y: 10.8), + ] + + func path(in rect: CGRect) -> Path { + let scaled = Self.points.map { point in + CGPoint( + x: rect.minX + rect.width * point.x / Self.viewBox.width, + y: rect.minY + rect.height * point.y / Self.viewBox.height) + } + + var path = Path() + path.move(to: scaled[0]) + path.addLine(to: scaled[1]) + path.addLine(to: scaled[2]) + return path + } +} + +#if DEBUG + +@available(iOS 17, macOS 14, *) +#Preview("Four directions") { + HStack(spacing: 32) { + ChevronIcon(direction: .left) + ChevronIcon(direction: .right) + ChevronIcon(direction: .up) + ChevronIcon(direction: .down) + } + .padding() + .background(Color.dash.primaryBackground) +} + +/// Each one drawn on its own bounds, so the frame the rotation reports is +/// visible: left/right are tall, up/down are wide. +@available(iOS 17, macOS 14, *) +#Preview("Measured bounds") { + HStack(spacing: 24) { + ForEach([ChevronIcon.Direction.left, .right, .up, .down], id: \.angle.degrees) { direction in + ChevronIcon(direction: direction, size: 40, color: Color.dash.blue, lineWidth: 4) + .background(Color.dash.gray300Alpha20) + } + } + .padding() + .background(Color.dash.primaryBackground) +} + +@available(iOS 17, macOS 14, *) +#Preview("Dark") { + HStack(spacing: 32) { + ChevronIcon(direction: .left) + ChevronIcon(direction: .right) + ChevronIcon(direction: .up) + ChevronIcon(direction: .down) + } + .padding() + .background(Color.dash.primaryBackground) + .preferredColorScheme(.dark) +} + +#endif diff --git a/docs/README.md b/docs/README.md index 99c03c6..8ff032f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,7 +23,7 @@ and callbacks; they render and report intent. They require `import DashUIKit` an `NavigationBarElement`, `TopIntroView`, `BottomSheet`, `MenuViewModifier`. - **[Feedback](feedback.md)** — `Toast`, `SystemMessageView`, `LoadingIllustration` / `LoadingSpinner`, `SuccessIllustration`, `ErrorIllustration`, - `XmarkIcon`, `CheckmarkIcon`, `InfoRoundIcon`. + `XmarkIcon`, `CheckmarkIcon`, `ChevronIcon`, `InfoRoundIcon`. - **[Utilities](utilities.md)** — geometry readers (`readingFrame`, `readingLocation`), `ScrollViewWithOnScrollChanged`, `scaleToFitWidth`. @@ -71,4 +71,5 @@ and callbacks; they render and report intent. They require `import DashUIKit` an | `TransactionView` | [Lists & rows](lists-and-rows.md#transactionview) | | `XmarkIcon` | [Feedback](feedback.md#xmarkicon) | | `CheckmarkIcon` | [Feedback](feedback.md#checkmarkicon) | +| `ChevronIcon` | [Feedback](feedback.md#chevronicon) | | `InfoRoundIcon` | [Feedback](feedback.md#inforoundicon) | diff --git a/docs/feedback.md b/docs/feedback.md index 9212519..8816168 100644 --- a/docs/feedback.md +++ b/docs/feedback.md @@ -149,3 +149,30 @@ CheckmarkIcon(size: 24, color: .white, lineWidth: 3) > `XmarkIcon`, whose artwork is square. The default colour is `Color.dash.blue` — the > token the source SVG's `#008DE4` corresponds to — so the mark follows the palette > rather than a frozen hex. + +--- + +## ChevronIcon + +File `Components/Icons/ChevronIcon.swift` · `@available(iOS 14, macOS 11, *)` · public + +A code-drawn chevron, in any of the four directions (a `Shape` stroking the polyline of a +7×12 SVG, round caps and joins). + +```swift +ChevronIcon() // points right, 12pt tall +ChevronIcon(direction: .down, size: 16) +ChevronIcon(direction: .left, color: Color.dash.blue, lineWidth: 2) +``` + +| Property | Default | Notes | +| --- | --- | --- | +| `direction` | `.right` | `.right` / `.left` / `.up` / `.down` | +| `size` | `12` | The **long** side — height for left/right, width for up/down | +| `color` | `Color.dash.gray300Alpha90` | The palette entry for the source SVG's `#B0B6BC` at 90% | +| `lineWidth` | `1.6` | Source stroke width | + +> Only the `.right` chevron is drawn; the rest are that glyph rotated, so all four share one +> geometry and one line weight. The frame swaps axes with the rotation, so a `.up` / `.down` +> chevron measures wider than tall and lays out without a gap beside it. + From b351db46a455ee5355637876d922345fc5e1a306 Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Sun, 23 Aug 2026 16:50:58 +0300 Subject: [PATCH 15/19] feat(converter-card): show a chevron on a row that opens a picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ConverterCardItem.onTap` made a row a button but left it looking exactly like an inert one, so the only way to find out a row could be changed was to tap it. The chevron follows `onTap` rather than a flag of its own: the two are set together by definition — a row is given an action precisely because there is something to open — and a separate `showsChevron` would only add a way for them to disagree. Rows without an action are unchanged. Co-Authored-By: Claude Opus 5 --- .../Components/ConverterCard/ConverterCard.swift | 15 +++++++++++++-- docs/feedback.md | 3 ++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift b/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift index d28217a..50e8c48 100644 --- a/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift +++ b/Sources/DashUIKit/Components/ConverterCard/ConverterCard.swift @@ -24,6 +24,9 @@ import SwiftUI /// /// Pass `onSwap` to show a tappable `diagonal-up-down` button; omit it (or pass `nil`) for a /// static `arrow-down` indicator when the card is non-swappable. +/// +/// A row given an `onTap` becomes a button and grows a trailing chevron, so a +/// row that opens a picker reads as one at rest. @available(iOS 14, macOS 11, *) public struct ConverterCard: View { @@ -119,6 +122,14 @@ public struct ConverterCard: View { Spacer(minLength: 8) trailing(item) + + // A row that opens something says so. `onTap` is the only signal + // the card has for that, and it is exactly the right one: the two + // are set together by definition. + if item.onTap != nil { + ChevronIcon() + .padding(.leading, 2) + } } .padding(10) } @@ -194,8 +205,8 @@ struct ConverterCard_Previews: PreviewProvider { ) .previewDisplayName("Static (no swap)") - // Tappable from-row: the whole row is a plain button (no chrome - // change at rest); the bottom row stays inert. + // Tappable from-row: the whole row is a plain button carrying the + // trailing chevron; the bottom row stays inert and chevron-less. ConverterCard( fromItem: ConverterCardItem( icon: .system("d.circle.fill"), diff --git a/docs/feedback.md b/docs/feedback.md index 8816168..0702607 100644 --- a/docs/feedback.md +++ b/docs/feedback.md @@ -157,7 +157,8 @@ CheckmarkIcon(size: 24, color: .white, lineWidth: 3) File `Components/Icons/ChevronIcon.swift` · `@available(iOS 14, macOS 11, *)` · public A code-drawn chevron, in any of the four directions (a `Shape` stroking the polyline of a -7×12 SVG, round caps and joins). +7×12 SVG, round caps and joins). `ConverterCard` puts one on every row that carries an +`onTap`, so a row that opens a picker reads as one at rest. ```swift ChevronIcon() // points right, 12pt tall From 13c6879a5c302e59fd8d331a2acef5522c2ef896 Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Sun, 23 Aug 2026 18:35:49 +0300 Subject: [PATCH 16/19] feat(icons): add the segmented control's directional arrows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The payments landing's selector drew SF Symbol arrows in the ambient colour, so the three tabs were indistinguishable until the label was read. The design gives each direction its own colour, and the arrows are artwork rather than a tint over one glyph. Six assets: `segmented-control-receive` / `-transfer` / `-send` in green, light blue and blue, each with a grey `-disabled` twin. Two files per direction rather than one tinted two ways — the colour lives in the artwork, and a coloured arrow dimmed to the unselected treatment would no longer match the grey label beside it. Rendering stays `original` for the same reason: template would discard the colour that is the whole point. Named in `DashIcon.SegmentedControl` so a consumer cannot reach them by raw string, where a typo is a blank image at runtime instead of a build error. Light only for now — the export carried no dark variants. Re-running `build-imagesets.py` with `--dark` will fold them into these same imagesets. Co-Authored-By: Claude Opus 5 --- .../DashUIKit/Foundation/Icon_DashUI.swift | 16 ++++++++++++ .../Segmented control/Contents.json | 6 +++++ .../Contents.json | 23 ++++++++++++++++++ .../segmented-control-receive-disabled.png | Bin 0 -> 341 bytes .../segmented-control-receive-disabled@2x.png | Bin 0 -> 463 bytes .../segmented-control-receive-disabled@3x.png | Bin 0 -> 606 bytes .../Contents.json | 23 ++++++++++++++++++ .../segmented-control-receive.png | Bin 0 -> 332 bytes .../segmented-control-receive@2x.png | Bin 0 -> 461 bytes .../segmented-control-receive@3x.png | Bin 0 -> 592 bytes .../Contents.json | 23 ++++++++++++++++++ .../segmented-control-send-disabled.png | Bin 0 -> 355 bytes .../segmented-control-send-disabled@2x.png | Bin 0 -> 470 bytes .../segmented-control-send-disabled@3x.png | Bin 0 -> 612 bytes .../Contents.json | 23 ++++++++++++++++++ .../segmented-control-send.png | Bin 0 -> 322 bytes .../segmented-control-send@2x.png | Bin 0 -> 441 bytes .../segmented-control-send@3x.png | Bin 0 -> 548 bytes .../Contents.json | 23 ++++++++++++++++++ .../segmented-control-transfer-disabled.png | Bin 0 -> 496 bytes ...segmented-control-transfer-disabled@2x.png | Bin 0 -> 769 bytes ...segmented-control-transfer-disabled@3x.png | Bin 0 -> 970 bytes .../Contents.json | 23 ++++++++++++++++++ .../segmented-control-transfer.png | Bin 0 -> 466 bytes .../segmented-control-transfer@2x.png | Bin 0 -> 735 bytes .../segmented-control-transfer@3x.png | Bin 0 -> 946 bytes docs/foundation.md | 4 ++- 27 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive-disabled.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive-disabled.imageset/segmented-control-receive-disabled.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive-disabled.imageset/segmented-control-receive-disabled@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive-disabled.imageset/segmented-control-receive-disabled@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/segmented-control-receive.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/segmented-control-receive@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/segmented-control-receive@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-send-disabled.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-send-disabled.imageset/segmented-control-send-disabled.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-send-disabled.imageset/segmented-control-send-disabled@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-send-disabled.imageset/segmented-control-send-disabled@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-send.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-send.imageset/segmented-control-send.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-send.imageset/segmented-control-send@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-send.imageset/segmented-control-send@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer-disabled.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer-disabled.imageset/segmented-control-transfer-disabled.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer-disabled.imageset/segmented-control-transfer-disabled@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer-disabled.imageset/segmented-control-transfer-disabled@3x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer.imageset/segmented-control-transfer.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer.imageset/segmented-control-transfer@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer.imageset/segmented-control-transfer@3x.png diff --git a/Sources/DashUIKit/Foundation/Icon_DashUI.swift b/Sources/DashUIKit/Foundation/Icon_DashUI.swift index 03f203d..e14cae6 100644 --- a/Sources/DashUIKit/Foundation/Icon_DashUI.swift +++ b/Sources/DashUIKit/Foundation/Icon_DashUI.swift @@ -97,6 +97,22 @@ public enum DashIcon { case checkmarkUnchecked = "checkbox-checkmark-unchecked" } + // MARK: - SegmentedControl + /// `Segmented control` + /// + /// One arrow per direction, in that direction's colour, plus a grey + /// `-disabled` twin for the segment that is not selected. The colour is + /// in the artwork rather than applied at the call site, so a segment + /// switches file rather than tint when the selection moves. + public enum SegmentedControl: String, CaseIterable, DashIconAsset { + case receive = "segmented-control-receive" + case receiveDisabled = "segmented-control-receive-disabled" + case send = "segmented-control-send" + case sendDisabled = "segmented-control-send-disabled" + case transfer = "segmented-control-transfer" + case transferDisabled = "segmented-control-transfer-disabled" + } + // MARK: - SearchBar /// `Components/SearchBar` public enum SearchBar: String, CaseIterable, DashIconAsset { diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive-disabled.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive-disabled.imageset/Contents.json new file mode 100644 index 0000000..f9ccdac --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive-disabled.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "segmented-control-receive-disabled.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "segmented-control-receive-disabled@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "segmented-control-receive-disabled@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive-disabled.imageset/segmented-control-receive-disabled.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive-disabled.imageset/segmented-control-receive-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..42c0de5672449fa9057f1a3fd1fd852818ce0ff2 GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^AhsX}8<2dWZ2J^QaTa()7Bet#3xhBt!>l*8o|0J>k`Ftg=5hcO-X(i=}MX3yKnd!NS^EOUP2FhIU zba4!+V0=2^EaxEyf!6yzUsNI*Wp6axQ|^?z(j3*u8{w#BP_ex^h9!_gV`o>Xis%!K zo3fi3P5)JYKX}gI&GA1rPM))4pQNZq9+;Hy;#S{Zn00009a7bBm000&x z000&x0ZCFM@Bjb+0drDELIAGL9O(c600d`2O+f$vv5yPNkl2fowx1sc5MnUZ5w) z2?X~Q_j-chqKJsG;yUl4Q(_Qqufyn`M5hVJ7e6N5f@7K2N28 z8J{Y5rYQBVz^7Unh3*gFx@ExMCzzgUB z5D!2+0C)g7fiV$X5Fo_Fofq%~cmj4tI?ovlqz!GE(bB|ZehboeCj8R*zs|ISpuzLt zCB-?v0m76#M+*jovre^8n5g~-s&?!Rhe?>(BVe@WZJ{YJo>!GJPz1?tuFB>ud*E2N zpop@N94H1hA{;~1LjSwdM_m{ZMredaXoN;+ghptDM))6uo>%=|?=>0NbNlNJ!uU+2 zgPN+mHT&bDzbLuf(_5J86l!8ybP0@fKaIE1)BO3Etz zfS3?N`p(49J;Vj0?z~ES{qcj&H{NfMcPFYe|MTEm&Xp`(*c5~lw44ihI2X$AbDLoU zHiVtP)?yD}Iki*5-*Lj?TV)usbNH(^|9AH!ldTqR&E2rvLx|07*qoM6N<$f_`fQi2wiq literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/Contents.json new file mode 100644 index 0000000..2b49d76 --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "segmented-control-receive.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "segmented-control-receive@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "segmented-control-receive@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/segmented-control-receive.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/segmented-control-receive.png new file mode 100644 index 0000000000000000000000000000000000000000..ade17382a9243f9087c75a07b24553db13743764 GIT binary patch literal 332 zcmeAS@N?(olHy`uVBq!ia0vp^AhsX}8<2dWZ2J^QaTa()7Bet#3xhBt!>l*8o|0J>k`Ftg=5hcO-X(i=}MX3yKnd!NS^EOUP2Fe`w zba4!+V0=1hFYh4(f!6nuDp76;EM^Ltg7#d(3T6tdU6)i-U(9IeX%P4#);&Sc$YIjs zH%glh<)5Eiv+r{4r_YgWmY>c=UeJ)~oN)iy?h9`0S11_srQ8fL>zoboFyt=akR{07?0H+yDRo literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/segmented-control-receive@2x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/segmented-control-receive@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a2a3eeb7bc8e50b01345c0160ac7951b4e7e7492 GIT binary patch literal 461 zcmV;;0W$uHP){Zn00009a7bBm000&x z000&x0ZCFM@Bjb+0drDELIAGL9O(c600d`2O+f$vv5yP|n$jO{PyYdm8F2f^d@lnHJbz(a&J zlAg(SS%qE}o>uq(z+>~x3{z0TydT=ukZnMH)1(k8VBi28SjB|EG3*FIj|^gi z*dR8D?FMctDqSGpq&$!nLN9xGwFQ8(8q)}?_b^_f|8(=(RzbVKh!OQ6WXSyTDcklZ z^Z;x9MW}fW`;*d$??|3ruCqRN4|_m}XQrR#sX;#nSO5N9hPe#md@UBSO^S$*YOi!* z(&X(p-ZtfJ(&pPlHG}S?C{$Dq{5b(KOqN~!Bi2#%-cdVFo%}OCog}gv#-_P0|EfMY zcV=+-SM#Y=EEjC>ugRw}2rUH9i*p9yqgJaXpUV6KbC7?R%nq|-00000NkvXXu0mjf Dz0tl3 literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/segmented-control-receive@3x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-receive.imageset/segmented-control-receive@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..886915689e6dc9bbbb9fbd84245fb77e945b8809 GIT binary patch literal 592 zcmV-W0x^RnEG8Ccffi_i7HEMMXn_{E3&3Bcep1e>IxIg}NIGa~kSRVXi2A|ysoQV?)P zIA>2#QW(;a{7Vm>hp#4kyiFoNv$x}JO%8l?Xj}fe+e?HF^O6e*Oa>-lybW6lVYHJD z{>9W5wz`2B{39qjV(f*jt028yImHk@r^2TT1tj&h{A^|N7Cya#d@j7@0*ms=k!2nj zWiC(u_9?u*H+zp+P0IC<%=i3cW&ral*8o|0J>k`Ftg=5hcO-X(i=}MX3yKnd!NS^EOUP2Fl#? zba4!+V0=2km+xSJNbCFBujT@5YC>xcu$`LmWA=oO+5~1DhX@8~0ZyTojNWYZwp%kY zZpFRz)>?nb@%gzYcV~Z|TRgd4;YGn-Q=Q3TEnD7f%{IR1Tz?~0Pj%B0UL%bvYfF@&%? z7Z-{9w|O$XFz!%rN;@)XXUW}~7m;!EyZk%msFmN}_QtleCM^H-rpBbA`mpCNCLf;V vtqv2|>#_X5cuyxcliuBW9md0-^?xvawRi3|$uBww^eKa{Zn00009a7bBm000&x z000&x0ZCFM@Bjb+0drDELIAGL9O(c600d`2O+f$vv5yPS1t0(9Mntj1m5V|NmK|O$a z0PO)p_f`)eB1I*pv~+7P;0fG|MBQkT=S)Cp%%7Sx%luZu%**d(K6pb&VK^FXk!M^8 zp$5XB-E17C)pOE;HJeq%HLe4ZM$jADlRomuzFt52#MlgND&hTjx}8aOV~zJjq#JkU zy55<-{=a0pkcp0w2vy-3rwe62Ibjh5GcLc7?DOUQFp^UeD!;pji5V-?IjN34WWa-5 z&gU1%f}Dg8keP6P{F@U?6?vG;kA{|7WV~J+2GC&$bx{Dm@-PZQSR7XWcYe@ z8HxWK-4zFUWV@}#BMF{D4M68ep^|pEg4K>oZ%y&pRL}L5Zje_04ZClD@o2s+asU7T M07*qoM6N<$f*bM3#{d8T literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-send-disabled.imageset/segmented-control-send-disabled@3x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-send-disabled.imageset/segmented-control-send-disabled@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..1e1b7541f64c579c77d08c6b04b2ef5d79dba4c4 GIT binary patch literal 612 zcmV-q0-ODbP)r+VQB-T#seF z5T@%va}$n`OQ+-Da#^veCH0Ydmh-bSR~S}UKRYXOC=W>BXL3*OK&by|d8U?>Be|0< zGtdMgSxf7cD75Bgbz<5)libC^$@yg|4Am|^K~kOrh3>goU}{5#OM~(pSW@@gED#rM zdG-l2=Vp}-&htY_>n9g~AbB4-(wpb|4CCC0Q$OphL`*fa2KNa~&4=7o5?`89>J`6*F<|l(F3V9~M2vMY(o*(9JSm{4gE69K$k!MXL?RH7 z(JCw4VASaK)$D)bmBxIj1@-0E3#;F-z8%&Q$757&ZBlT%@cnBrH#OC9+G4~nb=tg5 zYOl=B{D1?_o2b0-LND|}FZ4n$^g=K6LN6Rj$guxMGrvi47YYh&O)OLuc?&D$zuWp; yu+`Tl*8o|0J>k`Ftg=5hcO-X(i=}MX3yKnd!NS^EOUP2FmR9 zba4!+V0_wtkhjS|q^-W|-p33lrMv~w57_b+7-u{ZWfwTj_KM{d%en__DjgqJq))Wd z=H*qLQSse>)}HVG*8lz8uqy1NL}C^Dezp}SFNyB26!_^GHm~T7X4>7@Ok>`leHSOZ zTKs4eW8ET-I^DAqKgijXiJZ{4W6`~+a)@D(>{Zn00009a7bBm000&x z000&x0ZCFM@Bjb+0drDELIAGL9O(c600d`2O+f$vv5yPV1hTm69_l(2H**#A+FV} zimfLwo$Q?gl@sm+gwE~ zzu{d*=3QeL_V}3pYz7VEnDR2KWR{lzcuwEtTZyu(7>)(Kj6 jpd9@Rb&PIVuqx2U>!f8I;%Y2_Sgo-1X+jcps* miJ==1AybnipQ=h-%zpw{hkP1hGC>6Z0000d&|85 literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer-disabled.imageset/segmented-control-transfer-disabled@2x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer-disabled.imageset/segmented-control-transfer-disabled@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1928d926c63e7550d1c2a81979990d1c9aaadb00 GIT binary patch literal 769 zcmV+c1OEJpP)>lXf00009a7bBm000&x z000&x0ZCFM@Bjb+0drDELIAGL9O(c600d`2O+f$vv5yPv(;)e-4?aWjlWMOv0W;c}&UKO>>O!x41`qACH z2s7aNejWnx4j2o1i~`z>!<~A$+=qM5dtF=+;(>h?t;jH*Lej9>sD)q^;YMk3>g4NUyLq>&JhsjqFs0ZBv;eci#ZXk z3m%I7coG_z!$9mo$}mK1y09S&z0lY8Nh4AhRyOJC#R&qbPa_d7E@V?({3si@b-W@; z76f6xRw=b@c7!VCkPvacpS^Aq1b-sLO_huGT(9%NW=kMo8eyc=!u029wP-zo+ajS%)5n5H7z-j{EQo}$AQHyCA>!QBkadd~gM*g8 zOyHQ(pi4hpb)sVs;gW6*M&tTg@w=S}LtpK2tz%PLMiMb4UR77^Y<_SC39~hGLlwiu zmEUn6#PG$;&JJHg9@VuA!3?*6 z>JW1XDdCIPJOLF!h91s}?uaON3L1A)$3KIh9Ffjnm6W7mziPD7{STB6A`d zS}!WeZLox&WYESr0^+;$m46M4NSVPN z0Q3Oh0T>QIIe_2+I*e4*N~Q7&9)J^+2VWeZBaW2re(rWCrld(WrAc;}{2n^(Zj#B@ z{eGL>F9a0W-r37@&htPZ(`rE2M@oyc)<_XFg7_Wqap(IQBIdjGAwpqxYHSrWfqHdr zm8);v0+A2`OCQR+Z$J}VM@FR95J&zEe8D;;#@tJ5&TQUl{91gJt#p^oa09M z;2;Gt(>=`?T_F?(K^21Oi<7ezh#5yP(i0&=76KyOirb$KXVg7$BVK>5MkbgiZp7=) z|1=Bgi5oHc(+Z|nPuz&hA98a~+=$*ElOq-&6r?{~rrR6@L^{{_dH(S3lY!Hz;o-XK zd8YUWpFwDlKn}`D2$-?fIR2f10f5Hwg!KSf! zwK8vCL-vy>LUY#Wx_EM~BJbk}aZ(q5((;U@Gt9_v2r>8{B~Xl(q7#n zWj*^Prq@k~X}iwFWxSvWr_;UMdv_TLEv>H?66(A@e9VcfX66Pt{uptMB~@llDKJS zE!re;e)sW% z=f2Ju2AywJTO8+;8Zp=cD8d=FAT&toT%@jVFs)j#*_=+2}2%k7_$2P4zLpg0fxKukjR_cyUDXZqsLr6(Ot5F@?%WB0^P zybH}<{JG|domhkE$)9VU*ol1$5Xhe{o^*7BCiwZ|=7}ABHfO@0%e9{q#EP3IcC;Cp sHrqS9c?6n4j%ks8HA=83Q&HvIe*-*ZakpF=BLDyZ07*qoM6N<$f{Q!7g#Z8m literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer.imageset/Contents.json new file mode 100644 index 0000000..af0764e --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "segmented-control-transfer.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "segmented-control-transfer@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "segmented-control-transfer@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer.imageset/segmented-control-transfer.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer.imageset/segmented-control-transfer.png new file mode 100644 index 0000000000000000000000000000000000000000..02225b8f5079bb6c846ed15dd511b3beb5944af3 GIT binary patch literal 466 zcmV;@0WJQCP)W+5P zVWTYT3GfCz0whXYwl|O)*hQgDg?Y|kDNv%Bg*(55VfcpmX5P<-u){c9wMl?ah)aev zc<34n&;+W%ep_3R313dF9Urwmw;kTUTO;6IA+Ihm@wi0q==nOS+K*F{87QQrH8 z`yb{ePUg}U?|FF(5PFJ}&Fbw%+?(@28n2_CL-R?4neYn`G+>9TyZq$l_6Ux!ua%R$ z@kl%!@(jhmy*i7utJ6wTj+I_ycUU(iK{@me($0ssI207*qo IM6N<$g6=fK=l}o! literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer.imageset/segmented-control-transfer@2x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Segmented control/segmented-control-transfer.imageset/segmented-control-transfer@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5a2605f7af4ec2bbca77929758d27fc93b98030c GIT binary patch literal 735 zcmV<50wDc~P)>lXf00009a7bBm000&x z000&x0ZCFM@Bjb+0drDELIAGL9O(c600d`2O+f$vv5yPk6vn?dc#}4jxIvv8kY&{n!41d_$O%Gs zCL3TC??O+I<^<#hAOY!;9Kkneq{fi8@w~oqN(+wvB*<2?;FGYJ=Qq~R^FDiKOo);5 zbzvjm3u0X{ZB}#zrF!m%9S;S`iUztfF5mm}q5QR(=rPD>QV>Gzqadkh;D^8H7>H<` zflB*&evX1ftBup^`H9xtPwk*_bawdm3Pph!U(iD^lX8?ZI^1$e?dR>6ur$UO2=p)u z!bnO~7QYOTvqF$0U!=g~?iX^VcTF%|U<2jp7fT>I*i;IpZz_m0Uet(!3N(3X&`Qjg z>5C*W(mmx&{f-RVQ1Z@MVbC7?iQW>Aa1R@k@MY4e?(kb#NV{w4goYWRxP0sEV7Kp# zzu!ofy$q6)JBW-__}FUx^e@1kTYWb|g6=J=5`RY5yVh+&@Iuqa2ehoyxZr#J( zFDOU~uS8prsJ0+cZ9$^if<*OdK&Xiq%>5|?WvKy&e3xW{ggw8 zd+OXk!qVyB_(h!2)3Non}$Om0wfg2Ivsbixhb1Pv!> z*%>x9o5nlZ696YDH-MqgF3An>4eA+^H2tx^N)Dkm_(yi*NW=J9#BnUic_Y1w^d<-r zm{aP7xEb-Ab_SQYn^>>f?{IDpzj~@ zg8}jwqq(_5P|PDS!Y4$q1$iOpsP<39GqOHOX-uhq?t3PWPm)mWpL;O{e3FDR{{&(R z`y>fP{=xtCNn*na{8J~wBPapkA0j4(+a~11x~Ar6G-@^)4cA&lSGHcA-*g2^gN!_- z#UunC9gPOVwm9cUVS!^{?7&`|ce3gW&tw)ViWq9LWyeDNvwth|JGlSQ^QC54hCrX7 zBnWe~{l^m%h7sEiA3ZV?3hS;fedMcOzKll)rUVimXfpGn@22l~_&q%4+8agQ z{Pbb)gs8;mN+m{DDlxiJiP4oxjILB-bfprbD^E&H9cD}rBi#-j1&%19sr0G_-z;{b zb11y`4_L;>#i@Zf&_O_(hZb=h_s&n{L!GwYcN=4GGv4z^)hARODR#U1f<_FtN{+D@K8Jsor!3k$oGvzdz# z!-2?`o&{6klTi5g8zsw`Y4~U2ljv&-DeRw+Pr|5OXcqEM)F)w-iYeru2R;d-d<&4r zKXE>ZUe|<7|0MV%j6R#Qv43L!Gk^%ENj`~`1I-x!9~9dB=}WG{0pC9fhN1+20jSYO UJZB97S`~Uy| literal 0 HcmV?d00001 diff --git a/docs/foundation.md b/docs/foundation.md index 7e2ed8d..fb0a41c 100644 --- a/docs/foundation.md +++ b/docs/foundation.md @@ -116,7 +116,9 @@ Bundled custom assets (under `Media.xcassets/Icons & Illustrations/`) include na icons, text-field icons (`text-field-qr`, `text-field-clear`), menu icons (`menu-send/receive`, `support`, …), explainer-sheet feature icons (`feature-shield`, `feature-timer`, `feature-identity`, `feature-platform`, each -with a `-purple` variant where one exists), and illustrations +with a `-purple` variant where one exists), segmented-control arrows +(`segmented-control-receive/transfer/send`, each with a grey `-disabled` twin for +the unselected segment), and illustrations (`illustration-dash-dex`, `illustration-xmark`, `checkmark`, `crowdnode.warning`). From 4b43ddb4ca1daed9ebb15ce4dcc396d1c7be0957 Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Sun, 23 Aug 2026 23:36:58 +0300 Subject: [PATCH 17/19] fix(dash-amount): let a caller ask for more decimal places MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `DashAmount` rounded to five places, which is right for a balance and wrong for anything smaller: a Core network fee is a few hundred duffs, and at five places 0.00000226 DASH renders as "0". A confirmation drawn with this component therefore told the user the fee was zero. Five stays the default — enough to be exact at everyday sizes, short enough not to dominate a row — and becomes a parameter rather than a constant. `MenuItemAccessory.balance` carries it through, since a row is where a figure like that is usually shown. `DashAmountFormat` opens up with it: the default belongs beside the formatter that applies it, and a default argument cannot reference an internal one. Co-Authored-By: Claude Opus 5 --- Sources/DashUIKit/Components/DashAmount.swift | 28 +++++++++++++------ Sources/DashUIKit/Components/MenuItem.swift | 7 +++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Sources/DashUIKit/Components/DashAmount.swift b/Sources/DashUIKit/Components/DashAmount.swift index 115145a..23efd6d 100644 --- a/Sources/DashUIKit/Components/DashAmount.swift +++ b/Sources/DashUIKit/Components/DashAmount.swift @@ -31,22 +31,29 @@ public enum DashAmountSign { // MARK: - Internal formatter -private enum DashAmountFormat { +public enum DashAmountFormat { static let duffsPerDash: Decimal = 100_000_000 - static let numberFormatter: NumberFormatter = { + /// Five is what a balance wants: enough to be exact at everyday sizes, + /// short enough not to dominate a row. It is NOT enough for every figure — + /// a Core fee of a few hundred duffs rounds to zero at five places — so the + /// digit count is a parameter and this is only its default. + public static let defaultMaximumFractionDigits = 5 + + static func numberFormatter(maximumFractionDigits: Int) -> NumberFormatter { let f = NumberFormatter() f.numberStyle = .decimal f.locale = .current f.minimumFractionDigits = 0 - f.maximumFractionDigits = 5 + f.maximumFractionDigits = maximumFractionDigits f.usesGroupingSeparator = true return f - }() + } - static func string(forDuffs duffs: Int64) -> String { + static func string(forDuffs duffs: Int64, maximumFractionDigits: Int) -> String { let value = Decimal(duffs) / duffsPerDash - return numberFormatter.string(from: value as NSNumber) ?? "\(value)" + return numberFormatter(maximumFractionDigits: maximumFractionDigits) + .string(from: value as NSNumber) ?? "\(value)" } } @@ -60,19 +67,24 @@ public struct DashAmount: View { public var weight: Font.Weight public var dashSymbolFactor: CGFloat public var sign: DashAmountSign + /// Raise it for a figure the default would round away — a Core network fee + /// is a few hundred duffs, which is zero at five places. + public var maximumFractionDigits: Int public init( amount: Int64, fontSize: CGFloat = 13, weight: Font.Weight = .medium, dashSymbolFactor: CGFloat = 1, - sign: DashAmountSign = .negativeOnly + sign: DashAmountSign = .negativeOnly, + maximumFractionDigits: Int = DashAmountFormat.defaultMaximumFractionDigits ) { self.amount = amount self.fontSize = fontSize self.weight = weight self.dashSymbolFactor = dashSymbolFactor self.sign = sign + self.maximumFractionDigits = maximumFractionDigits } public var body: some View { @@ -85,7 +97,7 @@ public struct DashAmount: View { Text(prefix) .font(.system(size: fontSize, weight: weight)) } - Text(DashAmountFormat.string(forDuffs: abs(amount))) + Text(DashAmountFormat.string(forDuffs: abs(amount), maximumFractionDigits: maximumFractionDigits)) .font(.system(size: fontSize, weight: weight)) .lineLimit(1) DashIcon.Common.iconDashCurrency.image diff --git a/Sources/DashUIKit/Components/MenuItem.swift b/Sources/DashUIKit/Components/MenuItem.swift index 6c915e1..61ed051 100644 --- a/Sources/DashUIKit/Components/MenuItem.swift +++ b/Sources/DashUIKit/Components/MenuItem.swift @@ -29,7 +29,8 @@ public enum MenuItemAccessory { /// Dash amount with an optional pre-formatted fiat sub-line. /// The caller converts the fiat value via its own exchange infrastructure; /// the library only renders the string it receives. - case balance(dash: Int64, sign: DashAmountSign = .negativeOnly, fiat: String? = nil) + case balance(dash: Int64, sign: DashAmountSign = .negativeOnly, fiat: String? = nil, + maximumFractionDigits: Int = DashAmountFormat.defaultMaximumFractionDigits) /// A picker row: the design system's tick on the chosen one. /// /// The mark keeps its slot while unselected, so nothing in the row shifts @@ -143,9 +144,9 @@ public struct MenuItem: View { .foregroundColor(Color.dash.secondaryText) case .button(let button): button - case .balance(let dash, let sign, let fiat): + case .balance(let dash, let sign, let fiat, let maximumFractionDigits): VStack(alignment: .trailing, spacing: 1) { - DashAmount(amount: dash, sign: sign) + DashAmount(amount: dash, sign: sign, maximumFractionDigits: maximumFractionDigits) .foregroundColor(Color.dash.primaryText) if dash != 0, dash != .max, dash != .min, let fiat { From 3caefd69f29a43b9be3350df88db3509e1ea18c2 Mon Sep 17 00:00:00 2001 From: Roman <51091564+jeanpierreroma@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:48:49 +0300 Subject: [PATCH 18/19] feat(icons): add the swap-to-crypto menu icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The payments Send tab gains a "Swap to other crypto" row and had nothing to draw it with — `menu-convert` is the closest existing glyph and means something else. Imported as `menu-swap-dash-coin` rather than under the name it carries in the design export (`swap-dash-coin`), which is the only file in that folder without the group's prefix. Every asset in this group is `menu-*` and the raw value is the asset name, so the odd one out would have been visible at every call site. Light only, like the rest of the group — the export has no dark variant for it. Co-Authored-By: Claude Opus 5 --- .../DashUIKit/Foundation/Icon_DashUI.swift | 1 + .../Contents.json | 23 ++++++++++++++++++ .../menu-swap-dash-coin.png | Bin 0 -> 1203 bytes .../menu-swap-dash-coin@2x.png | Bin 0 -> 2266 bytes .../menu-swap-dash-coin@3x.png | Bin 0 -> 3390 bytes 5 files changed, 24 insertions(+) create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/Contents.json create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/menu-swap-dash-coin.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/menu-swap-dash-coin@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/menu-swap-dash-coin@3x.png diff --git a/Sources/DashUIKit/Foundation/Icon_DashUI.swift b/Sources/DashUIKit/Foundation/Icon_DashUI.swift index e14cae6..a97588f 100644 --- a/Sources/DashUIKit/Foundation/Icon_DashUI.swift +++ b/Sources/DashUIKit/Foundation/Icon_DashUI.swift @@ -201,6 +201,7 @@ public enum DashIcon { case spendingConfirmation = "menu-spending-confirmation" case staking = "menu-staking" case support = "menu-support" + case swapDashCoin = "menu-swap-dash-coin" case tools = "menu-tools" case tools2 = "menu-tools-2" case topper = "menu-topper" diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/Contents.json new file mode 100644 index 0000000..f36027f --- /dev/null +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "menu-swap-dash-coin.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "menu-swap-dash-coin@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "menu-swap-dash-coin@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/menu-swap-dash-coin.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/menu-swap-dash-coin.png new file mode 100644 index 0000000000000000000000000000000000000000..703703df0a8e671dd026f95b482b909ba4fcd086 GIT binary patch literal 1203 zcmV;k1WfyhP)F)Bq>Z#eD>6vWyYVtvKRaaHlSMPnVUKhY!{NKSMJ@Z#)0Q@`%F!9aCQUR_Nq40nm zo+eJ2M&o7+=dzpt$TARy><8xHqE&1xLM&EEy=U9o6I-jNz!%d4=GjO-N#u$U3G#|; z8_Xu%_yQ}~PPmhAk=Cstqy!U$ca|Crr(x`XR1!C0txKDqnP!yOs%OE6{4GBhkcGM0 zY^v~=jid{(6Oe3PYbi|5I|J73t-xl$S}_`KywpF!C!T`DTR#I!jDq&?l&lAl3(GVD zV(Tip5pv-c5>6o?x&U?bS~EZULS4TCJ-&|A{8 zI{01>Y4+Y2EKQB~%r9NM35Cja^?Ddf86$a_kEC-v^CD;m-woDXuYrymp_#i(L)0gl0$M2CFQVt0=&ny1wPtg4G2@uWofS4>Ee-y|QFU`&e;k@84}iOT zQj*7>DMhs=_$Szkd}>LsB;J zbmVKs;h;`@s^ij|dyrI19UgbTH%tj*oQ6#KddgP<((|2QopnMm1aTK2WSdwXb*(W>M!Luso( z!9~cW<2~=eSIk+%^T@~dcQtf>nU_H(wSDOesl9%v2(7cPMK_C_IzAmV-H0qs)}jZF zJe2B0dD!B%z3XMm)W`M*){?#|vXy(0n0I98hhVIV7W)@4Hci#J^3I+Rap2}-=hnj8 zwV{x4_#3~PfVvXMI;AgZ%|ih<5I!vGAUGY1qsl@>rckLjq5RuMXuD!r+I;4PEY->P zSHb`DoYXpOAz4lu4}|Acbl~kWra_sjou+`vGuOJ~lt4)PUa)UE^Vqp8Y6gDwL=ia4ZAwXh7Er(k_Dtx@`vCd@=$uJjJvG3cI7c2MQweI51!G93p%^A9p Re$D^@002ovPDHLkV1lScLeKyJ literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/menu-swap-dash-coin@2x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/menu-swap-dash-coin@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e64c47fabd2802e1b5e4312729547b408a907846 GIT binary patch literal 2266 zcmV<02qpK4P)VoWU2 zh;QyD{=KO9^Po|LD*D>)v(c`C@j(+-pAbygO$5p2yy! zAV6=NoyT?&+uy-Q$w}w}Vuzl%ex)|bG#CLP(n3H2@2R``0Gr2`EHZo+6XF1m#)`|{ zEtQ~41QX8BBmf4oz+npnqXY(w!6(ULn|?M)eDdqqN<&v*1)At(ACxDdL--TUpVfgI z`|(JJu+UfzGQ;D3m#=S2Zss6z3`7EY0frOJ*?1?!)r7ktYe7MJVOuiI7|)~1reG;R z#3QlwVN_jHp6!;b94m70DXw|v`v{R*GO`4+EA{HkwJ+~(xAYW*^W`jbOV++C+heYO z6N4X=C-9|8u*6`DM3`Zb`!)a@S_Rt5bs#F2<+=DPzsL#DhM$12&=|`ue68!Q zbMil}T@V{#DZr!lEZ>?b&C8&n0ZD3joo5KU{UP})E}k%768o^**FfZfmw?@pgs>1~ zOX@Y(GL4!qWu$`!>O&F8u2ZYl>C1H}%vPZ=SMeFmNON-bOE8#bjio~pB&m@UMfngI zr}lxUUc!kmNl{z&Ly249XAVJU5b4Ucm80=ZXQrrS5~eUu_z%&CqOftza^LG&6cQ00 zoBjvpE1nzake^FI#tb`I&6t9yCatKJG^I=Hlp!f6F+$|N=RjPU1+5`*e(^Yn%d^la zI0Jf~By6g5o1j2n5rK9{C*!bV!>v9~Y#PI5nR6w}S{uc0!T4(zl9*EtqX~$O{n%7{ z>_^L>V_;xH(Vo1gw9cgK0U(tQgVFn!N#cqprkyRVv5rY%wO=z(`TjB6I0bJ{;rnmzeakemlIPK}t;(m-%KSS+c4tV*u)~?cc z#YT5`L>gUUO62uK6uq=gmLv#U3YX-c^4h~ULw@}VyPdjYK-Eo`?kTfcsxlg_qBCt# zS!z;sjz-Hk_KhSKeR5p7E1LkGBhu9$o(-iPWU;SBt7)xu_B+MI(W@|Z^19u=*LS@S z==LFFCFd0tVd0Q08LHaeH>K2_Akrl5uwcN5Ff2MZo&I~lK2F4a2pe3tkL&f4^HOUt zL29g$8gIBMC)gcpAT)%~$t@`fC*lm%Go~D8u3^J&D_oHmO!J-CRtXCi6 zz0}mN03%UHjmm``8*W3ZJE$B;{?xRq9N!^gw(C@VP_M4@*Cd zTA)&2e&Ga!1=@~8RQYCTmHo&PTFHiUTk5ioGPI<%8Df2sK_lZ97G$`TgncZnjaDf- z370Mz&hnlxJ?W@x2T)0)jd}g2Y zzof>b+K~re^pGbF=qlm@B1{FamUUd1t-$(S7d@RR&?sK0dCsTqY-&XDy!W~_+&Wmu z-=)*9(*HJG4_RrFApKE#X+{ZLwSUJjp3pqPqE3DX+&GfJ%>^>?bYYC+4yZ~W=HLrQJ(hRd0jJCSO=0|R}k6Yc9 z`|X-4&DOq`h@3xxM(Jf&7eD_?5LdhjhHAosfR)o@sM7p&yUepP)F+JkDu(bjNPH&Ri` zeGMcoF8*N-B_E9EA|r2m`l}L)AS|!Pkgx^?;OY+KkK4H@3uL{0)mgZ+Bje;xPqBn< zC-zQgib8-Nj6ZinSfDyjGb}M_jY@>lWrWRxY3+_53d&fTbu2=4-#e-5&JCCcKOQR6 zQK93YgTW;IniUUHIR!|TGrn!x37`2p@3p))FFCUgJ$(jI)rmB12 zk_9~$GxKe#kN`$uoohvN*TW>N)h4}LBW7W)Eupp$)9z=3UH`TddL+ObM#^$ERGp*= z@XMUSf;Q3zHyMyQ*Hk9?DWrB$CF!K7|p{h~V_wQr*PK#2tfqF5m!d(F_^` oQ%r8G_t8fmee}^sAOAP}3;Y*_mN!P9_5c6?07*qoM6N<$g3tvq#{d8T literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/menu-swap-dash-coin@3x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Menu/menu-swap-dash-coin.imageset/menu-swap-dash-coin@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..0898e21577541ddf5c4a7b64fc5e9862b2a11dd0 GIT binary patch literal 3390 zcmV-E4Z-q>P)7&-702J~k`mR#78BS&YRNHUxIj`DuyRS^ z07WRIO@jc1C7^ME9%{Np4n<(tOwe8>UEA8U>jXKZh~(bl7O-?l9iW9|q=<_iY6^&x z9uk|5rL>6MhW=S}g`#H;9k_xatU`29vmt6h%_~yMgvjdQjkdTm&kdTm& zkdTm&knokG^sgM0u4iEjvGztTM;r7R22m*8%!9!W;k!%#hn!_k72C?#Rt7~q1I4db zpiA)CTz36_eT;KH#uPZf@R4)K;^*hwPk;(AR$+L3h5}aJ5F%wQ`at&Oc z#VgB0Cn#(hiX$`7A!}zx^Bj!hc-i@=;(9@%;}Nsgq%>n*>G)rf|zcFJWvhF2Uu5Ziu{y&*HM83c8Z#1Xlu& zh+kxaT+ju-2xa~3=y6`xuJm1Gg7X6CwZ*mHjufCn{Dmf;+;A81`$G=+1rE&m98l<% zAMOZ>su-VLdu?QvmvW)6%U@_+kM5Iw;S+Fj<%H`VU`5!9pmndOYgfX?=H5b=R;SQ| zk3sh-!LL>vpVNqK*%E9;$hy&$)LtKv*2i6F;WGF%^okUeT-)pVQP`@W*oMKS#da6k zFs7iFP+Wd;rC@I!D*n5&Wvv?MvLP!gwo_IPYYt;cwVq-DpAM5GKRO7r&j)`m)eWrvf3Cs8 zCu=bO@j6Igc8aI1jq7@PK9j1orR*f6X^kX0!O=aCi!iQT1ZCILZEsBbOdLXg7LU*b z4nCZ~DCCT_2xSpEoZH9^tB~8Ck~(&5XBrCozXp}NI*gycB_90|g`_s2$$hN9Hd3aM z6tm4PB*Av<2bDIo@V0kE-%R`X>gC24gPo*Fo9~VbQFVmz=Cz8yC1(c|L~gqZVWD#t z5AT#Xmp$XwYH+yJJc8Urh)|7=7o8u5dg*7|g9EGYjt`!<#?}p?IfU*6Wv$tHo{BE# zp`gREZqWw^WvTfbEpX$_C!luWhoCRNiLX~c24;&yMBBDU9c^)7)N2Lv`DSOx4nRou z42QR96Y}r0`JymKTC3DGN919kE70DXhK;ws2Yll$bOQNzGt{%YoG73|$IS@DwCFs9 zBs&;9X5dGD_dw7gjxV2TAD;0vip=kQ3Q-{ULP-s6afEvmfvl(M!eCOHBPgMv4z0T3 ze zxsHD&CJHl-;KNc1bOT2Y!dvI$!hfWWJMrX$P#Pb0v`hH-#$=r0V>S!sH{1?pJH82C zP-jPiQN%&6h9EIhPrd3XxUg=FcH0&nzxM-?1+>}qlU^p*QiT)g9GCrb~GJ~EC# zoipZ|A=;A@E7nA%+4j%#!mqpSGocPLY9}cYm^U8Dq0l@BU53Q2t~=dKpr6+@hY>IX zTz#~!MOzY;Hqgqoznt)mSjg%lW2kxjPnA8;0~S7UW`%)5GsT&DGFEXk`T6Hfx8Vuy z)35)@bGL$iJu2LQG5Sw?Qs1cDtwVTRer@%7#opv+&4!O_Xr4bf?7cIN`Zeb{>XD>` z7O`(|NCv0>abMIo;(p;pH~WPoQ6eODb;?M`})!FH^vSg`PUN4WrvOcr2M_4;PKY<&^-h}$!PeEjG^d&HuC6^&vK=KoAk4xMvQN;@^ ziuhTx>+u2NHa{6V$=W+Vi>;~`n5kql^2y9H)}6iIr+q$UP*y2mY&htUt`vhXQW05U zfPW7|phb`El&{j2eS{v{k#2h}P|& zjR@nQZ|W^7dFYO6^Do9HHiJqE|5W`VkENB3K9k$HC`P?0B60m#s&nArK-4-%^K&^=O^fzq?&_Bil?klW+3|BT zO}O*g)PYP^HyRf0E3}_~MN6+^4VxalQfzu}Dm+n5Br1%Fdt{s`ZQLB%H{@uOvS^pu zvu@Ozzdk?zY>%1UpwNw6j2Bc=jRUmkWh^S`cc6>VpsYPSEZ?e0gmyqhf{LT<;e9!J z5b?2(f0l*c{?Kz@ToDZij`ip+S~Lkf<&sgBKbK5YRHb5X%KUgSN1dGC96Fi$h6&pi z)08kD(h9@Q5aJg)JIK&44?!tE?3xjtllA+>QMcN2HfW-vAsPRQD8$cdBaW^JFhm`A zd9|DZMO^|zKGZt@gNLg-hZN5!#VqK1l|U(ip-3hW<~ao{a3Gl?2C7le>?3nRP1{>j zSorvl0_WR>aY0@v3M|oMO5B2Wa>X~6w({aPh9Z?!XWo}AI&t?QDAb51l(NYVaW%<9)Jh8{E}am`Hz5pzck z?a*yJaO|^rdb*kR@nyR0H|V@hC(RI4+ZaE8TmA%tJLhoJ^)bMI-V{KTIiEc(EnXO? zpcEC;ZHVXMFD7;}Cx$67**SXg3%QW4K^1fg3ye*_*qN#crjC7!%?!<~uVjK^!Y{eCGFwL|X6##prVfy1Es1?p zvv}bxkp4tUaxgWgBqoj;hr(cPP~7a)pdmEPojA2pmYK06Q*|?^>MoM=lxEm@j%+T4uza{G%asmBEWBsvps9nufN-{)YbUrL3?4hX3^GqrjZp@q3`eo zw^a|YFmtHwwci|P+l*0akA42L7Z_>St(j;EP40v7bI@-%pR+en4Vr`Dm|AQN^Stg) zA>y3P9}JrrvRO0H98GWwXkx%PT)3=92NphGD9i!+*Vg~m2@W8BSUm^d?#ep$7LHS1i9ul0bWWqhwV=67*(7Omk_~y|$d$Wk0d0!R)kb*?U!sqyd#nr%Ck-N|$yy9n| zS1@(lwO!Zxeao~r6vj!4O!%)7c4}p@>o?&Wd&pdJ48NqN+yaBNxRe^>+IJ)}lUQJ|^hnM~Dbr{TN1%^TB0@28Cjy(u2~CjLDY#%; zRkxE*#PCs@blz>{jaV|1@hLQ`JoFNVqdXi_>KInzl^2>2tA`n-j@R|@==fwI!3{79 z-L!CW5va;JhW$sJ%N>Uk+Jsj0VlX9b-NK5Y=rMgDG~`l1RWXA_!DFy;5|^o1=h5L( zMo-U2aS>j0CbS8q>x>ofA3h)!G- Date: Tue, 25 Aug 2026 15:40:17 +0300 Subject: [PATCH 19/19] feat(icons): give copy-outline a dark appearance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The glyph shipped in one appearance, so on the dark theme it stayed near-black and disappeared into the surface it was drawn on. Derived from the light asset rather than drawn: the artwork is a single colour (0x0A0B0D) with anti-aliased alpha, and that colour is `PrimaryText`'s light value. The dark variant is the same shape carrying that token's dark value — white at 90% — so only RGB and the alpha multiplier change. Shape, edges and transparent pixels are identical to the original. Replace the files if design would rather draw it; the imageset is wired for two appearances now either way. Co-Authored-By: Claude Opus 5 --- .../Icons/copy-outline.imageset/Contents.json | 61 ++++++++++++++---- .../copy-outline-dark.png | Bin 0 -> 246 bytes .../copy-outline-dark@2x.png | Bin 0 -> 368 bytes .../copy-outline-dark@3x.png | Bin 0 -> 497 bytes 4 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Icons/copy-outline.imageset/copy-outline-dark.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Icons/copy-outline.imageset/copy-outline-dark@2x.png create mode 100644 Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Icons/copy-outline.imageset/copy-outline-dark@3x.png diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Icons/copy-outline.imageset/Contents.json b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Icons/copy-outline.imageset/Contents.json index 9204d47..e5e004d 100644 --- a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Icons/copy-outline.imageset/Contents.json +++ b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Icons/copy-outline.imageset/Contents.json @@ -1,23 +1,56 @@ { - "images" : [ + "images": [ { - "filename" : "copy-outline.png", - "idiom" : "universal", - "scale" : "1x" + "filename": "copy-outline.png", + "idiom": "universal", + "scale": "1x" }, { - "filename" : "copy-outline@2x.png", - "idiom" : "universal", - "scale" : "2x" + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "copy-outline-dark.png", + "idiom": "universal", + "scale": "1x" }, { - "filename" : "copy-outline@3x.png", - "idiom" : "universal", - "scale" : "3x" + "filename": "copy-outline@2x.png", + "idiom": "universal", + "scale": "2x" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "copy-outline-dark@2x.png", + "idiom": "universal", + "scale": "2x" + }, + { + "filename": "copy-outline@3x.png", + "idiom": "universal", + "scale": "3x" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "filename": "copy-outline-dark@3x.png", + "idiom": "universal", + "scale": "3x" } ], - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "author": "xcode", + "version": 1 } -} +} \ No newline at end of file diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Icons/copy-outline.imageset/copy-outline-dark.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Icons/copy-outline.imageset/copy-outline-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..55aeaecad7849335c434eba184032ed6a34a0f76 GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjdp%toLn?0FopzVERe`5H*U+NRy^}-iq0)NA-Kt3y5Du>i{k-n z>NFJ=RcL-X!g-BtqRDm*E}#D)i9!v`2Qpc`nRYerXWk=b!#II${*4CqgZdw&^&CWO z{3_nwH~lQj;LgYscTS5z;?@?Qs0P8y4?-FCvf47NXSHDce_Z~-_DQl=oA{6DuKR4? u9sWb_$B8?xo__?b*6?P{o|XU4A&2MFf=LG-ZdnZU2!p4qpUXO@geCy9ieQ`o literal 0 HcmV?d00001 diff --git a/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Icons/copy-outline.imageset/copy-outline-dark@2x.png b/Sources/DashUIKit/Resources/Media.xcassets/Icons & Illustrations/Icons/copy-outline.imageset/copy-outline-dark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..541907d3e8270b1fcab6c62a5215bc0a30300817 GIT binary patch literal 368 zcmV-$0gwKPP)O801{Qn}7*igbCc>*6=XFgfyB6@{s1*q(3cu1$ne|otT)| zf7r4t+Pm>SX81dN+ut#9<$d!0E#zw~RWs{z!O3sF#?&=3Pbcp(w~!6rQV4|vpxee2 zh-pg4JQ}D+4HQ8**b0zLIY9gedjAg*faQ4ReelipwAqS%>o<< z)RP+ky&}xCl4=@Y=aS}FtpHhx-8(u9fI9~e00Foj-$wBVjtwveZ9|?_?9A9N5*yZr zM3j`TOC{w+#omf~n_1Y`ZNoR&R=KbeJfETtt{y|?7BcYXCowUxcI*coIo^ps<4qv| O0000A~45~}6H`sr$d zo?fba(xvS^%Bi?9~G3^ftW;KvzCEYXYRs#0J#>@~lPP^{6U99!O6DI)>yuEvOQZ z8|e1VF-o<7xRWLz0wN#+Die?qu4lW48>Inh@KcRJ8W7UCSRdtFOe!i#R0qUO{j$&I zBS$5%Z+G;bv2lLR^F$@9S8hO?KgQ}BG`$Hah4J73LBfUe3sNg|%Qdeba2%-46+cF} nE3Mu0`DYSEQ4~c{)Cqk74N|cD`(Z_900000NkvXXu0mjfIX%v1 literal 0 HcmV?d00001