@astryxdesign/native 0.7.5-canary.577735c → 0.7.5-canary.577e8ca

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +1 -1
  2. package/apple/README.md +72 -26
  3. package/apple/Sources/Astryx/Banner/{AstryxBanner.swift → Banner.swift} +4 -4
  4. package/apple/Sources/Astryx/Button/{AstryxButton.swift → Button.swift} +5 -5
  5. package/apple/Sources/Astryx/Card/{AstryxCard.swift → Card.swift} +1 -1
  6. package/apple/Sources/Astryx/Exports.swift +8 -0
  7. package/apple/Sources/Astryx/Heading/{AstryxHeading.swift → Heading.swift} +2 -2
  8. package/apple/Sources/Astryx/Text/{AstryxText.swift → Text.swift} +2 -2
  9. package/apple/Sources/Astryx/TextInput/{AstryxTextInput.swift → TextInput.swift} +4 -4
  10. package/apple/Sources/Astryx/Theme/AstryxIcons.swift +1 -1
  11. package/apple/Sources/Astryx/Theme/Generated/ButterTheme.swift +493 -0
  12. package/apple/Sources/Astryx/Theme/Generated/ChocolateTheme.swift +493 -0
  13. package/apple/Sources/Astryx/Theme/Generated/DefaultTheme.swift +489 -478
  14. package/apple/Sources/Astryx/Theme/Generated/GothicTheme.swift +493 -0
  15. package/apple/Sources/Astryx/Theme/Generated/MatchaTheme.swift +493 -0
  16. package/apple/Sources/Astryx/Theme/Generated/NeutralTheme.swift +493 -0
  17. package/apple/Sources/Astryx/Theme/Generated/StoneTheme.swift +493 -0
  18. package/apple/Sources/Astryx/Theme/Generated/Tokens.swift +1260 -0
  19. package/apple/Sources/Astryx/Theme/Generated/Y2kTheme.swift +493 -0
  20. package/apple/Sources/Astryx/Theme/{AstryxTheme.swift → Theme.swift} +88 -44
  21. package/apple/Sources/AstryxNative/Generated/Button.swift +14 -2
  22. package/apple/Tests/AstryxTests/BannerTests.swift +10 -10
  23. package/apple/Tests/AstryxTests/ButtonTests.swift +17 -17
  24. package/apple/Tests/AstryxTests/CardTests.swift +1 -1
  25. package/apple/Tests/AstryxTests/EnvironmentTests.swift +58 -7
  26. package/apple/Tests/AstryxTests/HeadingTests.swift +14 -14
  27. package/apple/Tests/AstryxTests/NamesTests.swift +28 -0
  28. package/apple/Tests/AstryxTests/Pixels.swift +7 -1
  29. package/apple/Tests/AstryxTests/TextInputTests.swift +5 -5
  30. package/apple/Tests/AstryxTests/TextTests.swift +2 -2
  31. package/apple/Tests/AstryxTests/ThemeTests.swift +22 -16
  32. package/apple/Tests/AstryxTests/TwoThemeTests.swift +15 -15
  33. package/dist/components/Button.d.ts.map +1 -1
  34. package/dist/components/Button.js +1 -0
  35. package/dist/contract/fromComponentDoc.d.ts +8 -0
  36. package/dist/contract/fromComponentDoc.d.ts.map +1 -1
  37. package/dist/contract/fromComponentDoc.js +15 -0
  38. package/dist/contract/types.d.ts +5 -0
  39. package/dist/contract/types.d.ts.map +1 -1
  40. package/dist/types/android.d.ts +1 -1
  41. package/dist/types/core.d.ts +1 -0
  42. package/dist/types/ios.d.ts +1 -1
  43. package/dist/types/macos.d.ts +1 -1
  44. package/dist/types/shared.d.ts +1 -1
  45. package/dist/types/windows.d.ts +1 -1
  46. package/package.json +3 -3
  47. package/src/components/Button.ts +1 -0
  48. package/src/contract/contracts.test.ts +10 -0
  49. package/src/contract/fromComponentDoc.test.ts +29 -0
  50. package/src/contract/fromComponentDoc.ts +28 -0
  51. package/src/contract/types.ts +7 -0
  52. package/src/theme/swiftIOSTheme.test.ts +196 -22
package/README.md CHANGED
@@ -132,7 +132,7 @@ pnpm -F @astryxdesign/native typecheck # source and tests
132
132
  pnpm -F @astryxdesign/native build # dist/, including platform types
133
133
  pnpm -F @astryxdesign/native typecheck:types # every platform's types, after build
