ionbase-ui 0.69.0 → 0.73.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 (47) hide show
  1. package/dist/components/ScrollProgress.d.ts +6 -0
  2. package/dist/components/ScrollProgress.d.ts.map +1 -1
  3. package/dist/components/ScrollProgress.js +2 -2
  4. package/dist/components/ScrollProgress.js.map +1 -1
  5. package/dist/components/SettingRow.d.ts +54 -0
  6. package/dist/components/SettingRow.d.ts.map +1 -0
  7. package/dist/components/SettingRow.js +60 -0
  8. package/dist/components/SettingRow.js.map +1 -0
  9. package/dist/components/StatTile.d.ts +65 -0
  10. package/dist/components/StatTile.d.ts.map +1 -0
  11. package/dist/components/StatTile.js +54 -0
  12. package/dist/components/StatTile.js.map +1 -0
  13. package/dist/components/Toast.d.ts +8 -1
  14. package/dist/components/Toast.d.ts.map +1 -1
  15. package/dist/components/Toast.js +2 -2
  16. package/dist/components/Toast.js.map +1 -1
  17. package/dist/components/index.d.ts +5 -1
  18. package/dist/components/index.d.ts.map +1 -1
  19. package/dist/components/index.js +2 -0
  20. package/dist/components/index.js.map +1 -1
  21. package/dist/figma-descriptions.json +78 -63
  22. package/dist/figma-map.json +149 -1
  23. package/dist/meta/ScrollProgress.json +26 -1
  24. package/dist/meta/SettingRow.json +102 -0
  25. package/dist/meta/StatGroup.json +65 -0
  26. package/dist/meta/StatTile.json +163 -0
  27. package/dist/meta/ToastProvider.json +9 -2
  28. package/dist/meta/components.json +366 -4
  29. package/dist/meta/contrast.json +264 -0
  30. package/dist/meta/index.json +38 -2
  31. package/dist/meta/patterns/SettingsPanel.json +2 -1
  32. package/dist/meta/patterns/index.json +2 -1
  33. package/dist/styles/index.css +2 -0
  34. package/dist/styles/scroll-progress.css +42 -4
  35. package/dist/styles/setting-row.css +58 -0
  36. package/dist/styles/stat-tile.css +66 -0
  37. package/dist/styles/table.css +9 -0
  38. package/dist/styles/tokens/base.css +36 -8
  39. package/dist/styles/tokens/theme-dark.css +12 -0
  40. package/dist/tokens/index.d.ts +193 -40
  41. package/dist/tokens/index.d.ts.map +1 -1
  42. package/dist/tokens/index.js +157 -32
  43. package/dist/tokens/index.js.map +1 -1
  44. package/eslint-plugin/meta-data.js +9 -0
  45. package/eslint-plugin/rules/needs-accessible-name.js +14 -1
  46. package/llms.txt +11 -2
  47. package/package.json +1 -1
@@ -9350,6 +9350,20 @@
9350
9350
  "ratio": 7.56,
9351
9351
  "min": 4.5
9352
9352
  },
9353
+ {
9354
+ "component": "stat-tile",
9355
+ "context": "ion-stat-tile ion-stat-tile__comparison",
9356
+ "state": "default",
9357
+ "fg": "--text-tertiary",
9358
+ "bg": "--surface-default",
9359
+ "backdrop": "--surface-default",
9360
+ "kind": "text",
9361
+ "mode": "Light",
9362
+ "fgHex": "#4b5563",
9363
+ "bgHex": "#ffffff",
9364
+ "ratio": 7.56,
9365
+ "min": 4.5
9366
+ },
9353
9367
  {
9354
9368
  "component": "stepper",
9355
9369
  "context": "ion-stepper ion-stepper__description",
@@ -10614,6 +10628,20 @@
10614
10628
  "ratio": 8.28,
10615
10629
  "min": 4.5
10616
10630
  },
10631
+ {
10632
+ "component": "stat-tile",
10633
+ "context": "ion-stat-tile ion-stat-tile__comparison",
10634
+ "state": "default",
10635
+ "fg": "--text-tertiary",
10636
+ "bg": "--surface-default",
10637
+ "backdrop": "--surface-default",
10638
+ "kind": "text",
10639
+ "mode": "Dark",
10640
+ "fgHex": "#9ca3b0",
10641
+ "bgHex": "#000000",
10642
+ "ratio": 8.28,
10643
+ "min": 4.5
10644
+ },
10617
10645
  {
10618
10646
  "component": "stepper",
10619
10647
  "context": "ion-stepper ion-stepper__description",
@@ -11522,6 +11550,21 @@
11522
11550
  "ratio": 9.04,
11523
11551
  "min": 4.5
11524
11552
  },
