@octane-xplat/ui 0.3.0 → 0.5.0

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 (129) hide show
  1. package/README.md +46 -0
  2. package/dist/native/octane-client-build.json +1 -1
  3. package/dist/native/ui.js +55 -40
  4. package/dist/native/ui10.js +20 -11
  5. package/dist/native/ui11.js +42 -109
  6. package/dist/native/ui12.js +17 -84
  7. package/dist/native/ui13.js +11 -41
  8. package/dist/native/ui14.js +133 -76
  9. package/dist/native/ui15.js +43 -92
  10. package/dist/native/ui16.js +106 -45
  11. package/dist/native/ui17.js +29 -42
  12. package/dist/native/ui18.js +34 -53
  13. package/dist/native/ui19.js +246 -32
  14. package/dist/native/ui20.js +27 -16
  15. package/dist/native/ui21.js +376 -86
  16. package/dist/native/ui22.js +26 -114
  17. package/dist/native/ui23.js +49 -97
  18. package/dist/native/ui24.js +79 -53
  19. package/dist/native/ui25.js +138 -16
  20. package/dist/native/ui26.js +8 -23
  21. package/dist/native/ui27.js +60 -34
  22. package/dist/native/ui28.js +36 -142
  23. package/dist/native/ui29.js +101 -21
  24. package/dist/native/ui3.js +22 -1
  25. package/dist/native/ui30.js +14 -31
  26. package/dist/native/ui31.js +55 -36
  27. package/dist/native/ui32.js +49 -61
  28. package/dist/native/ui33.js +36 -28
  29. package/dist/native/ui34.js +51 -8
  30. package/dist/native/ui35.js +98 -20
  31. package/dist/native/ui36.js +9 -33
  32. package/dist/native/ui37.js +42 -15
  33. package/dist/native/ui38.js +186 -57
  34. package/dist/native/ui39.js +96 -26
  35. package/dist/native/ui4.js +22 -20
  36. package/dist/native/ui40.js +179 -8
  37. package/dist/native/ui41.js +52 -98
  38. package/dist/native/ui42.js +14 -145
  39. package/dist/native/ui43.js +16 -19
  40. package/dist/native/ui44.js +6 -6
  41. package/dist/native/ui45.js +38 -0
  42. package/dist/native/ui46.js +267 -0
  43. package/dist/native/ui47.js +28 -0
  44. package/dist/native/ui48.js +35 -0
  45. package/dist/native/ui49.js +41 -0
  46. package/dist/native/ui5.js +37 -77
  47. package/dist/native/ui50.js +72 -0
  48. package/dist/native/ui51.js +66 -0
  49. package/dist/native/ui52.js +64 -0
  50. package/dist/native/ui53.js +27 -0
  51. package/dist/native/ui54.js +13 -0
  52. package/dist/native/ui55.js +92 -0
  53. package/dist/native/ui56.js +34 -0
  54. package/dist/native/ui57.js +19 -0
  55. package/dist/native/ui58.js +156 -0
  56. package/dist/native/ui59.js +33 -0
  57. package/dist/native/ui6.js +94 -9
  58. package/dist/native/ui60.js +14 -0
  59. package/dist/native/ui61.js +22 -0
  60. package/dist/native/ui62.js +8 -0
  61. package/dist/native/ui63.js +108 -0
  62. package/dist/native/ui64.js +76 -0
  63. package/dist/native/ui7.js +42 -26
  64. package/dist/native/ui8.js +22 -12
  65. package/dist/native/ui9.js +18 -9
  66. package/dist/web/octane-client-build.json +1 -1
  67. package/dist/web/ui.js +53 -40
  68. package/dist/web/ui10.js +33 -186
  69. package/dist/web/ui11.js +28 -409
  70. package/dist/web/ui12.js +21 -68
  71. package/dist/web/ui13.js +265 -96
  72. package/dist/web/ui14.js +85 -95
  73. package/dist/web/ui15.js +368 -25
  74. package/dist/web/ui16.js +13 -101
  75. package/dist/web/ui17.js +246 -42
  76. package/dist/web/ui18.js +237 -28
  77. package/dist/web/ui19.js +36 -10
  78. package/dist/web/ui20.js +73 -102
  79. package/dist/web/ui21.js +102 -73
  80. package/dist/web/ui22.js +95 -55
  81. package/dist/web/ui23.js +94 -55
  82. package/dist/web/ui24.js +31 -15
  83. package/dist/web/ui25.js +105 -19
  84. package/dist/web/ui26.js +29 -23
  85. package/dist/web/ui27.js +12 -123
  86. package/dist/web/ui28.js +9 -110
  87. package/dist/web/ui29.js +52 -102
  88. package/dist/web/ui3.js +29 -18
  89. package/dist/web/ui30.js +32 -41
  90. package/dist/web/ui31.js +120 -39
  91. package/dist/web/ui32.js +41 -74
  92. package/dist/web/ui33.js +148 -32
  93. package/dist/web/ui34.js +145 -62
  94. package/dist/web/ui35.js +140 -7
  95. package/dist/web/ui36.js +86 -41
  96. package/dist/web/ui37.js +19 -44
  97. package/dist/web/ui38.js +5 -15
  98. package/dist/web/ui39.js +24 -86
  99. package/dist/web/ui4.js +46 -152
  100. package/dist/web/ui40.js +150 -37
  101. package/dist/web/ui41.js +36 -10
  102. package/dist/web/ui42.js +40 -7
  103. package/dist/web/ui43.js +73 -19
  104. package/dist/web/ui44.js +40 -6
  105. package/dist/web/ui45.js +62 -0
  106. package/dist/web/ui46.js +50 -0
  107. package/dist/web/ui47.js +9 -0
  108. package/dist/web/ui48.js +45 -0
  109. package/dist/web/ui49.js +13 -0
  110. package/dist/web/ui5.js +131 -27
  111. package/dist/web/ui50.js +63 -0
  112. package/dist/web/ui51.js +47 -0
  113. package/dist/web/ui52.js +19 -0
  114. package/dist/web/ui53.js +273 -0
  115. package/dist/web/ui54.js +42 -0
  116. package/dist/web/ui55.js +17 -0
  117. package/dist/web/ui56.js +9 -0
  118. package/dist/web/ui57.js +22 -0
  119. package/dist/web/ui58.js +8 -0
  120. package/dist/web/ui59.js +44 -0
  121. package/dist/web/ui6.js +81 -45
  122. package/dist/web/ui60.js +46 -0
  123. package/dist/web/ui7.js +43 -21
  124. package/dist/web/ui8.js +27 -21
  125. package/dist/web/ui9.js +29 -18
  126. package/package.json +17 -4
  127. package/src/theme/tokens.css +597 -141
  128. package/types/index.d.ts +112 -58
  129. package/types/props.d.ts +291 -16
