@crewlethq/ui 0.15.1 → 0.17.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 (111) hide show
  1. package/README.md +36 -6
  2. package/dist/AppShell/AccountRow.d.ts +40 -0
  3. package/dist/AppShell/AppShell.d.ts +1 -0
  4. package/dist/AppShell/index.d.ts +2 -0
  5. package/dist/AppShell/index.js +1 -1
  6. package/dist/{AppShell-6QUD7NWM.css → AppShell-RUNVM4WH.css} +93 -0
  7. package/dist/AuthLayout/AuthLayout.d.ts +35 -0
  8. package/dist/AuthLayout/index.d.ts +3 -0
  9. package/dist/AuthLayout/index.js +8 -0
  10. package/dist/AuthLayout-ODHBUPHQ.css +54 -0
  11. package/dist/Avatar/Avatar.d.ts +37 -2
  12. package/dist/Avatar/index.d.ts +1 -1
  13. package/dist/Avatar/index.js +2 -2
  14. package/dist/{Avatar-I4NFP3WQ.css → Avatar-3C2H2WA4.css} +34 -0
  15. package/dist/AvatarPicker/index.js +4 -4
  16. package/dist/Callout/Callout.d.ts +7 -1
  17. package/dist/Callout/index.js +1 -1
  18. package/dist/{Callout-S7QDWHDL.css → Callout-SXXK4B5R.css} +3 -1
  19. package/dist/CharacterPicker/index.js +2 -2
  20. package/dist/Checkbox/CheckboxGroup.d.ts +30 -0
  21. package/dist/Checkbox/index.d.ts +2 -0
  22. package/dist/Checkbox/index.js +5 -1
  23. package/dist/{Checkbox-UE7TEPAM.css → Checkbox-KT7RS6QQ.css} +16 -0
  24. package/dist/CodeBlock/index.js +4 -3
  25. package/dist/Combobox/index.js +3 -3
  26. package/dist/CommandPalette/index.js +5 -5
  27. package/dist/Composer/index.js +3 -3
  28. package/dist/ConfirmField/ConfirmField.d.ts +43 -0
  29. package/dist/ConfirmField/index.d.ts +2 -0
  30. package/dist/ConfirmField/index.js +7 -0
  31. package/dist/Copyable/CopyButton.d.ts +13 -6
  32. package/dist/Copyable/index.js +3 -2
  33. package/dist/DataTable/index.js +7 -5
  34. package/dist/DataView/index.js +12 -11
  35. package/dist/DateTimePicker/index.js +2 -2
  36. package/dist/DownloadButton/DownloadButton.d.ts +52 -0
  37. package/dist/DownloadButton/index.d.ts +2 -0
  38. package/dist/DownloadButton/index.js +6 -0
  39. package/dist/EntityChip/index.js +3 -3
  40. package/dist/ImageUpload/index.js +3 -3
  41. package/dist/InlineCode/InlineCode.d.ts +8 -1
  42. package/dist/InlineCode/index.js +1 -1
  43. package/dist/{InlineCode-6WSR5COL.css → InlineCode-ASYHLN3E.css} +9 -0
  44. package/dist/Input/index.js +2 -2
  45. package/dist/{Input-RAKNQJCV.css → Input-3KVSNUO5.css} +11 -0
  46. package/dist/ListInput/index.js +3 -3
  47. package/dist/MarkdownEditor/index.js +1 -1
  48. package/dist/Modal/index.js +2 -2
  49. package/dist/ObjectHeader/ObjectHeader.d.ts +138 -0
  50. package/dist/ObjectHeader/index.d.ts +3 -0
  51. package/dist/ObjectHeader/index.js +4 -0
  52. package/dist/ObjectHeader-UNVYM2KD.css +174 -0
  53. package/dist/OrgLabel/index.js +3 -3
  54. package/dist/OrgNode/index.js +4 -4
  55. package/dist/OrgTable/index.js +4 -4
  56. package/dist/QrCode/QrCode.d.ts +58 -0
  57. package/dist/QrCode/index.d.ts +3 -0
  58. package/dist/QrCode/index.js +2 -0
  59. package/dist/QrCode-JWJTWMU5.css +19 -0
  60. package/dist/Switcher/index.js +4 -4
  61. package/dist/Text/Text.d.ts +7 -1
  62. package/dist/Text/index.js +1 -1
  63. package/dist/{Text-XIPYYZX2.css → Text-S3Q4O3IG.css} +11 -0
  64. package/dist/TimeWindowPicker/index.js +4 -4
  65. package/dist/{chunk-FTEJPHBI.js → chunk-4GB57WIT.js} +2 -2
  66. package/dist/{chunk-DDTDN5L2.js → chunk-4KWOKX2B.js} +1 -1
  67. package/dist/{chunk-NN62JQLR.js → chunk-5CLW2CXT.js} +3 -1
  68. package/dist/{chunk-HI6Q2UXQ.js → chunk-5OM447RW.js} +19 -7
  69. package/dist/{chunk-YXMCUKFI.js → chunk-5PSXBL73.js} +1 -1
  70. package/dist/{chunk-QSLIN64M.js → chunk-6NIMZ5XC.js} +1 -1
  71. package/dist/{chunk-YCTMKKRA.js → chunk-75CLJNDQ.js} +12 -2
  72. package/dist/chunk-7QFAORCY.js +37 -0
  73. package/dist/{chunk-22M7OXKW.js → chunk-AKEZ3G5V.js} +1 -1
  74. package/dist/chunk-BJCFNRGH.js +53 -0
  75. package/dist/chunk-CXBAIVYZ.js +39 -0
  76. package/dist/chunk-DTIW4CHZ.js +37 -0
  77. package/dist/chunk-F6NZOVWL.js +68 -0
  78. package/dist/{chunk-LCF4WPYS.js → chunk-FA2V2VT6.js} +2 -2
  79. package/dist/{chunk-KGTQ5CVD.js → chunk-GDDE4UM7.js} +1 -1
  80. package/dist/{chunk-EFNHOVC2.js → chunk-HENOTGUV.js} +2 -2
  81. package/dist/chunk-I7WXZOWA.js +90 -0
  82. package/dist/{chunk-23DHIMJA.js → chunk-IKBK4A5O.js} +1 -1
  83. package/dist/{chunk-DY77H372.js → chunk-JF7X673Z.js} +2 -2
  84. package/dist/{chunk-YYBD73OI.js → chunk-L4ZLXYPO.js} +1 -1
  85. package/dist/{chunk-H3XOMT7W.js → chunk-OYOQU7O5.js} +44 -3
  86. package/dist/{chunk-IYZZL3MZ.js → chunk-PQ63NLFC.js} +3 -3
  87. package/dist/{chunk-TK6YC5QT.js → chunk-RF5O4FYL.js} +4 -4
  88. package/dist/{chunk-5VNUYO76.js → chunk-STJ7QBC3.js} +8 -25
  89. package/dist/{chunk-R6FBDTP3.js → chunk-SVSCLHHI.js} +1 -1
  90. package/dist/{chunk-GARACZAH.js → chunk-T3ZWBZ65.js} +1 -1
  91. package/dist/{chunk-FY2R4U4Q.js → chunk-TBMDMXEX.js} +1 -1
  92. package/dist/{chunk-ZPWUVVIB.js → chunk-UAN5NE7M.js} +2 -2
  93. package/dist/{chunk-YACKC3K2.js → chunk-W46LPU4R.js} +8 -8
  94. package/dist/{chunk-4EOJR52M.js → chunk-XU4H6JQB.js} +4 -4
  95. package/dist/chunk-Y56FM3K4.js +57 -0
  96. package/dist/{chunk-DJMLHCLD.js → chunk-YDBC4DNW.js} +1 -1
  97. package/dist/{chunk-4Y7WKXAW.js → chunk-ZFH7H7GS.js} +1 -1
  98. package/dist/generated/index.d.ts +5 -0
  99. package/dist/generated/index.js +67 -60
  100. package/dist/index.js +67 -60
  101. package/dist/styles.css +440 -8
  102. package/dist/utils/index.d.ts +4 -0
  103. package/dist/utils/index.js +4 -2
  104. package/dist/utils/useClipboard.d.ts +6 -3
  105. package/dist/utils/useDownload.d.ts +63 -0
  106. package/dist/utils/useOutcome.d.ts +44 -0
  107. package/package.json +4 -3
  108. package/dist/{chunk-GUHP5JHC.js → chunk-2UU5IGZI.js} +1 -1
  109. package/dist/{chunk-6AR2T5Y4.js → chunk-CRVETI5L.js} +0 -0
  110. package/dist/{chunk-K4VORB64.js → chunk-DEEVA6TV.js} +1 -1
  111. /package/dist/{chunk-AER7UDD4.js → chunk-U5LQ2AS5.js} +0 -0
