Repository navigation
feat: add remaining components (EnterAmount, Converter, Transaction, TopIntro) #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e85188a
feat: add remaining components (EnterAmount, CoinSelector, Converter,…
romchornyi bf67b3c
docs: add component catalog (docs/) and CLAUDE.md dev guide
romchornyi f36d872
fix: address SwapAmountView review
romchornyi 72f56c2
feat(TransactionView): add trailingStatusText
jeanpierreroma 8ba5629
test(NumericKeyboard): cover locale decimal separators
jeanpierreroma 5ba40f9
docs(dashuikit): refresh component documentation
jeanpierreroma File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # DashUIKit context | ||
|
|
||
| SwiftUI design-system library for Dash Core Group apps. Pure UI components, design | ||
| tokens (colors / typography), and small layout utilities. No business logic, no | ||
| networking — every component is dumb and driven entirely by the values and closures | ||
| the host passes in. | ||
|
|
||
| - Swift tools: **6.3** | ||
| - Single library target **`DashUIKit`** (`import DashUIKit`) | ||
| - Assets live in `Sources/DashUIKit/Resources/Media.xcassets` and are reached via | ||
| `Bundle.module` / `Bundle.dashUIKit`. | ||
|
|
||
| ## ⚠️ Hard constraint: must support iOS 14 | ||
|
|
||
| **This library must remain available on iOS 14.** The package deployment target is | ||
| `iOS(.v14)` (see `Package.swift`) and this is non-negotiable — every public component | ||
| must be usable from an iOS 14 host. | ||
|
|
||
| When writing or changing code: | ||
|
|
||
| - Annotate public API with `@available(iOS 14, macOS 11, *)` (or `macOS 10.15` for | ||
| Foundation-level token types). **Do not** make a component's *minimum* availability | ||
| higher than iOS 14. | ||
| - If you need a SwiftUI API introduced after iOS 14 (e.g. `@FocusState` 15, | ||
| `presentationDetents` 16, `#Preview` macro 17), **gate it with `if #available(...)` | ||
| and provide an iOS 14 fallback path** — never raise the whole component's floor. | ||
| See `SearchBar` (15+ focus path + 14 legacy path), `BottomSheet` / `selfSizingSheet` | ||
| (16+ detents, no-op below), and `AddressFieldView` (17 axis field + older fallback). | ||
| - It is fine for `#Preview`-only code to require iOS 17 — previews are `#if DEBUG` and | ||
| never ship — but the component itself must still build and run on iOS 14. | ||
|
|
||
| ## Where things are | ||
|
|
||
| ``` | ||
| Sources/DashUIKit/ | ||
| Button/ DashButton (+ DashButtonSize / DashButtonStyle) | ||
| 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) | ||
| Illustrations/ Loading / success / error illustrations | ||
| Table List/ List1View (label/value row) | ||
| ViewModifiers/ MenuViewModifier (card chrome) | ||
| Foundation/ Color / Font / DashTextStyle / Image / line-height / Bundle | ||
| Resources/ Media.xcassets (colors, icons, illustrations) | ||
| ``` | ||
|
|
||
| ## Conventions (match these when adding code) | ||
|
|
||
| - **One public type per file**, named after the file. Heavy `#Preview` / `PreviewProvider` | ||
| coverage under `#if DEBUG` is expected — add previews for every state. | ||
| - **Theming is asset-driven.** Never hardcode a `Color(...)`/hex. Use `Color.dash.<token>` | ||
| (see `Foundation/Color+DashUI.swift`) — each maps to a named color set in the asset | ||
| catalog with light/dark variants. Add a new token there, not inline. | ||
| - **Typography:** use `.dashFont(.subhead)` (sets font **and** design line height together) | ||
| or `Font.dash.subhead` when you only need the font. Tokens defined in `DashTextStyle.swift`. | ||
| - **Icons:** pass a `DashIconSource` (`.system` / `.custom(name, bundle:)` / `.uiImage`) and | ||
| render with `Image(dash: source)`. Custom assets resolve from `.dashUIKit`/`.module`. | ||
| - **Availability:** annotate public API with `@available(iOS 14, macOS 11, *)` (or higher only | ||
| when a newer SwiftUI feature requires it, with an iOS 14 fallback path — see `SearchBar`, | ||
| `BottomSheet`). | ||
| - **UIKit-only files** are wrapped in `#if canImport(UIKit)` (e.g. `SearchBar`, `Toast`, | ||
| `AddressFieldView`, `DashSwitch`). | ||
| - **Localization:** user-facing strings use `NSLocalizedString(_, bundle: .module, comment:)`. | ||
| - Components are **stateless/value-driven** — state (`@Binding`) and callbacks live with the | ||
| host. Don't add view models or persistence here. | ||
|
|
||
| ## Build / preview | ||
|
|
||
| - This is a plain SwiftPM library — `swift build` compiles it; there is no app target. | ||
| - Develop visually with Xcode SwiftUI **#Previews** (open a file, use the canvas). Previews | ||
| require iOS 17 for the `#Preview` macro; older `PreviewProvider` previews work back further. | ||
|
|
||
| ## Component catalog | ||
|
|
||
| Full per-component reference (API, props, behavior, usage) lives in **`docs/`**. Start at | ||
| [`docs/README.md`](docs/README.md) — it indexes every component so you can check whether a | ||
| given UI element already exists before building a new one. The public surface is also | ||
| summarized in [`README.md`](README.md). | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| // | ||
| // Created by 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 | ||
|
|
||
| @available(iOS 14, macOS 11, *) | ||
| public enum CoinSelectorTrailing { | ||
| case price(String) | ||
| case halted | ||
| } | ||
|
|
||
| private enum CoinSelectorLayout { | ||
| static let spacing: CGFloat = 16 | ||
| static let textSpacing: CGFloat = 1 | ||
| static let padding: CGFloat = 10 | ||
| static let badgeHPadding: CGFloat = 8 | ||
| static let badgeVPadding: CGFloat = 2 | ||
| static let badgeCornerRadius: CGFloat = 6 | ||
| } | ||
|
|
||
| @available(iOS 14, macOS 11, *) | ||
| public struct CoinSelector<Icon: View>: View { | ||
|
|
||
| private let name: String | ||
| private let code: String | ||
| private let network: String? | ||
| private let trailing: CoinSelectorTrailing? | ||
| private let icon: Icon | ||
|
|
||
| public init( | ||
| name: String, | ||
| code: String, | ||
| network: String? = nil, | ||
| trailing: CoinSelectorTrailing? = nil, | ||
| @ViewBuilder icon: () -> Icon | ||
| ) { | ||
| self.name = name | ||
| self.code = code | ||
| self.network = network | ||
| self.trailing = trailing | ||
| self.icon = icon() | ||
| } | ||
|
|
||
| private var isHalted: Bool { | ||
| if case .halted = trailing { return true } | ||
| return false | ||
| } | ||
|
|
||
| public var body: some View { | ||
| HStack(spacing: CoinSelectorLayout.spacing) { | ||
| icon | ||
| info | ||
| trailingView | ||
| } | ||
| .padding(CoinSelectorLayout.padding) | ||
| .opacity(isHalted ? 0.5 : 1.0) | ||
| .contentShape(Rectangle()) | ||
| } | ||
|
|
||
| private var info: some View { | ||
| VStack(alignment: .leading, spacing: CoinSelectorLayout.textSpacing) { | ||
| Text(name) | ||
| .dashFont(.subheadMedium) | ||
| .foregroundColor(Color.dash.primaryText) | ||
| .lineLimit(1) | ||
| .truncationMode(.tail) | ||
| Text(code) | ||
| .dashFont(.footnote) | ||
| .foregroundColor(Color.dash.tertiaryText) | ||
| .lineLimit(1) | ||
| } | ||
| .frame(maxWidth: .infinity, alignment: .leading) | ||
| } | ||
|
|
||
| @ViewBuilder | ||
| private var trailingView: some View { | ||
| if case .halted = trailing { | ||
| haltedLabel | ||
| } else { | ||
| VStack(alignment: .trailing, spacing: 0) { | ||
| if case .price(let value) = trailing { | ||
| Text(value) | ||
| .dashFont(.caption1) | ||
| .foregroundColor(Color.dash.primaryText) | ||
| .lineLimit(1) | ||
| } | ||
| if let network { | ||
| Text(network) | ||
| .dashFont(.caption1) | ||
| .foregroundColor(Color.dash.tertiaryText) | ||
| .lineLimit(1) | ||
| .fixedSize() | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private var haltedLabel: some View { | ||
| Text(NSLocalizedString("halted", bundle: .module, comment: "DashUIKit")) | ||
| .dashFont(.caption1) | ||
| .foregroundColor(Color.dash.tertiaryText) | ||
| .padding(.horizontal, CoinSelectorLayout.badgeHPadding) | ||
| .padding(.vertical, CoinSelectorLayout.badgeVPadding) | ||
| .background(Color.dash.black1000Alpha8) | ||
| .clipShape(RoundedRectangle(cornerRadius: CoinSelectorLayout.badgeCornerRadius, style: .continuous)) | ||
| } | ||
| } | ||
|
|
||
| #if DEBUG | ||
|
|
||
| @available(iOS 14, macOS 11, *) | ||
| struct CoinSelector_Previews: PreviewProvider { | ||
|
|
||
| static var iconPlaceholder: some View { | ||
| RoundedRectangle(cornerRadius: 6) | ||
| .fill(Color.dash.blue) | ||
| .frame(width: 30, height: 30) | ||
| } | ||
|
|
||
| static var previews: some View { | ||
| VStack(spacing: 0) { | ||
| CoinSelector(name: "Rune", code: "RUNE", network: "Maya", trailing: .price("$0.40")) { | ||
| iconPlaceholder | ||
| } | ||
| CoinSelector(name: "Ethereum", code: "ETH", network: "NEAR", trailing: .price("$3,200.00")) { | ||
| iconPlaceholder | ||
| } | ||
| CoinSelector(name: "USD Coin", code: "USDC", network: "Multiple", trailing: .price("$1.00")) { | ||
| iconPlaceholder | ||
| } | ||
| CoinSelector(name: "Tether", code: "USDT", trailing: .halted) { | ||
| iconPlaceholder | ||
| } | ||
| CoinSelector(name: "Bitcoin", code: "BTC") { | ||
| iconPlaceholder | ||
| } | ||
| } | ||
| .previewLayout(.sizeThatFits) | ||
| } | ||
| } | ||
|
|
||
| #endif |
63 changes: 63 additions & 0 deletions
63
Sources/DashUIKit/Components/ConverterCard/ConverterArrowBadge.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| // | ||
| // 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: - ConverterArrowBadge | ||
|
|
||
| /// The arrow badge centered on the seam between the two `ConverterCard` rows. | ||
| /// | ||
| /// - `onSwap == nil` → static `arrow-down` icon, non-interactive. | ||
| /// - `onSwap != nil` → tappable `diagonal-up-down` button that rotates 180° on each tap and fires | ||
| /// the swap action. | ||
| @available(iOS 14, macOS 11, *) | ||
| struct ConverterArrowBadge: View { | ||
| let onSwap: (() -> Void)? | ||
| @State private var rotation: Double = 0 | ||
|
|
||
| var body: some View { | ||
| Group { | ||
| if let onSwap { | ||
| Button { | ||
| withAnimation(.spring(response: 0.35, dampingFraction: 0.82)) { rotation += 180 } | ||
| onSwap() | ||
| } label: { | ||
| badge(iconName: "diagonal-up-down", iconRotation: rotation) | ||
| } | ||
| .buttonStyle(.plain) | ||
| } else { | ||
| badge(iconName: "arrow-down", iconRotation: 0) | ||
| } | ||
| } | ||
| .frame(height: 35) | ||
| .padding(.horizontal, 10) | ||
| } | ||
|
|
||
| private func badge(iconName: String, iconRotation: Double) -> some View { | ||
| RoundedRectangle(cornerRadius: 10, style: .continuous) | ||
| .fill(Color.dash.secondaryBackground) | ||
| .frame(width: 35, height: 35) | ||
| .overlay( | ||
| RoundedRectangle(cornerRadius: 10, style: .continuous) | ||
| .stroke(Color.dash.primaryBackground, lineWidth: 5) | ||
| ) | ||
| .overlay( | ||
| Image(dash: .custom(iconName, bundle: .dashUIKit)) | ||
| .rotationEffect(.degrees(iconRotation)) | ||
| ) | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.