@@ -3,274 +3,730 @@
3
3
 
4
4
  :root,
5
5
  .ns-root {
6
- --color-primary: #4f46e5;
7
- --color-on-primary: #ffffff;
8
- --color-surface: #ffffff;
9
- --color-text: #111827;
10
- --space-4: 16px;
6
+ --color-primary: #171717;
7
+ /* Token names double as the future Tailwind theme keys: text-onprimary
8
+ resolves --color-onprimary, bg-danger resolves --color-danger. Keep
9
+ class-name segments and var-name segments identical. */
10
+ --color-onprimary: #fafafa;
11
+ --color-surface: #ffffff;
12
+ --color-surface-secondary: #fafafa;
13
+ --color-text: #0a0a0a;
14
+ --color-text-secondary: #666666;
15
+ --color-border: #eaeaea;
16
+ --color-border-strong: #d4d4d4;
17
+ --color-danger: #dc2626;
18
+ /* Family lists are deliberately generic. Apps override these with their
19
+ web @font-face family and the platform's registered native name. */
20
+ --font-sans: system-ui, sans-serif;
21
+ --font-mono: ui-monospace, monospace;
22
+ --space-4: 16px;
23
+ /* Corner scale — rounded-rectangle radii. Smooth (squircle) corners are
24
+ the default corner treatment: every class that consumes a token also
25
+ declares `corner-shape: squircle`. Full-radius circles opt out —
26
+ `corner-shape` would reshape them into squircle disks on iOS/Chromium. */
27
+ --radius-sm: 6px;
28
+ --radius-md: 8px;
29
+ --radius-lg: 12px;
30
+ --radius-xl: 16px;
11
31
  }
12
32
 
13
33
  .ns-dark,
14
34
  .dark {
15
- --color-surface: #0f172a;
16
- --color-text: #f8fafc;
35
+ --color-primary: #ededed;
36
+ --color-onprimary: #171717;
37
+ --color-surface: #0a0a0a;
38
+ --color-surface-secondary: #141414;
39
+ --color-text: #ededed;
40
+ --color-text-secondary: #a1a1a1;
41
+ --color-border: #2e2e2e;
42
+ --color-border-strong: #4a4a4a;
43
+ }
44
+
45
+ /* xplat-web-only:start — DOM normalization. NativeScript is the reference
46
+ box model: dip sizes are outer boxes, and a border paints from
47
+ width+color alone. The DOM's UA stylesheet diverges on both counts, so
48
+ normalize the deltas once instead of patching individual rules:
49
+ - border-box keeps padding/borders inside declared sizes (a 40dip box
50
+ is 40px, not 40px + padding + border).
51
+ - border-width 0 + border-style solid makes the DOM behave like native:
52
+ any shared border-*-width declaration paints on both targets — the
53
+ DOM's initial border-width is `medium`, so style alone would draw
54
+ phantom ~3px edges wherever a rule sets only one side's width.
55
+ - <img> is a leaf, not inline text (no baseline descender gap).
56
+ - .vx-app columns are flex like their native screen containers. */
57
+ *,
58
+ *::before,
59
+ *::after {
60
+ box-sizing: border-box;
61
+ }
62
+ * {
63
+ border-width: 0;
64
+ border-style: solid;
65
+ }
66
+ img {
67
+ display: block;
17
68
  }