11553
+ {
11554
+ "component": "setting-row",
11555
+ "context": "ion-setting-row ion-setting-row__description",
11556
+ "state": "default",
11557
+ "fg": "--text-secondary",
11558
+ "bg": "--surface-muted",
11559
+ "backdrop": null,
11560
+ "kind": "text",
11561
+ "ground": "assumed",
11562
+ "mode": "Light",
11563
+ "fgHex": "#384252",
11564
+ "bgHex": "#f0f2f4",
11565
+ "ratio": 9.04,
11566
+ "min": 4.5
11567
+ },
11525
11568
  {
11526
11569
  "component": "spinner",
11527
11570
  "context": "ion-spinner",
@@ -12698,6 +12741,21 @@
12698
12741
  "ratio": 9.53,
12699
12742
  "min": 4.5
12700
12743
  },
12744
+ {
12745
+ "component": "setting-row",
12746
+ "context": "ion-setting-row ion-setting-row__description",
12747
+ "state": "default",
12748
+ "fg": "--text-secondary",
12749
+ "bg": "--surface-page",
12750
+ "backdrop": null,
12751
+ "kind": "text",
12752
+ "ground": "assumed",
12753
+ "mode": "Light",
12754
+ "fgHex": "#384252",
12755
+ "bgHex": "#f6f8f9",
12756
+ "ratio": 9.53,
12757
+ "min": 4.5
12758
+ },
12701
12759
  {
12702
12760
  "component": "spinner",
12703
12761
  "context": "ion-spinner",
@@ -14014,6 +14072,21 @@
14014
14072
  "ratio": 10.15,
14015
14073
  "min": 4.5
14016
14074
  },
14075
+ {
14076
+ "component": "setting-row",
14077
+ "context": "ion-setting-row ion-setting-row__description",
14078
+ "state": "default",
14079
+ "fg": "--text-secondary",
14080
+ "bg": "--surface-default",
14081
+ "backdrop": null,
14082
+ "kind": "text",
14083
+ "ground": "assumed",
14084
+ "mode": "Light",
14085
+ "fgHex": "#384252",
14086
+ "bgHex": "#ffffff",
14087
+ "ratio": 10.15,
14088
+ "min": 4.5
14089
+ },
14017
14090
  {
14018
14091
  "component": "sidebar",
14019
14092
  "context": "ion-sidebar",
@@ -14354,6 +14427,20 @@
14354
14427
  "ratio": 10.15,
14355
14428
  "min": 4.5
14356
14429
  },
14430
+ {
14431
+ "component": "stat-tile",
14432
+ "context": "ion-stat-tile ion-stat-tile__label",
14433
+ "state": "default",
14434
+ "fg": "--text-secondary",
14435
+ "bg": "--surface-default",
14436
+ "backdrop": "--surface-default",
14437
+ "kind": "text",
14438
+ "mode": "Light",
14439
+ "fgHex": "#384252",
14440
+ "bgHex": "#ffffff",
14441
+ "ratio": 10.15,
14442
+ "min": 4.5
14443
+ },
14357
14444
  {
14358
14445
  "component": "stepper",
14359
14446
  "context": "ion-stepper ion-stepper__step",
@@ -15829,6 +15916,21 @@
15829
15916
  "ratio": 11.38,
15830
15917
  "min": 4.5
15831
15918
  },
15919
+ {
15920
+ "component": "setting-row",
15921
+ "context": "ion-setting-row ion-setting-row__description",
15922
+ "state": "default",
15923
+ "fg": "--text-secondary",
15924
+ "bg": "--surface-muted",
15925
+ "backdrop": null,
15926
+ "kind": "text",
15927
+ "ground": "assumed",
15928
+ "mode": "Dark",
15929
+ "fgHex": "#dde0e4",
15930
+ "bgHex": "#1d2735",
15931
+ "ratio": 11.38,
15932
+ "min": 4.5
15933
+ },
15832
15934
  {
15833
15935
  "component": "spinner",
15834
15936
  "context": "ion-spinner",
@@ -17032,6 +17134,21 @@
17032
17134
  "ratio": 13.31,
17033
17135
  "min": 4.5
17034
17136
  },