134
134
  pnpm -F @astryxdesign/native generate:apple # regenerate apple/Sources/.../Generated
135
- pnpm -F @astryxdesign/native generate:swift-ios # regenerate the Astryx product's default theme and strings
135
+ pnpm -F @astryxdesign/native generate:swift-ios # regenerate the Astryx product's typed tokens, every published theme, and strings
136
136
  pnpm vitest run --project node packages/native
137
137
  ```
138
138
 
package/apple/README.md CHANGED
@@ -23,15 +23,15 @@ apple/
23
23
  ├── Sources/
24
24
  │ ├── Astryx/ # SwiftUI views (module Astryx)
25
25
  │ │ ├── Theme/ # tokens, the theme environment, Dynamic Type
26
- │ │ │ └── Generated/ # core's default theme, generated; do not edit
26
+ │ │ │ └── Generated/ # typed tokens and every published theme, generated; do not edit
27
27
  │ │ ├── Resources/ # core's messages per locale, generated; do not edit
28
28
  │ │ ├── Localization/
29
- │ │ ├── Text/ # AstryxText
30
- │ │ ├── Heading/ # AstryxHeading
31
- │ │ ├── Button/ # AstryxButton
32
- │ │ ├── TextInput/ # AstryxTextInput
33
- │ │ ├── Card/ # AstryxCard
34
- │ │ └── Banner/ # AstryxBanner
29
+ │ │ ├── Text/ # Text
30
+ │ │ ├── Heading/ # Heading
31
+ │ │ ├── Button/ # Button
32
+ │ │ ├── TextInput/ # TextInput
33
+ │ │ ├── Card/ # Card
34
+ │ │ └── Banner/ # Banner
35
35
  │ └── AstryxNative/ # contracts, wire, logic host (module AstryxNative)
36
36
  │ └── Generated/ # generated; do not edit
37
37
  ├── Tests/
@@ -44,17 +44,21 @@ apple/
44
44
  ```
45
45
 
46
46
  - **`Sources/Astryx/<Name>/`** holds one folder per component, named as in
47
- `packages/core/src/<Name>/`. A component's parameters use core's prop names
47
+ `packages/core/src/<Name>/`. Each view has core's component name in the
48
+ `Astryx` module (`Button`, `Text`), which re-exports SwiftUI and the
49
+ generated contracts, so an app file writes `import Astryx` alone. In a file
50
+ that also imports SwiftUI, write `Astryx.Button` and `SwiftUI.Button`. A
51
+ component's parameters use core's prop names
48
52
  and values (`variant: .primary`, `size: .lg`, `isLoading`), so code reads
49
53
  the same across web and native, with six exceptions, the interim shape
50
54
  AST-093 FR5 exempts until the components move onto the generated contracts:
51
- - `AstryxButton` takes `action` where core has `onClick`.
52
- - `AstryxButton` takes `fillsWidth` where core has `width="100%"`.
53
- - `AstryxButton` takes `icon` as a shared icon name (`"search"`), which the
55
+ - `Button` takes `action` where core has `onClick`.
56
+ - `Button` takes `fillsWidth` where core has `width="100%"`.
57
+ - `Button` takes `icon` as a shared icon name (`"search"`), which the
54
58
  generated `ButtonProps` does not carry yet.
55
- - `AstryxTextInput` takes a `Binding` where core has `value` and `onChange`.
56
- - `AstryxHeading`'s `level` is optional where core requires it.
57
- - `AstryxCard` takes `padding` as a whole spacing step, so it cannot spell
59
+ - `TextInput` takes a `Binding` where core has `value` and `onChange`.
60
+ - `Heading`'s `level` is optional where core requires it.
61
+ - `Card` takes `padding` as a whole spacing step, so it cannot spell
58
62
  core's `0.5` and `1.5` steps.
59
63
  - **`Templates/`** holds one folder per page template, named after the
60
64
  `astryx template` id. Templates are clone-only, as on the web: you copy a
@@ -74,7 +78,6 @@ app.
74
78
 