69
+ .vx-app {
70
+ display: flex;
71
+ flex-direction: column;
72
+ }
73
+ /* Native has no scroll chaining — a ScrollView/ListView just bounces
74
+ inside itself because the parent is never a scroller. contain is the
75
+ DOM equivalent: scroll stays in the leaf it started in. */
76
+ .vx-scroll,
77
+ .vx-list {
78
+ overscroll-behavior: contain;
79
+ }
80
+ /* xplat-web-only:end */
18
81
 
19
82
  /* vocabulary base styles */
20
83
 
21
84
  .vx-view,
22
85
  .vx-row {
23
- display: flex;
86
+ display: flex;
24
87
  }
25
88
 
26
89
  .vx-view {
27
- flex-direction: column;
90
+ flex-direction: column;
28
91
  }
29
92
 
30
93
  /* Safe-area values are zero when the viewport has no display cutout. */
31
94
  .vx-safe-area {
32
- padding-top: constant(safe-area-inset-top);
33
- padding-right: constant(safe-area-inset-right);
34
- padding-bottom: constant(safe-area-inset-bottom);
35
- padding-left: constant(safe-area-inset-left);
95
+ padding-top: constant(safe-area-inset-top);
96
+ padding-right: constant(safe-area-inset-right);
97
+ padding-bottom: constant(safe-area-inset-bottom);
98
+ padding-left: constant(safe-area-inset-left);
36
99
  }
37
100
 
38
101
  .vx-safe-area {
39
- padding-top: env(safe-area-inset-top);
40
- padding-right: env(safe-area-inset-right);
41
- padding-bottom: env(safe-area-inset-bottom);
42
- padding-left: env(safe-area-inset-left);
102
+ padding-top: env(safe-area-inset-top);
103
+ padding-right: env(safe-area-inset-right);
104
+ padding-bottom: env(safe-area-inset-bottom);
105
+ padding-left: env(safe-area-inset-left);
106
+ }
107
+
108
+ .vx-keyboard-avoiding {
109
+ display: flex;
110
+ flex-direction: column;
111
+ }
112
+ /* xplat-web-only:start — the native Drawer leaf is the ui-drawer widget;
113
+ these rules style the web DOM drawer overlay */
114
+ .vx-drawer {
115
+ display: flex;
116
+ flex-direction: row;
117
+ position: relative;
118
+ overflow: hidden;
119
+ }
120
+ .vx-drawer-main {
121
+ display: flex;
122
+ flex: 1;
123
+ min-width: 0;
124
+ }
125
+ .vx-drawer-backdrop {
126
+ position: absolute;
127
+ inset: 0;
128
+ background: rgba(0, 0, 0, 0.4);
43
129
  }
44
-
45
- .vx-keyboard-avoiding { display: flex; flex-direction: column; }
46
- .vx-drawer { display: flex; flex-direction: row; position: relative; overflow: hidden; }
47
- .vx-drawer-main { display: flex; flex: 1; min-width: 0; }
48
- .vx-drawer-backdrop { position: absolute; inset: 0; background: rgba(15, 23, 42, 0.35); }
49
130
  .vx-drawer-panel {
50
- position: absolute;
51
- z-index: 1;
52
- top: 0;
53
- bottom: 0;
54
- left: 0;
55
- width: 280px;
56
- overflow: auto;
57
- background: var(--color-surface);
58
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
59
- }
131
+ position: absolute;
132
+ z-index: 1;
133
+ top: 0;
134
+ bottom: 0;
135
+ left: 0;
136
+ width: 280px;
137
+ overflow: auto;
138
+ background: var(--color-surface);
139
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
140
+ }
141
+ /* xplat-web-only:end */
60
142
 
61
143
  .vx-row {
62
- flex-direction: row;
144
+ flex-direction: row;
63
145
  }
64
146
 
65
147
  .vx-grid,
66
148
  .vx-stack {
67
- display: grid;
149
+ display: grid;
68
150
  }
69
151
 