17137
+ {
17138
+ "component": "setting-row",
17139
+ "context": "ion-setting-row ion-setting-row__description",
17140
+ "state": "default",
17141
+ "fg": "--text-secondary",
17142
+ "bg": "--surface-page",
17143
+ "backdrop": null,
17144
+ "kind": "text",
17145
+ "ground": "assumed",
17146
+ "mode": "Dark",
17147
+ "fgHex": "#dde0e4",
17148
+ "bgHex": "#131923",
17149
+ "ratio": 13.31,
17150
+ "min": 4.5
17151
+ },
17035
17152
  {
17036
17153
  "component": "spinner",
17037
17154
  "context": "ion-spinner",
@@ -18089,6 +18206,21 @@
18089
18206
  "ratio": 14.14,
18090
18207
  "min": 4.5
18091
18208
  },
18209
+ {
18210
+ "component": "setting-row",
18211
+ "context": "ion-setting-row ion-setting-row__label",
18212
+ "state": "default",
18213
+ "fg": "--text-default",
18214
+ "bg": "--surface-muted",
18215
+ "backdrop": null,
18216
+ "kind": "text",
18217
+ "ground": "assumed",
18218
+ "mode": "Dark",
18219
+ "fgHex": "#f6f8f9",
18220
+ "bgHex": "#1d2735",
18221
+ "ratio": 14.14,
18222
+ "min": 4.5
18223
+ },
18092
18224
  {
18093
18225
  "component": "stepper",
18094
18226
  "context": "ion-stepper ion-stepper__trigger",
@@ -18842,6 +18974,21 @@
18842
18974
  "ratio": 15.71,
18843
18975
  "min": 4.5
18844
18976
  },
18977
+ {
18978
+ "component": "setting-row",
18979
+ "context": "ion-setting-row ion-setting-row__label",
18980
+ "state": "default",
18981
+ "fg": "--text-default",
18982
+ "bg": "--surface-muted",
18983
+ "backdrop": null,
18984
+ "kind": "text",
18985
+ "ground": "assumed",
18986
+ "mode": "Light",
18987
+ "fgHex": "#131923",
18988
+ "bgHex": "#f0f2f4",
18989
+ "ratio": 15.71,
18990
+ "min": 4.5
18991
+ },
18845
18992
  {
18846
18993
  "component": "stepper",
18847
18994
  "context": "ion-stepper ion-stepper__trigger",
@@ -19725,6 +19872,21 @@
19725
19872
  "ratio": 15.86,
19726
19873
  "min": 4.5
19727
19874
  },
19875
+ {
19876
+ "component": "setting-row",
19877
+ "context": "ion-setting-row ion-setting-row__description",
19878
+ "state": "default",
19879
+ "fg": "--text-secondary",
19880
+ "bg": "--surface-default",
19881
+ "backdrop": null,
19882
+ "kind": "text",
19883
+ "ground": "assumed",
19884
+ "mode": "Dark",
19885
+ "fgHex": "#dde0e4",
19886
+ "bgHex": "#000000",
19887
+ "ratio": 15.86,
19888
+ "min": 4.5
19889
+ },
19728
19890
  {
19729
19891
  "component": "sidebar",
19730
19892
  "context": "ion-sidebar",
@@ -20065,6 +20227,20 @@
20065
20227
  "ratio": 15.86,
20066
20228
  "min": 4.5
20067
20229
  },
20230
+ {
20231
+ "component": "stat-tile",
20232
+ "context": "ion-stat-tile ion-stat-tile__label",
20233
+ "state": "default",
20234
+ "fg": "--text-secondary",
20235
+ "bg": "--surface-default",
20236
+ "backdrop": "--surface-default",
20237
+ "kind": "text",
20238
+ "mode": "Dark",
20239
+ "fgHex": "#dde0e4",
20240
+ "bgHex": "#000000",
20241
+ "ratio": 15.86,
20242
+ "min": 4.5
20243
+ },
20068
20244
  {
20069
20245
  "component": "stepper",
20070
20246
  "context": "ion-stepper ion-stepper__step",
@@ -20711,6 +20887,36 @@
20711
20887
  "ratio": 16.55,
20712
20888
  "min": 4.5
20713
20889
  },