75
79
  ```swift
76
80
  import Astryx
77
- import SwiftUI
78
81
 
79
82
  @main
80
83
  struct DemoApp: App {
@@ -93,24 +96,66 @@ size.
93
96
  ## Theme tokens
94
97
 
95
98
  Components read every color, size, and font from the `astryx` environment
96
- value, never from a constant of their own. Token names are core's, without
97
- the leading `--` (`color-accent`, `spacing-4`, `size-element-md`). Text sizes
98
- grow with Dynamic Type.
99
+ value, never from a constant of their own. `astryx.tokens` holds the theme's
100
+ tokens in the current mode as typed members (`tokens.color.textPrimary`,
101
+ `tokens.space.s4`, `tokens.font.bodySize`), so a misspelled token fails to
102
+ compile. Text sizes grow with Dynamic Type. The components still read some
103
+ tokens by core's name (`astryx.color("color-accent")`); moving them onto the
104
+ typed members comes next.
99
105
 
100
106
  Font families come from the theme's `font-family-*` stacks: the first family
101
107
  the device has wins, and a system family such as `-apple-system` or
102
108
  `sans-serif` is the system font. Icons use core's shared icon names
103
109
  (`success`, `warning`, …), drawn as an SF Symbol by default; a theme replaces
104
- one with `AstryxTheme(…, icons: ["success": "checkmark.seal.fill"])`.
110
+ one with `Theme(…, icons: ["success": "checkmark.seal.fill"])`.
105
111
 
106
- `Sources/Astryx/Theme/Generated/DefaultTheme.swift` is core's default theme
107
- run through core's own resolver, light and dark. Regenerate it after a core
108
- token change:
112
+ `Sources/Astryx/Theme/Generated/` is written by the Swift theme generator
113
+ (`../specs/swift/library/theme-generator.md`) from core's own resolver, light
114
+ and dark:
115
+
116
+ - `Tokens.swift`: the typed token surface, from core's token names.
117
+ - One `<Name>Theme.swift` per theme, declaring `Theme.<name>`: core's
118
+ defaults (`.default`) and every published theme package (`.butter`,
119
+ `.chocolate`, `.gothic`, `.matcha`, `.neutral`, `.stone`, `.y2k`).
120
+
121
+ ```swift
122
+ LoginTemplate().astryxTheme(.neutral)
123
+ ```
124
+
125
+ Regenerate after a theme or core token change:
109
126
 
110
127
  ```sh
111
128
  pnpm -F @astryxdesign/native generate:swift-ios