70
152
  .vx-stack > * {
71
- grid-area: 1 / 1;
153
+ grid-area: 1 / 1;
72
154
  }
73
155
 
156
+ /* xplat-web-only:start — Absolute's native leaf is absolutelayout; CSS
157
+ positioning is web-only (children position via left/top props) */
74
158
  .vx-absolute {
75
- position: relative;
159
+ position: relative;
76
160
  }
77
161
 
78
162
  .vx-absolute > * {
79
- position: absolute;
163
+ position: absolute;
80
164
  }
165
+ /* xplat-web-only:end */
81
166
 
82
167
  .vx-spacer {
83
- flex-grow: 1;
168
+ flex-grow: 1;
84
169
  }
85
170
 
86
171
  .vx-pressable {
87
- display: flex;
88
- flex-direction: column;
89
- user-select: none;
172
+ display: flex;
173
+ flex-direction: column;
174
+ user-select: none;
90
175
  }
91
176
 
92
177
  /* web press feedback */
93
178
  .vx-pressable:active {
94
- opacity: 0.85;
179
+ opacity: 0.85;
95
180
  }
96
181
  .vx-pressable-no-feedback:active {
97
- opacity: 1;
182
+ opacity: 1;
98
183
  }
99
184
  /* native press feedback (NS pseudo — dead rule on web, and vice versa) */
100
185
  .vx-pressable:pressed {
101
- opacity: 0.85;
186
+ opacity: 0.85;
102
187
  }
103
188
 
189
+ /* xplat-web-only:start — overlays/toasts/popovers render through RootLayout
190
+ or showModal on native; these portal/positioning rules are web-only */
104
191
  .vx-overlay {
105
- z-index: 1000;
192
+ z-index: 1000;
106
193
  }
107
194
 
108
195
  .vx-overlay-shade {
109
- position: absolute;
110
- inset: 0;
111
- background-color: rgba(0, 0, 0, 0.45);
196
+ position: absolute;
197
+ inset: 0;
198
+ background-color: rgba(0, 0, 0, 0.4);
112
199
  }
113
200
 
114
201
  .vx-overlay-content {
115
- position: relative;
116
- z-index: 1;
202
+ position: relative;
203
+ z-index: 1;
117
204
  }
118
205
 
119
206
  .vx-popover {
120
- z-index: 1001;
207
+ z-index: 1001;
208
+ }
209
+
210
+ .vx-popover-backdrop {
211
+ background-color: transparent;
121
212
  }
122
213
 
123
214
  .vx-toast-host {
124
- position: fixed;
125
- z-index: 1002;
126
- left: 0;
127
- right: 0;
128
- display: flex;
129
- justify-content: center;
130
- pointer-events: none;
215
+ position: fixed;
216
+ z-index: 1002;
217
+ left: 0;
218
+ right: 0;
219
+ display: flex;
220
+ justify-content: center;
221
+ pointer-events: none;
131
222
  }
132
223
 
133
- .vx-toast-top { top: 24px; }
134
- .vx-toast-bottom { bottom: 24px; }
224
+ .vx-toast-top {
225
+ top: 24px;
226
+ }
227
+ .vx-toast-bottom {
228
+ bottom: 24px;
229
+ }
135
230
 
136
231
  .vx-toast {
137
- pointer-events: auto;
232
+ pointer-events: auto;
138
233
  }
234
+ /* xplat-web-only:end */
139
235
 
140
236
  /* auto-grow owns height — the manual resize handle has no native analog */
237
+ /* xplat-web-only:start */
141
238
  .vx-textarea {
142
- resize: none;
239
+ resize: none;
143
240
  }
241
+ /* xplat-web-only:end */
144
242
 
145
243
  /* Modal is a real top-layer dialog on web. Presentation variants keep their
146
244
  sizing and anchoring in tokens so both the component and openModal share it. */
245
+ /* xplat-web-only:start */
147
246
  .vx-modal {
148
- width: 480px;
149
- max-width: 90%;
150
- max-height: 90%;
151
- padding: 24px;
152
- border: 0;
153
- border-radius: 16px;
154
- color: var(--color-text);
155
- background-color: var(--color-surface);
247
+ width: 480px;
248
+ max-width: 90%;
249
+ max-height: 90%;
250
+ padding: 24px;
251
+ border: 1px solid var(--color-border);
252
+ border-radius: var(--radius-xl);
253
+ corner-shape: squircle;
254
+ color: var(--color-text);
255
+ background-color: var(--color-surface);
156
256
  }
157
257
 
158
258
  .vx-modal::backdrop {
159
- background: rgba(15, 23, 42, 0.45);
259
+ background: rgba(0, 0, 0, 0.4);
160
260
  }
161
261
 