package/dist/styles.css CHANGED
@@ -537,7 +537,7 @@
537
537
  }
538
538
 
539
539
 
540
- /* AppShell-6QUD7NWM.css */
540
+ /* AppShell-RUNVM4WH.css */
541
541
  /*
542
542
  * The application frame: a rail on the FRAME, and the screen on a SHEET that
543
543
  * floats beside it.
@@ -1162,8 +1162,158 @@
1162
1162
  }
1163
1163
  }
1164
1164
 
1165
+ /* ── the account row ───────────────────────────────────────────────────── */
1165
1166
 
1166
- /* Avatar-I4NFP3WQ.css */
1167
+ /*
1168
+ * The signed-in reader at the foot of the rail, with the row's controls at
1169
+ * its end. The link takes the room, so a long name is cut rather than pushing
1170
+ * the controls off the rail.
1171
+ */
1172
+ .crewlet-account-row {
1173
+ display: flex;
1174
+ align-items: center;
1175
+ gap: var(--spacing-1);
1176
+ min-width: 0;
1177
+ padding: var(--spacing-1) 0;
1178
+ }
1179
+
1180
+ .crewlet-account-row__who {
1181
+ display: flex;
1182
+ flex: 1 1 auto;
1183
+ align-items: center;
1184
+ gap: var(--spacing-2);
1185
+ min-width: 0;
1186
+ padding: var(--spacing-1);
1187
+ border-radius: var(--radius-md);
1188
+ color: var(--color-text-primary);
1189
+ text-decoration: none;
1190
+ /* The fill eases under the pointer, as the rail's rows do. */
1191
+ transition: background var(--motion-duration-fast) var(--motion-easing-default);
1192
+ }
1193
+
1194
+ a.crewlet-account-row__who:hover {
1195
+ background: var(--color-surface-hover);
1196
+ }
1197
+
1198
+ /* After :hover, so the current row shows the lift rather than the overlay. */
1199
+ a.crewlet-account-row__who[aria-current='page'] {
1200
+ background: var(--color-surface-elevated);
1201
+ outline: 1px solid var(--color-border-default);
1202
+ outline-offset: -1px;
1203
+ }
1204
+
1205
+ a.crewlet-account-row__who:focus-visible {
1206
+ outline: 2px solid var(--color-focus);
1207
+ outline-offset: var(--size-focus-ring-inset-offset);
1208
+ }
1209
+
1210
+ .crewlet-account-row__avatar {
1211
+ display: inline-flex;
1212
+ flex: none;
1213
+ }
1214
+
1215
+ .crewlet-account-row__text {
1216
+ display: flex;
1217
+ flex-direction: column;
1218
+ min-width: 0;
1219
+ line-height: var(--font-line-height-snug);
1220
+ }
1221
+
1222
+ .crewlet-account-row__name,
1223
+ .crewlet-account-row__detail {
1224
+ overflow: hidden;
1225
+ text-overflow: ellipsis;
1226
+ white-space: nowrap;
1227
+ }
1228
+
1229
+ .crewlet-account-row__name {
1230
+ font-size: var(--font-size-xs);
1231
+ font-weight: var(--font-weight-medium);
1232
+ }
1233
+
1234
+ .crewlet-account-row__detail {
1235
+ font-size: var(--font-size-2xs);
1236
+ color: var(--color-text-tertiary);
1237
+ }
1238
+
1239
+ .crewlet-account-row__go {
1240
+ flex: none;
1241
+ margin-left: auto;
1242
+ color: var(--color-text-tertiary);
1243
+ }
1244
+
1245
+ .crewlet-account-row__actions {
1246
+ display: flex;
1247
+ flex: none;
1248
+ align-items: center;
1249
+ gap: var(--spacing-1);
1250
+ }
1251
+
1252
+ @media (prefers-reduced-motion: reduce) {
1253
+ .crewlet-account-row__who {
1254
+ transition: none;
1255
+ }
1256
+ }
1257
+
1258
+
1259
+ /* AuthLayout-ODHBUPHQ.css */
1260
+ /*
1261
+ * The sign-in page: one narrow column near the top of the window.
1262
+ *
1263
+ * THE PAGE SCROLLS ITSELF, at the viewport's dynamic height, because it is
1264
+ * drawn outside any shell that would: on a phone with the keyboard up, a form
1265
+ * taller than what is left of the window has to scroll, not be cut off.
1266
+ */
1267
+ .crewlet-auth-layout {
1268
+ display: flex;
1269
+ justify-content: center;
1270
+ height: 100dvh;
1271
+ overflow-y: auto;
1272
+ padding: var(--spacing-8) var(--spacing-4);
1273
+ background: var(--color-surface-background);
1274
+ }
1275
+
1276
+ /* A sentence's measure wide, and held at the top: the form does not jump when
1277
+ an error line appears under a field. */
1278
+ .crewlet-auth-layout__column {
1279
+ display: flex;
1280
+ flex-direction: column;
1281
+ gap: var(--spacing-5);
1282
+ width: 100%;
1283
+ max-width: 52ch;
1284
+ margin-block-end: auto;
1285
+ }
1286
+
1287
+ /* The lockup is centred over the card, and takes none of the rail's inset,
1288
+ which is what its own padding is for. */
1289
+ .crewlet-auth-layout__brand {
1290
+ align-self: center;
1291
+ padding: 0;
1292
+ }
1293
+
1294
+ /* The form's parts stand one field step apart, under the title and lede. */
1295
+ .crewlet-auth-layout__body {
1296
+ display: flex;
1297
+ flex-direction: column;
1298
+ gap: var(--spacing-4);
1299
+ margin-top: var(--spacing-4);
1300
+ }
1301
+
1302
+ /* A submit does not stretch: every button here states one gesture, and a
1303
+ full-width bar reads as the page's edge rather than a control. */
1304
+ .crewlet-auth-layout__body > form {
1305
+ display: flex;
1306
+ flex-direction: column;
1307
+ gap: var(--spacing-4);
1308
+ }
1309
+
1310
+ .crewlet-auth-layout__body > form > button,
1311
+ .crewlet-auth-layout__body > button {
1312
+ align-self: flex-start;
1313
+ }
1314
+
1315
+
1316
+ /* Avatar-3C2H2WA4.css */
1167
1317
  /*
1168
1318
  * The identity badge.
1169
1319
  *
@@ -1292,6 +1442,25 @@
1292
1442
  object-fit: cover;
1293
1443
  }
1294
1444
 
1445
+ /*
1446
+ * A GLYPH IN PLACE OF THE INITIALS, for a principal whose name's initials
1447
+ * mean nothing (an API token, a service account). It takes a little over half
1448
+ * the box, the share the initials' cap height and width take, so a key badge
1449
+ * beside a person's badge reads as the same size of mark.
1450
+ */
1451
+ .crewlet-avatar__icon {
1452
+ display: inline-flex;
1453
+ align-items: center;
1454
+ justify-content: center;
1455
+ width: 55%;
1456
+ height: 55%;
1457
+ }
1458
+
1459
+ .crewlet-avatar__icon > * {
1460
+ width: 100%;
1461
+ height: 100%;
1462
+ }
1463
+
1295
1464
  /* ── tones ─────────────────────────────────────────────────────────────── */