20890
+ {
20891
+ "component": "setting-row",
20892
+ "context": "ion-setting-row ion-setting-row__label",
20893
+ "state": "default",
20894
+ "fg": "--text-default",
20895
+ "bg": "--surface-page",
20896
+ "backdrop": null,
20897
+ "kind": "text",
20898
+ "ground": "assumed",
20899
+ "mode": "Light",
20900
+ "fgHex": "#131923",
20901
+ "bgHex": "#f6f8f9",
20902
+ "ratio": 16.55,
20903
+ "min": 4.5
20904
+ },
20905
+ {
20906
+ "component": "setting-row",
20907
+ "context": "ion-setting-row ion-setting-row__label",
20908
+ "state": "default",
20909
+ "fg": "--text-default",
20910
+ "bg": "--surface-page",
20911
+ "backdrop": null,
20912
+ "kind": "text",
20913
+ "ground": "assumed",
20914
+ "mode": "Dark",
20915
+ "fgHex": "#f6f8f9",
20916
+ "bgHex": "#131923",
20917
+ "ratio": 16.55,
20918
+ "min": 4.5
20919
+ },
20714
20920
  {
20715
20921
  "component": "stepper",
20716
20922
  "context": "ion-stepper ion-stepper__trigger",
@@ -21714,6 +21920,35 @@
21714
21920
  "ratio": 17.63,
21715
21921
  "min": 4.5
21716
21922
  },
21923
+ {
21924
+ "component": "setting-row",
21925
+ "context": "ion-setting-row ion-setting-row__label",
21926
+ "state": "default",
21927
+ "fg": "--text-default",
21928
+ "bg": "--surface-default",
21929
+ "backdrop": null,
21930
+ "kind": "text",
21931
+ "ground": "assumed",
21932
+ "mode": "Light",
21933
+ "fgHex": "#131923",
21934
+ "bgHex": "#ffffff",
21935
+ "ratio": 17.63,
21936
+ "min": 4.5
21937
+ },
21938
+ {
21939
+ "component": "stat-tile",
21940
+ "context": "ion-stat-tile ion-stat-tile__value",
21941
+ "state": "default",
21942
+ "fg": "--text-default",
21943
+ "bg": "--surface-default",
21944
+ "backdrop": "--surface-default",
21945
+ "kind": "text",
21946
+ "mode": "Light",
21947
+ "fgHex": "#131923",
21948
+ "bgHex": "#ffffff",
21949
+ "ratio": 17.63,
21950
+ "min": 4.5
21951
+ },
21717
21952
  {
21718
21953
  "component": "stepper",
21719
21954
  "context": "ion-stepper ion-stepper__trigger",
@@ -22533,6 +22768,35 @@
22533
22768
  "ratio": 19.71,
22534
22769
  "min": 4.5
22535
22770
  },