162
262
  .vx-modal--dialog {
163
- width: 480px;
164
- max-width: 90%;
263
+ width: 480px;
264
+ max-width: 90%;
165
265
  }
166
266
 
167
267
  .vx-modal--sheet {
168
- width: 100%;
169
- max-width: none;
170
- max-height: 85%;
171
- position: fixed;
172
- inset: auto 0 0;
173
- margin: 0;
174
- border-radius: 16px 16px 0 0;
268
+ width: 100%;
269
+ max-width: none;
270
+ max-height: 85%;
271
+ position: fixed;
272
+ inset: auto 0 0;
273
+ margin: 0;
274
+ border-top-left-radius: var(--radius-xl);
275
+ border-top-right-radius: var(--radius-xl);
276
+ corner-shape: squircle;
175
277
  }
176
278
 
177
279
  .vx-modal--fullscreen {
178
- width: 100%;
179
- max-width: none;
180
- height: 100%;
181
- max-height: none;
182
- margin: 0;
183
- border-radius: 0;
184
- }
185
-
280
+ width: 100%;
281
+ max-width: none;
282
+ height: 100%;
283
+ max-height: none;
284
+ margin: 0;
285
+ border-radius: 0;
286
+ }
287
+
288
+ /* Modal ROUTES (pushRoute presentation:'modal') — the URL stays underneath,
289
+ so the overlay is a fixed layer over the shell rather than a dialog. */
290
+ .vx-modalroute {
291
+ position: fixed;
292
+ inset: 0;
293
+ z-index: 1000;
294
+ display: flex;
295
+ align-items: stretch;
296
+ justify-content: stretch;
297
+ }
298
+
299
+ .vx-modalroute-shade {
300
+ position: absolute;
301
+ inset: 0;
302
+ border: 0;
303
+ padding: 0;
304
+ background: rgba(0, 0, 0, 0.4);
305
+ cursor: default;
306
+ }
307
+
308
+ .vx-modalroute-body {
309
+ position: relative;
310
+ flex: 1;
311
+ min-width: 0;
312
+ overflow: auto;
313
+ color: var(--color-text);
314
+ background-color: var(--color-surface);
315
+ }
316
+ /* xplat-web-only:end */
317
+
318
+ /* xplat-web-only:start — placeholder pseudos are DOM-only. Muted like
319
+ real placeholder text, not currentColor (a full-opacity hint reads as
320
+ a typed value); --placeholder-color is the per-field prop escape. */
186
321
  input::placeholder,
187
322
  textarea::placeholder {
188
- color: var(--placeholder-color, currentColor);
323
+ color: var(--placeholder-color, var(--color-text-secondary));
324
+ }
325
+ /* xplat-web-only:end */
326
+
327
+ /* xplat-web-only:start — form controls don't inherit the body font in the
328
+ DOM (UA stylesheet wins); without this they render in the UA face. */
329
+ input,
330
+ textarea,
331
+ select,
332
+ button {
333
+ font-family: inherit;
189
334
  }
335
+ /* xplat-web-only:end */
190
336
 
337
+ /* xplat-web-only:start — native ActivityIndicator is the NS widget */
191
338
  .vx-spinner {
192
- display: inline-block;
193
- width: 1em;
194
- height: 1em;
195
- box-sizing: border-box;
196
- border: 2px solid currentColor;
197
- border-right-color: transparent;
198
- border-radius: 50%;
199
- animation: vx-spinner-turn 0.8s linear infinite;
339
+ display: inline-block;
340
+ width: 1em;
341
+ height: 1em;
342
+ box-sizing: border-box;
343
+ border: 2px solid currentColor;
344
+ border-right-color: transparent;
345
+ border-radius: 50%;
346
+ animation: vx-spinner-turn 0.8s linear infinite;
200
347
  }
201
348
 
202
349
  @keyframes vx-spinner-turn {
203
- to { transform: rotate(360deg); }
350
+ to {
351
+ transform: rotate(360deg);
352
+ }
353
+ }
354
+ /* xplat-web-only:end */
355
+
356
+ .vx-h1 {
357
+ font-size: 2em;
358
+ font-weight: bold;
359
+ }
360
+ .vx-h2 {
361
+ font-size: 1.5em;
362
+ font-weight: bold;
363
+ }
364
+ .vx-h3 {
365
+ font-size: 1.17em;
366
+ font-weight: bold;
367
+ }
368
+ .vx-h4 {
369
+ font-size: 1em;
370
+ font-weight: bold;
371
+ }
372
+ .vx-h5 {
373
+ font-size: 0.83em;
374
+ font-weight: bold;
375
+ }
376
+ .vx-h6 {
377
+ font-size: 0.67em;
378
+ font-weight: bold;
204
379
  }
205
380
 