1296
1465
 
1297
1466
  .crewlet-avatar--fallback {
@@ -1487,6 +1656,21 @@
1487
1656
  background: var(--color-feedback-danger);
1488
1657
  }
1489
1658
 
1659
+ /*
1660
+ * A MARK (`mark`): how a principal is present, not a state, so it is drawn
1661
+ * quiet rather than in a state's fill: the neutral badge's raised rung, its
1662
+ * strong hairline and its secondary ink. The hairline is the corner's own
1663
+ * outline, inset by its width, so it keeps the corner's size and survives
1664
+ * forced colours.
1665
+ */
1666
+ .crewlet-avatar__status--mark {
1667
+ padding: 1px;
1668
+ background: var(--color-surface-elevated);
1669
+ outline-color: var(--color-border-strong);
1670
+ outline-offset: -1px;
1671
+ color: var(--color-text-secondary);
1672
+ }
1673
+
1490
1674
 
1491
1675
  /* AvatarPicker-PREY36MG.css */
1492
1676
  /*
@@ -2418,7 +2602,7 @@ a.crewlet-breadcrumb__crumb:focus-visible {
2418
2602
  }
2419
2603
 
2420
2604
 
2421
- /* Callout-S7QDWHDL.css */
2605
+ /* Callout-SXXK4B5R.css */
2422
2606
  /*
2423
2607
  * The callout: a status that stays in the content flow.
2424
2608
  *
@@ -2473,8 +2657,10 @@ a.crewlet-breadcrumb__crumb:focus-visible {
2473
2657
  }
2474
2658
 
2475
2659
  .crewlet-callout__title {
2660
+ /* A LINE OF ITS OWN above the message, so the heading and the first
2661
+ sentence are never read as one run-on sentence. */
2662
+ display: block;
2476
2663
  font-weight: var(--font-weight-semibold);