112
129
  ```
113
130
 
131
+ A product generates its own theme the same way, from its theme's module and
132
+ export:
133
+
134
+ ```sh
135
+ node packages/native/scripts/swift-ios-theme.mjs --theme ./src/acmeTheme.ts#acmeTheme --out ./Sources/Theme
136
+ ```
137
+
138
+ A theme's local tokens are not generated yet; the generator names each one it
139
+ leaves out.
140
+
141
+ **Fonts.** A theme's font families are carried as data. Astryx never bundles
142
+ a font: a component walks the theme's family list in order and uses the first
143
+ family the app has registered; a system family in the list (such as
144
+ `-apple-system` or `sans-serif`) is the system font, and a list with no
145
+ registered family falls back to the system font. To draw a theme's brand face, bundle and register it
146
+ in the app. Faces the published themes name (body, heading, code):
147
+
148
+ | Theme | Faces to bundle |
149
+ | ------------ | ------------------------------------------ |
150
+ | `.default` | none (system fonts) |
151
+ | `.butter` | Outfit, JetBrains Mono |
152
+ | `.chocolate` | Albert Sans, Fraunces, JetBrains Mono |
153
+ | `.gothic` | Fustat, JetBrains Mono |
154
+ | `.matcha` | DM Sans, Playwrite US Trad, JetBrains Mono |
155
+ | `.neutral` | Figtree |
156
+ | `.stone` | Figtree, Montserrat, JetBrains Mono |
157
+ | `.y2k` | Poppins, JetBrains Mono |
158
+
114
159
  The same command writes
115
160
  `Sources/Astryx/Resources/<locale>.lproj/Localizable.strings` from core's
116
161
  locale catalogs, so words the components speak to assistive technology, such
@@ -131,8 +176,9 @@ Both packages also declare macOS 14 so they build and test on a Mac without a
131
176
  simulator; iOS 17 is the target. Open `Templates/Package.swift` in Xcode to
132
177
  run `LoginTemplate` in an iOS simulator through its preview.
133
178
 
134
- To add a component, create `Sources/Astryx/<Name>/Astryx<Name>.swift` with
135
- core's prop names, read its tokens from `astryx`, and add tests under
179
+ To add a component, create `Sources/Astryx/<Name>/<Name>.swift` declaring
180
+ `<Name>` with core's prop names, writing `SwiftUI.<Name>` wherever it means the
181
+ system type, read its tokens from `astryx`, and add tests under
136
182
  `Tests/AstryxTests/`. To add a template, create
137
183
  `Templates/<Name>/<Name>Template.swift` from the output of
138
184
  `astryx template <id>`, with tests under `Templates/Tests/`.
@@ -142,5 +188,5 @@ core's prop names, read its tokens from `astryx`, and add tests under
142
188
  - The views do not use the generated contract types in `AstryxNative` yet.
143
189
  Moving them onto those types is how a core prop change reaches Swift.
144
190
  - Pixel and behavior parity with the web components has not been measured.
145
- - Only the default theme is generated; named themes, Increase Contrast, and
146
- platform theme entries (`defineNativeTheme`) are not wired in.
191
+ - Increase Contrast, theme-local tokens, and platform theme entries
192
+ (`defineNativeTheme`, reserved as `Theme.native`) are not wired in.
@@ -35,7 +35,7 @@ public enum AstryxBannerStatus: String, CaseIterable, Sendable {
35
35
  /// A message about the page or a section of it. The status sets the color
36
36
  /// and icon only; the title must say what happened, because the icon is
37
37
  /// decorative to VoiceOver.
38
- public struct AstryxBanner: View {
38
+ public struct Banner: View {
39
39
  let status: AstryxBannerStatus
40
40
  let title: String
41
41
  let description: String?
@@ -57,9 +57,9 @@ public struct AstryxBanner: View {
57
57
  .accessibilityHidden(true)
58
58
  }
59
59
  VStack(alignment: .leading, spacing: astryx.space(1)) {
60
- AstryxText(title, weight: .semibold)
60
+ Text(title, weight: .semibold)
61
61
  if let description {
62
- AstryxText(description, color: .secondary)
62
+ Text(description, color: .secondary)
63
63
  }
64
64
  }
65
65
  .frame(maxWidth: .infinity, alignment: .leading)
@@ -70,7 +70,7 @@ public struct AstryxBanner: View {
70
70
  .fill(astryx.color(status.groundToken))
71
71
  )
72
72
  .accessibilityElement(children: .ignore)
73
- .accessibilityLabel(Text(accessibilityText))
73
+ .accessibilityLabel(SwiftUI.Text(accessibilityText))
74
74
  }
75
75
 
76
76
  /// What VoiceOver reads: the title, then the description.
@@ -29,7 +29,7 @@ public enum AstryxButtonSize: String, CaseIterable, Sendable {
29
29
 
30
30
  /// A labeled action, painted with the theme's tokens. The tap area reaches
31
31
  /// the platform's 44-by-44-point minimum even when the drawn button is smaller.
32
- public struct AstryxButton: View {
32
+ public struct Button: View {
33
33
  let label: String
34
34
  let variant: AstryxButtonVariant
35
35
  let size: AstryxButtonSize
@@ -66,14 +66,14 @@ public struct AstryxButton: View {
66
66
  }
67
67
 
68
68
  public var body: some View {
69
- Button(action: activate) {
69
+ SwiftUI.Button(action: activate) {
70
70
  // The label keeps its place while loading so the width holds; the
71
71
  // spinner draws over it.
72
72
  HStack(spacing: astryx.space(2)) {
73
73
  if let icon, let symbol = astryx.icon(icon) {
74
74
  Image(systemName: symbol)
75
75
  }
76
- Text(label).lineLimit(1)
76
+ SwiftUI.Text(label).lineLimit(1)
77
77
  }
78
78
  .opacity(isLoading ? 0 : 1)
79
79
  .overlay {
@@ -94,9 +94,9 @@ public struct AstryxButton: View {
94
94
  // As core's Button: a loading button stays undimmed and focusable, keeps
95
95
  // its label, and says Loading; taps do nothing until it finishes.
96
96
  .disabled(isDisabled)
97
- .accessibilityLabel(Text(accessibilityText))
97
+ .accessibilityLabel(SwiftUI.Text(accessibilityText))
98
98
  .accessibilityAddTraits(Self.accessibilityTraits)
99
- .accessibilityValue(Text(loadingValue ?? ""))
99
+ .accessibilityValue(SwiftUI.Text(loadingValue ?? ""))
100
100
  .onChange(of: isLoading) { _, loading in
101
101
  if loading { AccessibilityNotification.Announcement(AstryxStrings.buttonLoading).post() }
102
102
  }
@@ -4,7 +4,7 @@ import SwiftUI
4
4
 
5
5
  /// A card: one independent item on the theme's card ground, with a hairline
6
6
  /// border and the container radius.
7
- public struct AstryxCard<Content: View>: View {
7
+ public struct Card<Content: View>: View {
8
8
  let padding: Int
9
9
  let content: Content
10
10
  @Environment(\.astryx) private var astryx
@@ -0,0 +1,8 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ // `import Astryx` is the only import an app file needs: it brings SwiftUI and
4
+ // the generated contracts with it. In a file that imports only Astryx, a name
5
+ // Astryx shares with SwiftUI (`Button`, `Text`) means Astryx's view, and
6
+ // `SwiftUI.Button` names the system one (Swift component API spec, A1, A1a).
7
+ @_exported import AstryxNative
8
+ @_exported import SwiftUI
@@ -5,7 +5,7 @@ import SwiftUI
5
5
 
6
6
  /// A heading at levels 1 through 6, painted with the theme's heading tokens
7
7
  /// and exposed to VoiceOver as a heading.
8
- public struct AstryxHeading: View {
8
+ public struct Heading: View {
9
9
  let content: String
10
10
  let level: Int
11
11
  @Environment(\.astryx) private var astryx
@@ -16,7 +16,7 @@ public struct AstryxHeading: View {
16
16
  }
17
17
 
18
18
  public var body: some View {
19
- Text(content)
19
+ SwiftUI.Text(content)
20
20
  .font(
21
21
  astryx.font(
22
22
  size: "text-heading-\(level)-size",
@@ -51,7 +51,7 @@ public enum AstryxTextSize: String, CaseIterable, Sendable {
51
51
  }
52
52
 
53
53
  /// A run of text in a semantic type, painted with the theme's type tokens.
54
- public struct AstryxText: View {
54
+ public struct Text: View {
55
55
  let content: String
56
56
  let type: AstryxTextType
57
57
  let color: AstryxTextColor?
@@ -76,7 +76,7 @@ public struct AstryxText: View {
76
76
  public var body: some View {
77
77
  let sizeToken = size?.token ?? "\(type.token)-size"
78
78
  let weightToken = weight?.token ?? "\(type.token)-weight"
79
- Text(content)
79
+ SwiftUI.Text(content)
80
80
  .font(astryx.font(size: sizeToken, 14, weight: weightToken))
81
81
  .foregroundStyle(astryx.color((color ?? type.defaultColor).token))
82
82
  }
@@ -12,7 +12,7 @@ public enum AstryxTextInputType: String, CaseIterable, Sendable {
12
12
 
13
13
  /// A labeled one-line text field, painted with the theme's tokens. The label
14
14
  /// stays visible above the field and is the field's accessibility label.
15
- public struct AstryxTextInput: View {
15
+ public struct TextInput: View {
16
16
  let label: String
17
17
  @Binding var value: String
18
18
  let type: AstryxTextInputType
@@ -39,7 +39,7 @@ public struct AstryxTextInput: View {
39
39
 
40
40
  public var body: some View {
41
41
  VStack(alignment: .leading, spacing: astryx.space(1)) {
42
- AstryxText(label, type: .label, color: .secondary)
42
+ Text(label, type: .label, color: .secondary)
43
43
  .accessibilityHidden(true)
44
44
  field
45
45
  .font(astryx.font(size: "text-body-size", 14, weight: "text-body-weight"))
@@ -52,7 +52,7 @@ public struct AstryxTextInput: View {
52
52
  )
53
53
  .disabled(isDisabled)
54
54
  .opacity(isDisabled ? 0.5 : 1)
55
- .accessibilityLabel(Text(accessibilityText))
55
+ .accessibilityLabel(SwiftUI.Text(accessibilityText))
56
56
  }
57
57
  }
58
58
 
@@ -61,7 +61,7 @@ public struct AstryxTextInput: View {
61
61
  var accessibilityText: String { label }
62
62
 
63
63
  @ViewBuilder private var field: some View {
64
- let prompt = placeholder.map { Text($0).foregroundStyle(astryx.color("color-text-secondary")) }
64
+ let prompt = placeholder.map { SwiftUI.Text($0).foregroundStyle(astryx.color("color-text-secondary")) }
65
65
  switch type {
66
66
  case .password:
67
67
  SecureField("", text: $value, prompt: prompt)
@@ -3,7 +3,7 @@
3
3
  /// The SF Symbol each of Astryx's built-in icon names draws by default on
4
4
  /// Apple platforms (AST-082 FR17). A test keeps this map equal to
5
5
  /// `NATIVE_ICON_DEFAULTS` in `@astryxdesign/native`; a theme replaces any
6
- /// entry through `AstryxTheme(icons:)`.
6
+ /// entry through `Theme(icons:)`.
7
7
  let astryxDefaultIcons: [String: String] = [
8
8
  "close": "xmark",
9
9
  "chevronDown": "chevron.down",