206
- .vx-h1 { font-size: 2em; font-weight: bold; }
207
- .vx-h2 { font-size: 1.5em; font-weight: bold; }
208
- .vx-h3 { font-size: 1.17em; font-weight: bold; }
209
- .vx-h4 { font-size: 1em; font-weight: bold; }
210
- .vx-h5 { font-size: 0.83em; font-weight: bold; }
211
- .vx-h6 { font-size: 0.67em; font-weight: bold; }
381
+ /* xplat-web-only:start — glass material is real on iOS 26+; web approximates
382
+ with backdrop-filter. The class also lands on native elements (inert) so
383
+ apps can style their own fallback — but these rules are DOM-only. */
384
+ .vx-glass {
385
+ background-color: color-mix(in srgb, var(--color-surface) 30%, transparent);
386
+ border: 1px solid rgba(255, 255, 255, 0.25);
387
+ }
212
388
 
213
- /* utilities (hand-written subset for the harness; Tailwind later) */
389
+ @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
390
+ .vx-glass {
391
+ background-color: color-mix(in srgb, var(--color-surface) 20%, transparent);
392
+ -webkit-backdrop-filter: blur(20px) saturate(1.6);
393
+ backdrop-filter: blur(20px) saturate(1.6);
394
+ }
395
+ }
214
396
 
215
- .flex-1 { flex-grow: 1; }
216
- .items-center { align-items: center; }
217
- .justify-center { justify-content: center; }
218
- .gap-4 { gap: 16px; }
397
+ .vx-glass--clear {
398
+ background-color: color-mix(in srgb, var(--color-surface) 12%, transparent);
399
+ border-color: rgba(255, 255, 255, 0.15);
400
+ }
219
401
 
220
- .text-xl { font-size: 20px; }
221
- .font-bold { font-weight: bold; }
402
+ @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
403
+ .vx-glass--clear {
404
+ -webkit-backdrop-filter: blur(8px) saturate(1.3);
405
+ backdrop-filter: blur(8px) saturate(1.3);
406
+ }
407
+ }
408
+ /* xplat-web-only:end */
222
409
 