22771
+ {
22772
+ "component": "setting-row",
22773
+ "context": "ion-setting-row ion-setting-row__label",
22774
+ "state": "default",
22775
+ "fg": "--text-default",
22776
+ "bg": "--surface-default",
22777
+ "backdrop": null,
22778
+ "kind": "text",
22779
+ "ground": "assumed",
22780
+ "mode": "Dark",
22781
+ "fgHex": "#f6f8f9",
22782
+ "bgHex": "#000000",
22783
+ "ratio": 19.71,
22784
+ "min": 4.5
22785
+ },
22786
+ {
22787
+ "component": "stat-tile",
22788
+ "context": "ion-stat-tile ion-stat-tile__value",
22789
+ "state": "default",
22790
+ "fg": "--text-default",
22791
+ "bg": "--surface-default",
22792
+ "backdrop": "--surface-default",
22793
+ "kind": "text",
22794
+ "mode": "Dark",
22795
+ "fgHex": "#f6f8f9",
22796
+ "bgHex": "#000000",
22797
+ "ratio": 19.71,
22798
+ "min": 4.5
22799
+ },
22536
22800
  {
22537
22801
  "component": "stepper",
22538
22802
  "context": "ion-stepper ion-stepper__trigger",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "ionbase-ui",
3
- "version": "0.69.0",
3
+ "version": "0.73.0",
4
4
  "usage": "Pick a component here, then read dist/meta/<Name>.json for its full contract.",
5
5
  "hooks": [
6
6
  "useToast"
@@ -663,7 +663,12 @@
663
663
  "ScrollProgress": {
664
664
  "summary": "A compact progress rail that expands into a section list. A disclosure, not a menu — and it computes nothing itself.",
665
665
  "status": "stable",
666
- "variants": {},
666
+ "variants": {
667
+ "placement": [
668
+ "right",
669
+ "left"
670
+ ]
671
+ },
667
672
  "detail": "dist/meta/ScrollProgress.json",
668
673
  "hasIntent": true
669
674
  },
@@ -700,6 +705,13 @@
700
705
  "detail": "dist/meta/Select.json",
701
706
  "hasIntent": true
702
707
  },
708
+ "SettingRow": {
709
+ "summary": "One setting: its name and description on the left, the control on the right — and the control named and described by them automatically.",
710
+ "status": "stable",
711
+ "variants": {},
712
+ "detail": "dist/meta/SettingRow.json",
713
+ "hasIntent": true
714
+ },
703
715
  "Sidebar": {
704
716
  "summary": "An application's navigation as a column: pinned header and footer that take any component, a scrolling body of titled, collapsible sections, and nested items that expand.",
705
717
  "status": "stable",
@@ -747,6 +759,30 @@
747
759
  "detail": "dist/meta/Spinner.json",
748
760
  "hasIntent": true
749
761
  },
762
+ "StatGroup": {
763
+ "summary": "A row of StatTiles, as a description list. Wraps on its own — no breakpoints in the caller's code.",
764
+ "status": "stable",
765
+ "variants": {},
766
+ "detail": "dist/meta/StatGroup.json",
767
+ "hasIntent": true
768
+ },
769
+ "StatTile": {
770
+ "summary": "One headline figure and how it moved against a comparison period. Good and bad come from `goodWhen`, never from the sign alone.",
771
+ "status": "stable",
772
+ "variants": {
773
+ "changeUnit": [
774
+ "points",
775
+ "percent"
776
+ ],
777
+ "goodWhen": [
778
+ "neutral",
779
+ "up",
780
+ "down"
781
+ ]
782
+ },
783
+ "detail": "dist/meta/StatTile.json",
784
+ "hasIntent": true
785
+ },
750
786
  "Stepper": {
751
787
  "summary": "Where the user is in a task that has an order. An ordered list of steps, each stating its position and status in text.",
752
788
  "status": "stable",
@@ -6,6 +6,7 @@
6
6
  "the settings are grouped and the groups are worth naming"
7
7
  ],
8
8
  "composes": [
9
+ "SettingRow",
9
10
  "Toggle",
10
11
  "Checkbox",
11
12
  "RadioGroup",
@@ -21,7 +22,7 @@
21
22
  ],
22
23
  "structure": [
23
24
  "Named groups, separated by Divider, each with a heading.",
24
- "One row per setting: label and description on the left, control on the right.",
25
+ "One SettingRow per setting: label and description on the left, control on the right, wired together by the row.",
25
26
  "For save-on-submit panels, a save bar that appears only when something is dirty, with save and discard.",
26
27
  "Tabs for panels large enough that a single scroll is unreasonable — but never with unsaved changes hidden behind an inactive tab."
27
28
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "ionbase-ui",
3
- "version": "0.69.0",
3
+ "version": "0.73.0",
4
4
  "usage": "A pattern is a documented composition of components, not a component. Read one here, then read the contracts of what it composes.",
5
5
  "patterns": {
6
6
  "AgentRun": {
@@ -147,6 +147,7 @@
147
147
  "SettingsPanel": {
148
148
  "summary": "Grouped settings that either apply immediately or are saved together — never both in one panel.",
149
149
  "composes": [
150
+ "SettingRow",
150
151
  "Toggle",
151
152
  "Checkbox",
152
153
  "RadioGroup",
@@ -90,6 +90,8 @@
90
90
  */
91
91
  @import url('./date-picker.css');
92
92
  @import url('./date-range-picker.css');
93
+ @import url('./stat-tile.css');
94
+ @import url('./setting-row.css');
93
95
 
94
96
  /*
95
97
  * Visually hidden, but not hidden from assistive tech.
@@ -160,13 +160,24 @@
160
160
  * very percentage the reader opened the panel to act on. Keyboard users are
161
161
  * unaffected either way: the rows are real buttons in tab order, so Tab
162
162
  * scrolls each one into view without touching the wheel.
163
+ *
164
+ * CLOSED IS `display: none`, NOT ONLY INVISIBLE
165
+ *
166
+ * `visibility: hidden` removes the panel from sight and from tab order, but a
167
+ * hidden box still counts toward its scroll container's overflow. A rail near
168
+ * the right edge of a page therefore widened the page by however far the
169
+ * closed panel reached past it — 152px in the demo app — and a phone scrolled
170
+ * sideways into empty space. `display: none` takes it out of layout entirely.
171
+ * The reveal survives: `transition-behavior: allow-discrete` holds `display`
172
+ * until the fade-out finishes, and `@starting-style` gives the fade-in a
173
+ * starting point. A browser without either simply shows and hides it without
174
+ * the fade. `visibility` stays in the transition for the same browsers.
163
175
  */
164
176
  .ion-scroll-progress__panel {
165
177
  position: absolute;
166
- left: 0;
167
178
  top: 50%;
168
179
  z-index: 10;
169
- display: flex;
180
+ display: none;
170
181
  flex-direction: column;
171
182
  gap: var(--spacing-4);
172
183
  width: 220px;
@@ -181,20 +192,47 @@
181
192
  opacity: 0;
182
193
  visibility: hidden;
183
194
  pointer-events: none;
184
- transform: translateY(-50%) translateX(-4px);
195
+ transform: translateY(-50%) translateX(var(--ion-scroll-progress-slide));
185
196
  transition:
186
197
  opacity var(--ion-duration-base) var(--ion-ease-out),
187
198
  transform var(--ion-duration-base) var(--ion-ease-out),
188
- visibility var(--ion-duration-base);
199
+ visibility var(--ion-duration-base),
200
+ display var(--ion-duration-base) allow-discrete;
201
+ }
202
+
203
+ /*
204
+ * Placement picks the edge the panel is anchored to and the direction it
205
+ * slides in from — always from the rail outward. `right` is the original
206
+ * behaviour; `left` is for a rail against the right edge of the page, where a
207
+ * rightward panel would leave the screen.
208
+ */
209
+ .ion-scroll-progress__panel--right {
210
+ --ion-scroll-progress-slide: -4px;
211
+
212
+ left: 0;
213
+ }
214
+
215
+ .ion-scroll-progress__panel--left {
216
+ --ion-scroll-progress-slide: 4px;
217
+
218
+ right: 0;
189
219
  }
190
220
 
191
221
  .ion-scroll-progress__panel[data-open='true'] {
222
+ display: flex;
192
223
  opacity: 1;
193
224
  visibility: visible;
194
225
  pointer-events: auto;
195
226
  transform: translateY(-50%) translateX(0);
196
227
  }
197
228
 
229
+ @starting-style {
230
+ .ion-scroll-progress__panel[data-open='true'] {
231
+ opacity: 0;
232
+ transform: translateY(-50%) translateX(var(--ion-scroll-progress-slide));
233
+ }
234
+ }
235
+
198
236
  /*
199
237
  * `display: block`, not `flex`: `text-overflow: ellipsis` does not apply to an
200
238
  * anonymous flex item, so a flex row here would clip the label with no ellipsis
@@ -0,0 +1,58 @@
1
+ /*
2
+ * SettingRow
3
+ *
4
+ * Promoted from the demo app's settings screen: the name and description on the
5
+ * left in `text/default` and `text/secondary`, the control on the right, 16px
6
+ * apart. The label is the Body/Default Semibold rung, the description a step
7
+ * down on body-sm.
8
+ *
9
+ * IT STACKS BY ITS OWN WIDTH
10
+ *
11
+ * The demo stacked the row below a 767px VIEWPORT, which is the wrong question:
12
+ * a settings row in a 360px drawer on a desktop is just as cramped as one on a
13
+ * phone. The row is a size container and the inner flex reads it, so it stacks
14
+ * wherever it is narrow. Container queries apply to descendants only, which is
15
+ * why there is an `__inner` at all.
16
+ */
17
+ .ion-setting-row {
18
+ container-type: inline-size;
19
+ font-family: var(--font-family-sans);
20
+ }
21
+
22
+ .ion-setting-row__inner {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ gap: var(--spacing-16);
27
+ }
28
+
29
+ .ion-setting-row__text {
30
+ display: grid;
31
+ gap: var(--spacing-2);
32
+ min-width: 0;
33
+ }
34
+
35
+ .ion-setting-row__label {
36
+ color: var(--text-default);
37
+ font-size: var(--type-body);
38
+ line-height: var(--type-body-line-height);
39
+ font-weight: var(--font-weight-semibold);
40
+ }
41
+
42
+ .ion-setting-row__description {
43
+ color: var(--text-secondary);
44
+ font-size: var(--type-body-sm);
45
+ line-height: var(--type-body-sm-line-height);
46
+ }
47
+
48
+ .ion-setting-row__control {
49
+ flex-shrink: 0;
50
+ }
51
+
52
+ @container (width < 30rem) {
53
+ .ion-setting-row__inner {
54
+ flex-direction: column;
55
+ align-items: flex-start;
56
+ gap: var(--spacing-8);
57
+ }
58
+ }
@@ -0,0 +1,66 @@
1
+ /*
2
+ * StatTile, StatGroup
3
+ *
4
+ * Promoted from the demo app's Overview KPI row, where the measurements were
5
+ * settled against a real dashboard rather than invented here: a card on
6
+ * `surface/default` with a `border/subtle` rule and the `lg` radius, the label
7
+ * a rung down in `text/secondary`, the figure on the h4 rung, and the change as
8
+ * a small Badge beside a caption.
9
+ *
10
+ * THE GRID NEEDS NO BREAKPOINT
11
+ *
12
+ * `auto-fit` with a minimum tile width wraps on its own: four tiles in a row on
13
+ * a desktop, two on a phone. The minimum is `min(100%, 10rem)` so a single tile
14
+ * in a container narrower than 10rem shrinks instead of overflowing it.
15
+ */
16
+ .ion-stat-group {
17
+ display: grid;
18
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr));
19
+ gap: var(--spacing-12);
20
+ margin: 0;
21
+ }
22
+
23
+ .ion-stat-tile {
24
+ display: grid;
25
+ align-content: start;
26
+ gap: var(--spacing-8);
27
+ padding: var(--spacing-16);
28
+ border: var(--border-width-default) solid var(--border-subtle);
29
+ border-radius: var(--radius-lg);
30
+ background-color: var(--surface-default);
31
+ font-family: var(--font-family-sans);
32
+ }
33
+
34
+ .ion-stat-tile__label {
35
+ color: var(--text-secondary);
36
+ font-size: var(--type-body-sm);
37
+ line-height: var(--type-body-sm-line-height);
38
+ }
39
+
40
+ .ion-stat-tile__body {
41
+ display: grid;
42
+ gap: var(--spacing-6);
43
+ margin: 0;
44
+ }
45
+
46
+ /* Tabular figures, so a row of tiles lines its digits up as values change. */
47
+ .ion-stat-tile__value {
48
+ color: var(--text-default);
49
+ font-size: var(--type-h4);
50
+ line-height: var(--type-h4-line-height);
51
+ font-weight: var(--font-weight-semibold);
52
+ font-variant-numeric: tabular-nums;
53
+ }
54
+
55
+ .ion-stat-tile__change {
56
+ display: inline-flex;
57
+ flex-wrap: wrap;
58
+ align-items: center;
59
+ gap: var(--spacing-6);
60
+ }
61
+
62
+ .ion-stat-tile__comparison {
63
+ color: var(--text-tertiary);
64
+ font-size: var(--type-caption);
65
+ line-height: var(--type-caption-line-height);
66
+ }
@@ -30,7 +30,16 @@
30
30
  * assuming "MIXED" meant "top and bottom". One `border-bottom`, not a
31
31
  * 4-sided border that needs collapsing.
32
32
  */
33
+
34
+ /*
35
+ * `position: relative` makes the scroll container a containing block. Without
36
+ * it, absolutely positioned content in a cell — the selection checkbox's real
37
+ * input, `.ion-visually-hidden` text, avatar media — is placed against the
38
+ * nearest positioned ancestor outside the table, escapes `overflow-x`, and
39
+ * widens the page: 382px of sideways scroll on a 390px phone in the demo app.
40
+ */
33
41
  .ion-table-container {
42
+ position: relative;
34
43
  width: 100%;
35
44
  overflow-x: auto;
36
45
  border: var(--border-width-default) solid var(--border-default);