2477
- margin-right: var(--spacing-2);
2478
2664
  }
2479
2665
 
2480
2666
  .crewlet-callout__action {
@@ -4219,7 +4405,7 @@ a.crewlet-breadcrumb__crumb:focus-visible {
4219
4405
  }
4220
4406
 
4221
4407
 
4222
- /* Checkbox-UE7TEPAM.css */
4408
+ /* Checkbox-KT7RS6QQ.css */
4223
4409
  /*
4224
4410
  * Checkbox: a box, its label and its consequence as one target.
4225
4411
  *
@@ -4340,6 +4526,22 @@ a.crewlet-breadcrumb__crumb:focus-visible {
4340
4526
  outline-offset: 0;
4341
4527
  }
4342
4528
 
4529
+ /*
4530
+ * A group of boxes under one legend. The boxes keep a field's inner rhythm,
4531
+ * one step apart, so the group reads as one field and its boxes as its parts.
4532
+ */
4533
+ .crewlet-checkbox-group__options {
4534
+ display: flex;
4535
+ flex-direction: column;
4536
+ gap: var(--spacing-1);
4537
+ }
4538
+
4539
+ .crewlet-checkbox-group__empty {
4540
+ margin: 0;
4541
+ font-size: var(--font-size-xs);
4542
+ color: var(--color-text-secondary);
4543
+ }
4544
+
4343
4545
 
4344
4546
  /* ChoiceButtons-DJ5EOGUA.css */
4345
4547
  /*
@@ -9953,7 +10155,7 @@ svg.crewlet-datetime__chevron {
9953
10155
  }
9954
10156
 
9955
10157
 
9956
- /* InlineCode-6WSR5COL.css */
10158
+ /* InlineCode-ASYHLN3E.css */
9957
10159
  /*
9958
10160
  * One identifier inside a sentence: a mono chip on the inset surface.
9959
10161
  *
@@ -10011,6 +10213,15 @@ svg.crewlet-datetime__chevron {
10011
10213
  color: inherit;
10012
10214
  }
10013
10215
 
10216
+ /*
10217
+ * A short token read as one: it moves to the next line whole rather than
10218
+ * splitting, which `overflow-wrap: anywhere` would otherwise allow.
10219
+ */
10220
+ .crewlet-inline-code--whole {
10221
+ overflow-wrap: normal;
10222
+ white-space: nowrap;
10223
+ }
10224
+
10014
10225
  .crewlet-inline-code--truncate {
10015
10226
  display: inline-block;
10016
10227
  max-width: 100%;
@@ -10021,7 +10232,7 @@ svg.crewlet-datetime__chevron {
10021
10232
  }
10022
10233
 
10023
10234
 
10024
- /* Input-RAKNQJCV.css */
10235
+ /* Input-3KVSNUO5.css */
10025
10236
  /*
10026
10237
  * The form row: Input, Textarea, Label, FormField, FormRow, FormSection and
10027
10238
  * ReadOnlyField.
@@ -10459,6 +10670,17 @@ svg.crewlet-datetime__chevron {
10459
10670
  color: var(--color-text-tertiary);
10460
10671
  }
10461
10672
 
10673
+ /*
10674
+ * A LITERAL INSIDE A LABEL KEEPS ITS CASE. The label is set in capitals, and
10675
+ * a value a reader has to type back exactly ("Type jane.founder to confirm")
10676
+ * read as JANE.FOUNDER, which is not what the field accepts.
10677
+ */
10678
+ .crewlet-label code,
10679
+ .crewlet-label__text code {
10680
+ text-transform: none;
10681
+ letter-spacing: normal;
10682
+ }
10683
+
10462
10684
  .crewlet-label--with-action {
10463
10685
  display: flex;
10464
10686
  justify-content: space-between;
@@ -12755,6 +12977,183 @@ svg.crewlet-datetime__chevron {
12755
12977
  }
12756
12978
 
12757
12979
 
12980
+ /* ObjectHeader-UNVYM2KD.css */
12981
+ /*
12982
+ * The header of an object: its eyebrow, its head row and its fact line.
12983
+ */
12984
+ .crewlet-object-header {
12985
+ display: flex;
12986
+ flex-direction: column;
12987
+ gap: var(--spacing-1);
12988
+ min-width: 0;
12989
+ padding: var(--spacing-4) 0 var(--spacing-3);
12990
+ }
12991
+
12992
+ .crewlet-object-header--peek {
12993
+ padding: var(--spacing-3) 0 var(--spacing-2);
12994
+ }
12995
+
12996
+ .crewlet-object-header__eyebrow {
12997
+ display: flex;
12998
+ align-items: center;
12999
+ gap: var(--spacing-2);
13000
+ min-width: 0;
13001
+ font-size: var(--font-size-2xs);
13002
+ letter-spacing: var(--font-letter-spacing-wide);
13003
+ text-transform: uppercase;
13004
+ color: var(--color-text-tertiary);
13005
+ }
13006
+
13007
+ .crewlet-object-header__icon {
13008
+ display: inline-flex;
13009
+ flex: none;
13010
+ }
13011
+
13012
+ /* A key or a name keeps its own case: in capitals a name is a shout. */
13013
+ .crewlet-object-header__id {
13014
+ min-width: 0;
13015
+ overflow: hidden;
13016
+ text-overflow: ellipsis;
13017
+ white-space: nowrap;
13018
+ letter-spacing: normal;
13019
+ text-transform: none;
13020
+ }
13021
+
13022
+ .crewlet-object-header__id--mono {
13023
+ font-family: var(--font-family-mono);
13024
+ }
13025
+
13026
+ /* The head row wraps: the marks and the actions take a line under the title
13027
+ rather than every pill on the row giving up a few characters. */
13028
+ .crewlet-object-header__head {
13029
+ display: flex;
13030
+ flex-wrap: wrap;
13031
+ align-items: center;
13032
+ gap: var(--spacing-2);
13033
+ min-width: 0;
13034
+ }
13035
+
13036
+ /* TWO LINES, AND THAT IS THE CEILING, which catches a title written as a
13037
+ paragraph and holds every real one. */
13038
+ .crewlet-object-header__title {
13039
+ display: -webkit-box;
13040
+ -webkit-box-orient: vertical;
13041
+ -webkit-line-clamp: 2;
13042
+ line-clamp: 2;
13043
+ overflow: hidden;
13044
+ flex: 0 1 auto;
13045
+ min-width: 0;
13046
+ margin: 0;
13047
+ font-size: var(--font-size-xl);
13048
+ font-weight: var(--font-weight-semibold);
13049
+ line-height: var(--font-line-height-snug);
13050
+ color: var(--color-text-primary);
13051
+ }
13052
+
13053
+ .crewlet-object-header--peek .crewlet-object-header__title {
13054
+ font-size: var(--font-size-lg);
13055
+ }
13056
+
13057
+ /* The state keeps its width: a pill squeezed to "13 pro…" says something
13058
+ else. The marks travel together when the row wraps. */
13059
+ .crewlet-object-header__status,
13060
+ .crewlet-object-header__actions {
13061
+ display: flex;
13062
+ flex: 0 0 auto;
13063
+ flex-wrap: wrap;
13064
+ align-items: center;
13065
+ gap: var(--spacing-1);
13066
+ }
13067
+
13068
+ .crewlet-object-header__actions {
13069
+ margin-left: auto;
13070
+ }
13071
+
13072
+ /* ── the fact line ─────────────────────────────────────────────────────── */
13073
+
13074
+ /* THE TRACK FLOOR IS 7.5rem, which fits seven facts on one line at 1280px and
13075
+ still holds every label and a two-line value. */
13076
+ .crewlet-fact-line {
13077
+ display: grid;
13078
+ grid-template-columns: repeat(auto-fill, minmax(min(7.5rem, 100%), 1fr));
13079
+ column-gap: var(--spacing-4);
13080
+ row-gap: var(--spacing-3);
13081
+ padding-top: var(--spacing-1);
13082
+ }
13083
+
13084
+ /* Two bands: the label, then the value with its footnotes in one cell. */
13085
+ .crewlet-fact-line__fact {
13086
+ display: grid;
13087
+ grid-template-rows: subgrid;
13088
+ grid-row: span 2;
13089
+ row-gap: 0;
13090
+ min-width: 0;
13091
+ }
13092
+
13093
+ /* A token and a set of chips take two tracks. */
13094
+ .crewlet-fact-line__fact--token,
13095
+ .crewlet-fact-line__fact--set {
13096
+ grid-column: span 2;
13097
+ }
13098
+
13099
+ .crewlet-fact-line__body {
13100
+ display: flex;
13101
+ flex-direction: column;
13102
+ min-width: 0;
13103
+ }
13104
+
13105
+ .crewlet-fact-line__label {
13106
+ font-size: var(--font-size-2xs);
13107
+ letter-spacing: var(--font-letter-spacing-wide);
13108
+ text-transform: uppercase;
13109
+ color: var(--color-text-tertiary);
13110
+ }
13111
+
13112
+ .crewlet-fact-line__value {
13113
+ min-width: 0;
13114
+ font-size: var(--font-size-compact);
13115
+ color: var(--color-text-primary);
13116
+ overflow-wrap: anywhere;
13117
+ }
13118
+
13119
+ .crewlet-fact-line__value--clamp {
13120
+ display: -webkit-box;
13121
+ -webkit-box-orient: vertical;
13122
+ -webkit-line-clamp: 2;
13123
+ line-clamp: 2;
13124
+ overflow: hidden;
13125
+ }
13126
+
13127
+ /* One token, one line, cut with an ellipsis where two tracks are too narrow;
13128
+ its title carries the whole of it. */
13129
+ .crewlet-fact-line__value--token {
13130
+ display: block;
13131
+ overflow: hidden;
13132
+ text-overflow: ellipsis;
13133
+ white-space: nowrap;
13134
+ overflow-wrap: normal;
13135
+ }
13136
+
13137
+ /* A footnote may not set its column's width: the track does, and the note
13138
+ wraps inside it, balanced so the wrap reads as deliberate. */
13139
+ .crewlet-fact-line__note,
13140
+ .crewlet-fact-line__set-by {
13141
+ font-size: var(--font-size-2xs);
13142
+ color: var(--color-text-tertiary);
13143
+ text-wrap: balance;
13144
+ }
13145
+
13146
+ /* ── the by-line ───────────────────────────────────────────────────────── */
13147
+
13148
+ /* The word and its arrow are one atomic box, so the arrow never takes a line
13149
+ of its own. */
13150
+ .crewlet-set-by__turn {
13151
+ display: inline-flex;
13152
+ align-items: center;
13153
+ gap: 0.25em;
13154
+ }
13155
+
13156
+
12758
13157
  /* OrgLabel-R5KY7C3W.css */
12759
13158
  /*
12760
13159
  * What a node of an org chart and a row of an org table both say
@@ -14482,6 +14881,28 @@ svg.crewlet-datetime__chevron {
14482
14881
  }
14483
14882
 
14484
14883
 
14884
+ /* QrCode-JWJTWMU5.css */
14885
+ /*
14886
+ * A QR code on its own plate.
14887
+ *
14888
+ * A FIXED BOX, because a camera needs modules of a readable size: at 208px a
14889
+ * second factor's code (a version 7 symbol and its quiet zone) draws each
14890
+ * module at about 4.3px. A consumer that needs another size sets the variable.
14891
+ *
14892
+ * ITS INKS ARE KEPT UNDER FORCED COLOURS. A high-contrast mode repaints fills
14893
+ * to system colours, and a plate repainted dark is a code no scanner reads.
14894
+ */
14895
+ .crewlet-qr-code {
14896
+ --crewlet-qr-code-size: 208px;
14897
+ display: block;
14898
+ flex: none;
14899
+ width: var(--crewlet-qr-code-size);
14900
+ height: var(--crewlet-qr-code-size);
14901
+ border-radius: var(--radius-sm);
14902
+ forced-color-adjust: none;
14903
+ }
14904
+
14905
+
14485
14906
  /* ReadingPane-5UOLPWY5.css */
14486
14907
  /*
14487
14908
  * One item read in full, in one column.
@@ -18969,7 +19390,7 @@ b.crewlet-switcher__match {
18969
19390
  }
18970
19391
 
18971
19392
 
18972
- /* Text-XIPYYZX2.css */
19393
+ /* Text-S3Q4O3IG.css */
18973
19394
  /*
18974
19395
  * The type registers, one rule each.
18975
19396
  *
@@ -19021,6 +19442,17 @@ b.crewlet-switcher__match {
19021
19442
  color: var(--color-text-primary);
19022
19443
  }
19023
19444
 
19445
+ /*
19446
+ * The register around it: nothing is set, so the run keeps its sentence's
19447
+ * size, weight, tracking and ink, and only a modifier (mono, numeric) or a
19448
+ * tone changes it.
19449
+ */
19450
+ .crewlet-text--inherit {
19451
+ font-size: inherit;
19452
+ line-height: inherit;
19453
+ color: inherit;
19454
+ }
19455
+
19024
19456
  .crewlet-text--body {
19025
19457
  font-size: var(--font-size-sm);
19026
19458
  line-height: var(--font-line-height-normal);
@@ -1,6 +1,10 @@
1
1
  export { cx } from './cx.js';
2
2
  export { useClipboard, writeClipboard } from './useClipboard.js';
3
3
  export type { Clipboard, ClipboardOptions, ClipboardState } from './useClipboard.js';
4
+ export { useOutcome } from './useOutcome.js';
5
+ export type { Outcome, OutcomeOptions, OutcomeState } from './useOutcome.js';
6
+ export { downloadText, safeFilename, useDownload } from './useDownload.js';
7
+ export type { Download, DownloadState } from './useDownload.js';
4
8
  export type { Tone } from './tone.js';
5
9
  export { NODE_HUES, NODE_HUE_NAMES, isNodeHue } from './nodeHue.js';
6
10
  export type { NodeHue } from './nodeHue.js';
@@ -1,5 +1,7 @@
1
- import '../chunk-AER7UDD4.js';
2
- export { useClipboard, writeClipboard } from '../chunk-5VNUYO76.js';
1
+ import '../chunk-U5LQ2AS5.js';
2
+ export { downloadText, safeFilename, useDownload } from '../chunk-F6NZOVWL.js';
3
+ export { useClipboard, writeClipboard } from '../chunk-STJ7QBC3.js';
4
+ export { useOutcome } from '../chunk-DTIW4CHZ.js';
3
5
  export { HeadingLevelProvider, headingTag, nextHeadingLevel, useHeadingLevel } from '../chunk-PQADV6P6.js';
4
6
  export { NODE_HUES, NODE_HUE_NAMES, isNodeHue } from '../chunk-AYKXMIOR.js';
5
7
  export { cx } from '../chunk-ODEQTPFM.js';
@@ -33,6 +33,8 @@ export interface ClipboardOptions {
33
33
  }
34
34
  export interface Clipboard {
35
35
  state: ClipboardState;
36
+ /** How many copies have settled. See [Outcome.attempt]. */
37
+ attempt: number;
36
38
  copy: (text: string | (() => string)) => Promise<boolean>;
37
39
  }
38
40
  /**
@@ -56,9 +58,10 @@ export interface Clipboard {
56
58
  * timer exists AT THAT MOMENT, and for a write still in flight that is
57
59
  * none: the settle then ran after the cleanup and armed a fresh timer with
58
60
  * nothing left alive to clear it, so the hook leaked a timer per abandoned
59
- * copy and wrote state to a component that was gone. `live` is what the
60
- * settle is checked against, and it is checked AFTER the await rather than
61
- * before, because the await is the whole of the gap.
61
+ * copy and wrote state to a component that was gone. [useOutcome], the
62
+ * machine this and `useDownload` share, checks whether the component is
63
+ * still mounted AFTER the await rather than before, because the await is
64
+ * the whole of the gap.
62
65
  *
63
66
  * The text may be a THUNK, resolved on the press. On a live screen the thing
64
67
  * worth copying is assembled from everything on it, so a string prop means
@@ -0,0 +1,63 @@
1
+ import { type OutcomeOptions } from './useOutcome.js';
2
+ /** Where a download stands: not tried, handed to the browser, or refused. */
3
+ export type DownloadState = 'idle' | 'started' | 'failed';
4
+ export interface Download {
5
+ state: DownloadState;
6
+ /** How many downloads have settled. See [Outcome.attempt]. */
7
+ attempt: number;
8
+ /**
9
+ * Offers `text` as a file, and answers whether the browser took it. The
10
+ * text may be a thunk, resolved on the press.
11
+ */
12
+ download: (text: string | (() => string), filename: string, mime?: string) => Promise<boolean>;
13
+ }
14
+ /**
15
+ * Handing text to the reader as a file, and saying whether it landed: the
16
+ * mirror of `useClipboard`, on the same state machine.
17
+ *
18
+ * "STARTED", NOT "SAVED". There is no completion event on an `<a download>`:
19
+ * the browser took the click, and a gate such as Chrome's automatic multiple
20
+ * download prompt can still stop it silently after that. What the control can
21
+ * honestly say is that the download was handed over, and under which name.
22
+ */
23
+ export declare function useDownload(options?: OutcomeOptions): Download;
24
+ /**
25
+ * Saves `text` on the reader's machine and reports whether the browser took
26
+ * it, with the name it was offered under ([safeFilename] of `filename`).
27
+ *
28
+ * For a control that cannot say so on itself, such as a menu item that
29
+ * closes on the press: the caller raises a toast from the answer.
30
+ *
31
+ * TWO THINGS ARE CHECKED UP FRONT rather than assumed. Without
32
+ * `URL.createObjectURL` there is nothing to point a saveable link at, and a
33
+ * `data:` URL is the wrong fallback because several engines cap it near two
34
+ * megabytes, so it would fail silently on exactly the large files. Without the
35
+ * `download` attribute the click NAVIGATES to the text instead of saving it,
36
+ * which looks enough like something happening that nobody checks.
37
+ *
38
+ * The blob URL is revoked on the NEXT macrotask: the click only queues the
39
+ * download, and freeing the entry inside the same task races the fetch that is
40
+ * about to read it. Never revoking is the other failure, a second copy of the
41
+ * text held for the life of the tab.
42
+ */
43
+ export declare function downloadText(text: string, filename: string, mime?: string): {
44
+ started: boolean;
45
+ name: string;
46
+ };
47
+ /**
48
+ * A filename the reader will actually get, out of whatever the caller had.
49
+ *
50
+ * The `download` attribute is only a suggestion and each browser sanitises it
51
+ * its own way, so the name is decided here and every caller gets one
52
+ * predictable answer. A name composed from data (an id off the URL) that
53
+ * arrived as `../etc/passwd` or with a newline in it never reaches that guess.
54
+ *
55
+ * UNICODE-AWARE: letters, marks and numbers in every script are kept, and
56
+ * separators and invisibles (a slash, a colon, the fullwidth solidus, a
57
+ * right-to-left override) become a hyphen. An ASCII-only class turned
58
+ * `日本語.json` into a file called `json`. The EXTENSION is never what gets cut
59
+ * or stripped, because a name that loses it opens in the wrong application;
60
+ * leading dots, which make `..` a traversal and `.name` a hidden file, are
61
+ * stripped from the stem.
62
+ */
63
+ export declare function safeFilename(name: string): string;