223
- .bg-primary { background-color: var(--color-primary); }
224
- .text-onprimary { color: var(--color-on-primary); }
225
- .bg-danger { background-color: #dc2626; }
226
- .shadow-2 { box-shadow: 0 4px 12px rgba(0,0,0,0.25); }
410
+ /* utilities (hand-written subset — do NOT adopt Tailwind until
411
+ NativeScript/tailwind#226 is resolved; the plugin strips core-supported
412
+ props incl. gap, drops all @media, and rewrites invisible→collapse) */
227
413
 
228
- .btn {
229
- padding-top: 12px;
230
- padding-right: 20px;
231
- padding-bottom: 12px;
232
- padding-left: 20px;
233
- border-radius: 8px;
414
+ /* flex-1 keeps Tailwind v4's exact emission (`flex: 1 1 0%`) — web gets
415
+ identical semantics, NS parses grow/shrink out of the shorthand and
416
+ ignores the basis token it can't model. */
417
+ .flex-1 {
418
+ flex: 1 1 0%;
419
+ }
420
+ /* Flex divergence guards — NS FlexboxLayout diverges from web flexbox on
421
+ * shrinkability: grow children never shrink below intrinsic width, fixed
422
+ * children get crushed, and auto-height rows re-measure grow children at
423
+ * zero height. min-w-0/min-h-0 let a grow child shrink; shrink-0 protects
424
+ * a fixed-size child. All three are plain CSS on web. */
425
+ .min-w-0 {
426
+ min-width: 0;
427
+ }
428
+ .min-h-0 {
429
+ min-height: 0;
430
+ }
431
+ .shrink-0 {
432
+ flex-shrink: 0;
433
+ }
434
+ .grow {
435
+ flex-grow: 1;
436
+ }
437
+ .items-center {
438
+ align-items: center;
439
+ }
440
+ .items-baseline {
441
+ align-items: baseline;
442
+ }
443
+ .items-start {
444
+ align-items: flex-start;
445
+ }
446
+ .justify-center {
447
+ justify-content: center;
234
448
  }
449
+ .justify-between {
450
+ justify-content: space-between;
451
+ }
452
+ .flex-wrap {
453
+ flex-wrap: wrap;
454
+ }
455
+ .gap-4 {
456
+ gap: 16px;
457
+ }
458
+ .gap-3 {
459
+ gap: 12px;
460
+ }
461
+ .gap-6 {
462
+ gap: 24px;
463
+ }
464
+
465
+ .text-2xl {
466
+ font-size: 28px;
467
+ }
468
+ .text-xl {
469
+ font-size: 20px;
470
+ }
471
+ .text-lg {
472
+ font-size: 18px;
473
+ }
474
+ .text-sm {
475
+ font-size: 13px;
476
+ }
477
+ .font-bold {
478
+ font-weight: bold;
479
+ }
480
+ .font-semibold {
481
+ font-weight: 600;
482
+ }
483
+ .text-muted {
484
+ color: var(--color-text-secondary);
485
+ }
486
+
487
+ /* Corner utilities — consume the --radius-* scale and carry the smooth
488
+ (squircle) corner treatment. Circles stay circular: .rounded-full omits
489
+ corner-shape deliberately (squircle at full radius = a squircle disk,
490
+ not a circle). Opt out per element with `corner-shape: round`. */
491
+ .rounded-sm {
492
+ border-radius: var(--radius-sm);
493
+ corner-shape: squircle;
494
+ }
495
+ .rounded-md {
496
+ border-radius: var(--radius-md);
497
+ corner-shape: squircle;
498
+ }
499
+ .rounded-lg {
500
+ border-radius: var(--radius-lg);
501
+ corner-shape: squircle;
502
+ }
503
+ .rounded-xl {
504
+ border-radius: var(--radius-xl);
505
+ corner-shape: squircle;
506
+ }
507
+ .rounded-full {
508
+ border-radius: 9999px;
509
+ }
510
+
511
+ .bg-primary {
512
+ background-color: var(--color-primary);
513
+ }
514
+ .text-onprimary {
515
+ color: var(--color-onprimary);
516
+ }
517
+ .bg-danger {
518
+ background-color: var(--color-danger);
519
+ }
520
+ /* xplat-web-only:start — box-shadow is inert on native (elevation is
521
+ Android-only; no shared mapping yet) */
522
+ .shadow-2 {
523
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
524
+ }
525
+ /* xplat-web-only:end */
526
+
527
+ .btn {
528
+ padding-top: 10px;
529
+ padding-right: 16px;
530
+ padding-bottom: 10px;
531
+ padding-left: 16px;
532
+ border-radius: var(--radius-md);
533
+ corner-shape: squircle;
534
+ font-size: 14px;
535
+ font-weight: 500;
536
+ align-items: center;
537
+ justify-content: center;
538
+ text-decoration: none;
539
+ color: var(--color-text);
540
+ }
541
+
542
+ /* Secondary = the same control reading quiet: hairline border, surface
543
+ fill, body-text label. Reserve .bg-primary for the one decisive action
544
+ in a group. */
545
+ .btn-secondary {
546
+ background-color: var(--color-surface);
547
+ border-width: 1px;
548
+ border-color: var(--color-border);
549
+ color: var(--color-text);
550
+ }
551
+
552
+ /* xplat-web-only:start — controls that render as real <a>/<button> need
553
+ inline layout and UA resets; the pressable/nav-link leaves are divs on
554
+ web and don't hit these rules. */
555
+ a.btn {
556
+ display: inline-flex;
557
+ }
558
+ a.chip {
559
+ display: inline-flex;
560
+ align-items: center;
561
+ }
562
+ /* xplat-web-only:end */
563
+
564
+ /* Tab shell — web divs/buttons + the Android SwapTabs fallback (iOS draws
565
+ its own UITabBar and never sees these). */
566
+ .vx-tabbar {
567
+ border-bottom-width: 1px;
568
+ border-bottom-color: var(--color-border);
569
+ background-color: var(--color-surface);
570
+ }
571
+ .vx-tab {
572
+ align-items: center;
573
+ justify-content: center;
574
+ padding-top: 10px;
575
+ padding-right: 16px;
576
+ padding-bottom: 10px;
577
+ padding-left: 16px;
578
+ font-size: 14px;
579
+ color: var(--color-text-secondary);
580
+ }
581
+ .vx-tab.active {
582
+ color: var(--color-text);
583
+ }
584
+
585
+ /* xplat-web-only:start — DOM defaults are display:block (div) and
586
+ inline-block (button); every flex-bearing vx- class needs an explicit
587
+ display or flex-direction/align-items/justify-content are inert. */
588
+ .vx-tabs {
589
+ display: flex;
590
+ flex-direction: column;
591
+ flex: 1;
592
+ min-height: 0;
593
+ }
594
+ .vx-tabbar {
595
+ display: flex;
596
+ flex-direction: row;
597
+ }
598
+ .vx-tabpane {
599
+ flex: 1;
600
+ min-height: 0;
601
+ display: flex;
602
+ flex-direction: column;
603
+ }
604
+ button.vx-tab {
605
+ display: flex;
606
+ background: none;
607
+ cursor: pointer;
608
+ }
609
+ /* xplat-web-only:end */
235
610
 
236
611
  /* harness experiment styles */
237
612
 
238
613
  .vx-app {
239
- padding: 24px;
240
- background-color: var(--color-surface);
241
- color: var(--color-text);
614
+ padding: 24px;
615
+ background-color: var(--color-surface);
616
+ color: var(--color-text);
242
617
  }
243
618
 
244
619
  .vx-app .text-xl,
245
620
  .vx-app Text,
246
621
  .vx-app label {
247
- color: var(--color-text);
622
+ color: var(--color-text);
623
+ }
624
+
625
+ /* `.vx-app label` above (descendant + type) outranks single-class text
626
+ colors inside the app root — roles that must win escalate here. */
627
+ .vx-app .text-onprimary {
628
+ color: var(--color-onprimary);
629
+ }
630
+ .vx-app .text-muted {
631
+ color: var(--color-text-secondary);
632
+ }
633
+ .vx-app .vx-tab label {
634
+ color: var(--color-text-secondary);
635
+ }
636
+ .vx-app .vx-tab.active label {
637
+ color: var(--color-text);
638
+ }
639
+ .vx-app .chip-on label {
640
+ color: var(--color-onprimary);
641
+ }
642
+ /* Danger surfaces keep white text in both themes (on-primary flips). */
643
+ .bg-danger .text-onprimary {
644
+ color: #ffffff;
248
645
  }
249
646
 
250
647
  .input {
251
- padding: 10px;
252
- border-width: 1px;
253
- border-color: #94a3b8;
254
- border-radius: 6px;
255
- font-size: 16px;
648
+ padding-top: 10px;
649
+ padding-right: 12px;
650
+ padding-bottom: 10px;
651
+ padding-left: 12px;
652
+ border-width: 1px;
653
+ border-color: var(--color-border);
654
+ border-radius: var(--radius-md);
655
+ corner-shape: squircle;
656
+ font-size: 14px;
657
+ background-color: var(--color-surface);
658
+ color: var(--color-text);
659
+ /* NS TextField/TextView placeholder color (ignored on web — the
660
+ ::placeholder pseudo above covers it there). */
661
+ placeholder-color: var(--color-text-secondary);
256
662
  }
257
663
 
258
664
  .chip {
259
- background-color: var(--color-primary);
260
- color: var(--color-on-primary);
261
- padding: 4px 10px;
262
- border-radius: 12px;
263
- font-size: 13px;
665
+ background-color: var(--color-surface);
666
+ color: var(--color-text);
667
+ padding-top: 5px;
668
+ padding-right: 12px;
669
+ padding-bottom: 5px;
670
+ padding-left: 12px;
671
+ border-width: 1px;
672
+ border-color: var(--color-border);
673
+ border-radius: var(--radius-md);
674
+ corner-shape: squircle;
675
+ font-size: 13px;
676
+ text-decoration: none;
677
+ }
678
+
679
+ /* Selected chip — the one filled element in a group of bordered peers. */
680
+ .chip-on {
681
+ background-color: var(--color-primary);
682
+ border-color: var(--color-primary);
683
+ color: var(--color-onprimary);
264
684
  }
265
685
 
266
686
  .cell {
267
- padding: 12px;
268
- border-bottom-width: 1px;
269
- border-bottom-color: #e2e8f0;
687
+ padding: 12px;
688
+ border-bottom-width: 1px;
689
+ border-bottom-color: var(--color-border);
270
690
  }
271
691
 
272
692
  .vx-list {
273
- flex-grow: 1;
693
+ flex-grow: 1;
694
+ }
695
+
696
+ .gap-2 {
697
+ gap: 8px;
698
+ }
699
+
700
+ /* In-window sheet — bottom-anchored panel (matches the native RootLayout
701
+ sheet). Layer is pointer-events:none; backdrop + panel re-enable. */
702
+ .vx-sheet-layer {
703
+ position: fixed;
704
+ inset: 0;
705
+ z-index: 1100;
706
+ pointer-events: none;
707
+ }
708
+
709
+ .vx-sheet-backdrop {
710
+ position: absolute;
711
+ inset: 0;
712
+ background: rgba(0, 0, 0, 0.4);
713
+ pointer-events: auto;
714
+ }
715
+
716
+ .vx-sheet {
717
+ position: absolute;
718
+ left: 0;
719
+ right: 0;
720
+ bottom: 0;
721
+ max-height: 85%;
722
+ padding: 24px;
723
+ overflow: auto;
724
+ border-width: 1px;
725
+ border-color: var(--color-border);
726
+ border-top-left-radius: var(--radius-xl);
727
+ border-top-right-radius: var(--radius-xl);
728
+ corner-shape: squircle;
729
+ background-color: var(--color-surface);
730
+ color: var(--color-text);
731
+ pointer-events: auto;
274
732
  }
275
-
276
- .gap-2 { gap: 8px; }