@crewlethq/ui 0.15.0 → 0.16.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 (113) hide show
  1. package/README.md +36 -6
  2. package/dist/AddPill/index.js +1 -1
  3. package/dist/{AddPill-V4TQXJ53.css → AddPill-RYCY56P4.css} +3 -1
  4. package/dist/AppShell/AccountRow.d.ts +40 -0
  5. package/dist/AppShell/AppShell.d.ts +1 -0
  6. package/dist/AppShell/index.d.ts +2 -0
  7. package/dist/AppShell/index.js +1 -1
  8. package/dist/{AppShell-6QUD7NWM.css → AppShell-RUNVM4WH.css} +93 -0
  9. package/dist/AuthLayout/AuthLayout.d.ts +35 -0
  10. package/dist/AuthLayout/index.d.ts +3 -0
  11. package/dist/AuthLayout/index.js +8 -0
  12. package/dist/AuthLayout-ODHBUPHQ.css +54 -0
  13. package/dist/Avatar/Avatar.d.ts +22 -2
  14. package/dist/Avatar/index.d.ts +1 -1
  15. package/dist/Avatar/index.js +2 -2
  16. package/dist/{Avatar-BPJ36QZR.css → Avatar-MBUPR5E5.css} +22 -3
  17. package/dist/AvatarPicker/index.js +4 -4
  18. package/dist/Callout/Callout.d.ts +7 -1
  19. package/dist/Callout/index.js +1 -1
  20. package/dist/{Callout-S7QDWHDL.css → Callout-SXXK4B5R.css} +3 -1
  21. package/dist/CharacterPicker/index.js +2 -2
  22. package/dist/Checkbox/CheckboxGroup.d.ts +30 -0
  23. package/dist/Checkbox/index.d.ts +2 -0
  24. package/dist/Checkbox/index.js +5 -1
  25. package/dist/{Checkbox-UE7TEPAM.css → Checkbox-KT7RS6QQ.css} +16 -0
  26. package/dist/CodeBlock/index.js +4 -3
  27. package/dist/Combobox/index.js +3 -3
  28. package/dist/CommandPalette/index.js +5 -5
  29. package/dist/Composer/index.js +3 -3
  30. package/dist/ConfirmField/ConfirmField.d.ts +43 -0
  31. package/dist/ConfirmField/index.d.ts +2 -0
  32. package/dist/ConfirmField/index.js +7 -0
  33. package/dist/Copyable/CopyButton.d.ts +13 -6
  34. package/dist/Copyable/index.js +3 -2
  35. package/dist/DataTable/index.js +7 -5
  36. package/dist/DataView/index.js +10 -9
  37. package/dist/DownloadButton/DownloadButton.d.ts +52 -0
  38. package/dist/DownloadButton/index.d.ts +2 -0
  39. package/dist/DownloadButton/index.js +6 -0
  40. package/dist/EntityChip/index.js +3 -3
  41. package/dist/ImageUpload/index.js +3 -3
  42. package/dist/InlineCode/InlineCode.d.ts +8 -1
  43. package/dist/InlineCode/index.js +1 -1
  44. package/dist/{InlineCode-6WSR5COL.css → InlineCode-ASYHLN3E.css} +9 -0
  45. package/dist/Input/index.js +2 -2
  46. package/dist/{Input-RAKNQJCV.css → Input-3KVSNUO5.css} +11 -0
  47. package/dist/ListInput/index.js +3 -3
  48. package/dist/MarkdownEditor/index.js +1 -1
  49. package/dist/Modal/index.js +2 -2
  50. package/dist/ObjectHeader/ObjectHeader.d.ts +138 -0
  51. package/dist/ObjectHeader/index.d.ts +3 -0
  52. package/dist/ObjectHeader/index.js +4 -0
  53. package/dist/ObjectHeader-UNVYM2KD.css +174 -0
  54. package/dist/OrgLabel/index.js +3 -3
  55. package/dist/{OrgLabel-6WSKJQOT.css → OrgLabel-R5KY7C3W.css} +6 -1
  56. package/dist/OrgNode/index.js +4 -4
  57. package/dist/OrgTable/index.js +5 -5
  58. package/dist/QrCode/QrCode.d.ts +58 -0
  59. package/dist/QrCode/index.d.ts +3 -0
  60. package/dist/QrCode/index.js +2 -0
  61. package/dist/QrCode-JWJTWMU5.css +19 -0
  62. package/dist/Switcher/index.js +4 -4
  63. package/dist/Text/Text.d.ts +7 -1
  64. package/dist/Text/index.js +1 -1
  65. package/dist/{Text-XIPYYZX2.css → Text-S3Q4O3IG.css} +11 -0
  66. package/dist/TimeWindowPicker/index.js +3 -3
  67. package/dist/{chunk-FTEJPHBI.js → chunk-4GB57WIT.js} +2 -2
  68. package/dist/{chunk-NN62JQLR.js → chunk-5CLW2CXT.js} +3 -1
  69. package/dist/{chunk-2GXTIK37.js → chunk-5EFRWWGX.js} +3 -3
  70. package/dist/{chunk-6OF2DBKY.js → chunk-6DQWF5Y4.js} +2 -2
  71. package/dist/{chunk-QSLIN64M.js → chunk-6NIMZ5XC.js} +1 -1
  72. package/dist/{chunk-YCTMKKRA.js → chunk-75CLJNDQ.js} +12 -2
  73. package/dist/chunk-7QFAORCY.js +37 -0
  74. package/dist/chunk-BJCFNRGH.js +53 -0
  75. package/dist/{chunk-IHGKPEEV.js → chunk-CGNQPGJL.js} +1 -1
  76. package/dist/{chunk-DDTDN5L2.js → chunk-CKB3OHL3.js} +1 -1
  77. package/dist/chunk-CXBAIVYZ.js +39 -0
  78. package/dist/chunk-DTIW4CHZ.js +37 -0
  79. package/dist/{chunk-YXMCUKFI.js → chunk-DZ2YR6MV.js} +1 -1
  80. package/dist/{chunk-QV266R6G.js → chunk-EIOTVNYX.js} +4 -4
  81. package/dist/chunk-F6NZOVWL.js +68 -0
  82. package/dist/{chunk-LCF4WPYS.js → chunk-FA2V2VT6.js} +2 -2
  83. package/dist/{chunk-EFNHOVC2.js → chunk-FAEN3HYI.js} +2 -2
  84. package/dist/{chunk-HI6Q2UXQ.js → chunk-FY4SP3HF.js} +11 -3
  85. package/dist/{chunk-KGTQ5CVD.js → chunk-GDDE4UM7.js} +1 -1
  86. package/dist/chunk-I7WXZOWA.js +90 -0
  87. package/dist/{chunk-23DHIMJA.js → chunk-IKBK4A5O.js} +1 -1
  88. package/dist/{chunk-7BZPYA5P.js → chunk-JF7X673Z.js} +1 -1
  89. package/dist/{chunk-DJMLHCLD.js → chunk-KLESCRTS.js} +1 -1
  90. package/dist/{chunk-DYHSQHAZ.js → chunk-OFMPIRGD.js} +2 -2
  91. package/dist/{chunk-H3XOMT7W.js → chunk-OYOQU7O5.js} +44 -3
  92. package/dist/{chunk-IYZZL3MZ.js → chunk-PQ63NLFC.js} +3 -3
  93. package/dist/{chunk-5VNUYO76.js → chunk-STJ7QBC3.js} +8 -25
  94. package/dist/{chunk-R6FBDTP3.js → chunk-SVSCLHHI.js} +1 -1
  95. package/dist/{chunk-GARACZAH.js → chunk-T3ZWBZ65.js} +1 -1
  96. package/dist/{chunk-74WGARJE.js → chunk-TROLVEZY.js} +1 -1
  97. package/dist/{chunk-6NIZCQUM.js → chunk-UAMDRJD6.js} +2 -2
  98. package/dist/{chunk-YACKC3K2.js → chunk-W46LPU4R.js} +8 -8
  99. package/dist/chunk-Y56FM3K4.js +57 -0
  100. package/dist/{chunk-4Y7WKXAW.js → chunk-ZFH7H7GS.js} +1 -1
  101. package/dist/generated/index.d.ts +5 -0
  102. package/dist/generated/index.js +66 -59
  103. package/dist/index.js +66 -59
  104. package/dist/styles.css +439 -15
  105. package/dist/utils/index.d.ts +4 -0
  106. package/dist/utils/index.js +4 -2
  107. package/dist/utils/useClipboard.d.ts +6 -3
  108. package/dist/utils/useDownload.d.ts +63 -0
  109. package/dist/utils/useOutcome.d.ts +44 -0
  110. package/package.json +4 -3
  111. package/dist/{chunk-6AR2T5Y4.js → chunk-CRVETI5L.js} +0 -0
  112. package/dist/{chunk-K4VORB64.js → chunk-DEEVA6TV.js} +1 -1
  113. /package/dist/{chunk-AER7UDD4.js → chunk-U5LQ2AS5.js} +0 -0
package/dist/styles.css CHANGED
@@ -88,7 +88,7 @@
88
88
  }
89
89
 
90
90
 
91
- /* AddPill-V4TQXJ53.css */
91
+ /* AddPill-RYCY56P4.css */
92
92
  /*
93
93
  * The control that adds a child (AddPill.tsx).
94
94
  *
@@ -499,7 +499,9 @@
499
499
  /* THE FILL IS FOR A FILLED DRAWING, and a glyph is a stroke. Painting the
500
500
  inside of a Lucide glyph turns every closed shape in it solid and every
501
501
  open one into a smear, so a `.crewlet-glyph` keeps the fill its own frame
502
- decides and takes the ink through its stroke, which is `currentColor`. */
502
+ decides and takes the ink through its stroke, which is `currentColor`.
503
+ A drawing here cuts its holes as geometry, never through a mask: this rule
504
+ repaints a mask's paths and groups as well, and fills its holes with ink. */
503
505
  .crewlet-add-pill__section svg:not(.crewlet-glyph),
504
506
  .crewlet-add-pill__section svg:not(.crewlet-glyph) g,
505
507
  .crewlet-add-pill__section svg:not(.crewlet-glyph) path {
@@ -535,7 +537,7 @@
535
537
  }
536
538
 
537
539
 
538
- /* AppShell-6QUD7NWM.css */
540
+ /* AppShell-RUNVM4WH.css */
539
541
  /*
540
542
  * The application frame: a rail on the FRAME, and the screen on a SHEET that
541
543
  * floats beside it.
@@ -1160,8 +1162,158 @@
1160
1162
  }
1161
1163
  }
1162
1164
 
1165
+ /* ── the account row ───────────────────────────────────────────────────── */
1163
1166
 
1164
- /* Avatar-BPJ36QZR.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-MBUPR5E5.css */
1165
1317
  /*
1166
1318
  * The identity badge.
1167
1319
  *
@@ -1290,6 +1442,25 @@
1290
1442
  object-fit: cover;
1291
1443
  }
1292
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
+
1293
1464
  /* ── tones ─────────────────────────────────────────────────────────────── */
1294
1465
 
1295
1466
  .crewlet-avatar--fallback {
@@ -1324,9 +1495,9 @@
1324
1495
 
1325
1496
  /*
1326
1497
  * THE CHARACTER FILLS MOST OF THE PLATE, centred by its own frame (it frames
1327
- * every silhouette square on what it draws), with the plate's corner clear of
1328
- * it: a wide character such as the original Crewlet reaches the plate's
1329
- * sides, a tall one its top and its foot, and none touches the curve.
1498
+ * every silhouette square on its optical centre), with the plate's corner
1499
+ * clear of it: a wide character such as the original Crewlet reaches the
1500
+ * plate's sides, a tall one its top and its foot, and none touches the curve.
1330
1501
  */
1331
1502
  .crewlet-avatar--character {
1332
1503
  border: 1px solid transparent;
@@ -2416,7 +2587,7 @@ a.crewlet-breadcrumb__crumb:focus-visible {
2416
2587
  }
2417
2588
 
2418
2589
 
2419
- /* Callout-S7QDWHDL.css */
2590
+ /* Callout-SXXK4B5R.css */
2420
2591
  /*
2421
2592
  * The callout: a status that stays in the content flow.
2422
2593
  *
@@ -2471,8 +2642,10 @@ a.crewlet-breadcrumb__crumb:focus-visible {
2471
2642
  }
2472
2643
 
2473
2644
  .crewlet-callout__title {
2645
+ /* A LINE OF ITS OWN above the message, so the heading and the first
2646
+ sentence are never read as one run-on sentence. */
2647
+ display: block;
2474
2648
  font-weight: var(--font-weight-semibold);
2475
- margin-right: var(--spacing-2);
2476
2649
  }
2477
2650
 
2478
2651
  .crewlet-callout__action {
@@ -4217,7 +4390,7 @@ a.crewlet-breadcrumb__crumb:focus-visible {
4217
4390
  }
4218
4391
 
4219
4392
 
4220
- /* Checkbox-UE7TEPAM.css */
4393
+ /* Checkbox-KT7RS6QQ.css */
4221
4394
  /*
4222
4395
  * Checkbox: a box, its label and its consequence as one target.
4223
4396
  *
@@ -4338,6 +4511,22 @@ a.crewlet-breadcrumb__crumb:focus-visible {
4338
4511
  outline-offset: 0;
4339
4512
  }
4340
4513
 
4514
+ /*
4515
+ * A group of boxes under one legend. The boxes keep a field's inner rhythm,
4516
+ * one step apart, so the group reads as one field and its boxes as its parts.
4517
+ */
4518
+ .crewlet-checkbox-group__options {
4519
+ display: flex;
4520
+ flex-direction: column;
4521
+ gap: var(--spacing-1);
4522
+ }
4523
+
4524
+ .crewlet-checkbox-group__empty {
4525
+ margin: 0;
4526
+ font-size: var(--font-size-xs);
4527
+ color: var(--color-text-secondary);
4528
+ }
4529
+
4341
4530
 
4342
4531
  /* ChoiceButtons-DJ5EOGUA.css */
4343
4532
  /*
@@ -9951,7 +10140,7 @@ svg.crewlet-datetime__chevron {
9951
10140
  }
9952
10141
 
9953
10142
 
9954
- /* InlineCode-6WSR5COL.css */
10143
+ /* InlineCode-ASYHLN3E.css */
9955
10144
  /*
9956
10145
  * One identifier inside a sentence: a mono chip on the inset surface.
9957
10146
  *
@@ -10009,6 +10198,15 @@ svg.crewlet-datetime__chevron {
10009
10198
  color: inherit;
10010
10199
  }
10011
10200
 
10201
+ /*
10202
+ * A short token read as one: it moves to the next line whole rather than
10203
+ * splitting, which `overflow-wrap: anywhere` would otherwise allow.
10204
+ */
10205
+ .crewlet-inline-code--whole {
10206
+ overflow-wrap: normal;
10207
+ white-space: nowrap;
10208
+ }
10209
+
10012
10210
  .crewlet-inline-code--truncate {
10013
10211
  display: inline-block;
10014
10212
  max-width: 100%;
@@ -10019,7 +10217,7 @@ svg.crewlet-datetime__chevron {
10019
10217
  }
10020
10218
 
10021
10219
 
10022
- /* Input-RAKNQJCV.css */
10220
+ /* Input-3KVSNUO5.css */
10023
10221
  /*
10024
10222
  * The form row: Input, Textarea, Label, FormField, FormRow, FormSection and
10025
10223
  * ReadOnlyField.
@@ -10457,6 +10655,17 @@ svg.crewlet-datetime__chevron {
10457
10655
  color: var(--color-text-tertiary);
10458
10656
  }
10459
10657
 
10658
+ /*
10659
+ * A LITERAL INSIDE A LABEL KEEPS ITS CASE. The label is set in capitals, and
10660
+ * a value a reader has to type back exactly ("Type jane.founder to confirm")
10661
+ * read as JANE.FOUNDER, which is not what the field accepts.
10662
+ */
10663
+ .crewlet-label code,
10664
+ .crewlet-label__text code {
10665
+ text-transform: none;
10666
+ letter-spacing: normal;
10667
+ }
10668
+
10460
10669
  .crewlet-label--with-action {
10461
10670
  display: flex;
10462
10671
  justify-content: space-between;
@@ -12753,7 +12962,184 @@ svg.crewlet-datetime__chevron {
12753
12962
  }
12754
12963
 
12755
12964
 
12756
- /* OrgLabel-6WSKJQOT.css */
12965
+ /* ObjectHeader-UNVYM2KD.css */
12966
+ /*
12967
+ * The header of an object: its eyebrow, its head row and its fact line.
12968
+ */
12969
+ .crewlet-object-header {
12970
+ display: flex;
12971
+ flex-direction: column;
12972
+ gap: var(--spacing-1);
12973
+ min-width: 0;
12974
+ padding: var(--spacing-4) 0 var(--spacing-3);
12975
+ }
12976
+
12977
+ .crewlet-object-header--peek {
12978
+ padding: var(--spacing-3) 0 var(--spacing-2);
12979
+ }
12980
+
12981
+ .crewlet-object-header__eyebrow {
12982
+ display: flex;
12983
+ align-items: center;
12984
+ gap: var(--spacing-2);
12985
+ min-width: 0;
12986
+ font-size: var(--font-size-2xs);
12987
+ letter-spacing: var(--font-letter-spacing-wide);
12988
+ text-transform: uppercase;
12989
+ color: var(--color-text-tertiary);
12990
+ }
12991
+
12992
+ .crewlet-object-header__icon {
12993
+ display: inline-flex;
12994
+ flex: none;
12995
+ }
12996
+
12997
+ /* A key or a name keeps its own case: in capitals a name is a shout. */
12998
+ .crewlet-object-header__id {
12999
+ min-width: 0;
13000
+ overflow: hidden;
13001
+ text-overflow: ellipsis;
13002
+ white-space: nowrap;
13003
+ letter-spacing: normal;
13004
+ text-transform: none;
13005
+ }
13006
+
13007
+ .crewlet-object-header__id--mono {
13008
+ font-family: var(--font-family-mono);
13009
+ }
13010
+
13011
+ /* The head row wraps: the marks and the actions take a line under the title
13012
+ rather than every pill on the row giving up a few characters. */
13013
+ .crewlet-object-header__head {
13014
+ display: flex;
13015
+ flex-wrap: wrap;
13016
+ align-items: center;
13017
+ gap: var(--spacing-2);
13018
+ min-width: 0;
13019
+ }
13020
+
13021
+ /* TWO LINES, AND THAT IS THE CEILING, which catches a title written as a
13022
+ paragraph and holds every real one. */
13023
+ .crewlet-object-header__title {
13024
+ display: -webkit-box;
13025
+ -webkit-box-orient: vertical;
13026
+ -webkit-line-clamp: 2;
13027
+ line-clamp: 2;
13028
+ overflow: hidden;
13029
+ flex: 0 1 auto;
13030
+ min-width: 0;
13031
+ margin: 0;
13032
+ font-size: var(--font-size-xl);
13033
+ font-weight: var(--font-weight-semibold);
13034
+ line-height: var(--font-line-height-snug);
13035
+ color: var(--color-text-primary);
13036
+ }
13037
+
13038
+ .crewlet-object-header--peek .crewlet-object-header__title {
13039
+ font-size: var(--font-size-lg);
13040
+ }
13041
+
13042
+ /* The state keeps its width: a pill squeezed to "13 pro…" says something
13043
+ else. The marks travel together when the row wraps. */
13044
+ .crewlet-object-header__status,
13045
+ .crewlet-object-header__actions {
13046
+ display: flex;
13047
+ flex: 0 0 auto;
13048
+ flex-wrap: wrap;
13049
+ align-items: center;
13050
+ gap: var(--spacing-1);
13051
+ }
13052
+
13053
+ .crewlet-object-header__actions {
13054
+ margin-left: auto;
13055
+ }
13056
+
13057
+ /* ── the fact line ─────────────────────────────────────────────────────── */
13058
+
13059
+ /* THE TRACK FLOOR IS 7.5rem, which fits seven facts on one line at 1280px and
13060
+ still holds every label and a two-line value. */
13061
+ .crewlet-fact-line {
13062
+ display: grid;
13063
+ grid-template-columns: repeat(auto-fill, minmax(min(7.5rem, 100%), 1fr));
13064
+ column-gap: var(--spacing-4);
13065
+ row-gap: var(--spacing-3);
13066
+ padding-top: var(--spacing-1);
13067
+ }
13068
+
13069
+ /* Two bands: the label, then the value with its footnotes in one cell. */
13070
+ .crewlet-fact-line__fact {
13071
+ display: grid;
13072
+ grid-template-rows: subgrid;
13073
+ grid-row: span 2;
13074
+ row-gap: 0;
13075
+ min-width: 0;
13076
+ }
13077
+
13078
+ /* A token and a set of chips take two tracks. */
13079
+ .crewlet-fact-line__fact--token,
13080
+ .crewlet-fact-line__fact--set {
13081
+ grid-column: span 2;
13082
+ }
13083
+
13084
+ .crewlet-fact-line__body {
13085
+ display: flex;
13086
+ flex-direction: column;
13087
+ min-width: 0;
13088
+ }
13089
+
13090
+ .crewlet-fact-line__label {
13091
+ font-size: var(--font-size-2xs);
13092
+ letter-spacing: var(--font-letter-spacing-wide);
13093
+ text-transform: uppercase;
13094
+ color: var(--color-text-tertiary);
13095
+ }
13096
+
13097
+ .crewlet-fact-line__value {
13098
+ min-width: 0;
13099
+ font-size: var(--font-size-compact);
13100
+ color: var(--color-text-primary);
13101
+ overflow-wrap: anywhere;
13102
+ }
13103
+
13104
+ .crewlet-fact-line__value--clamp {
13105
+ display: -webkit-box;
13106
+ -webkit-box-orient: vertical;
13107
+ -webkit-line-clamp: 2;
13108
+ line-clamp: 2;
13109
+ overflow: hidden;
13110
+ }
13111
+
13112
+ /* One token, one line, cut with an ellipsis where two tracks are too narrow;
13113
+ its title carries the whole of it. */
13114
+ .crewlet-fact-line__value--token {
13115
+ display: block;
13116
+ overflow: hidden;
13117
+ text-overflow: ellipsis;
13118
+ white-space: nowrap;
13119
+ overflow-wrap: normal;
13120
+ }
13121
+
13122
+ /* A footnote may not set its column's width: the track does, and the note
13123
+ wraps inside it, balanced so the wrap reads as deliberate. */
13124
+ .crewlet-fact-line__note,
13125
+ .crewlet-fact-line__set-by {
13126
+ font-size: var(--font-size-2xs);
13127
+ color: var(--color-text-tertiary);
13128
+ text-wrap: balance;
13129
+ }
13130
+
13131
+ /* ── the by-line ───────────────────────────────────────────────────────── */
13132
+
13133
+ /* The word and its arrow are one atomic box, so the arrow never takes a line
13134
+ of its own. */
13135
+ .crewlet-set-by__turn {
13136
+ display: inline-flex;
13137
+ align-items: center;
13138
+ gap: 0.25em;
13139
+ }
13140
+
13141
+
13142
+ /* OrgLabel-R5KY7C3W.css */
12757
13143
  /*
12758
13144
  * What a node of an org chart and a row of an org table both say
12759
13145
  * (OrgLabel.tsx).
@@ -12835,7 +13221,12 @@ svg.crewlet-datetime__chevron {
12835
13221
  inside of a Lucide glyph turns every closed shape in it solid and every
12836
13222
  open one into a smear (a person's head and shoulders, a unit's building, a
12837
13223
  warning's triangle, each a blob), so a `.crewlet-glyph` keeps the fill its
12838
- own frame decides and takes the ink through its stroke. */
13224
+ own frame decides and takes the ink through its stroke.
13225
+
13226
+ A DRAWING HERE CUTS ITS HOLES AS GEOMETRY. This rule repaints every path and
13227
+ group in the zone, a mask's included, so a hole cut by a mask fills with a
13228
+ share of the ink that changes with the hue: every agent's eyes on the chart
13229
+ did, until the Crewlet characters cut their faces into their own paths. */
12839
13230
  .crewlet-org-label__icon svg:not(.crewlet-glyph),
12840
13231
  .crewlet-org-label__icon svg:not(.crewlet-glyph) g,
12841
13232
  .crewlet-org-label__icon svg:not(.crewlet-glyph) path {
@@ -14475,6 +14866,28 @@ svg.crewlet-datetime__chevron {
14475
14866
  }
14476
14867
 
14477
14868
 
14869
+ /* QrCode-JWJTWMU5.css */
14870
+ /*
14871
+ * A QR code on its own plate.
14872
+ *
14873
+ * A FIXED BOX, because a camera needs modules of a readable size: at 208px a
14874
+ * second factor's code (a version 7 symbol and its quiet zone) draws each
14875
+ * module at about 4.3px. A consumer that needs another size sets the variable.
14876
+ *
14877
+ * ITS INKS ARE KEPT UNDER FORCED COLOURS. A high-contrast mode repaints fills
14878
+ * to system colours, and a plate repainted dark is a code no scanner reads.
14879
+ */
14880
+ .crewlet-qr-code {
14881
+ --crewlet-qr-code-size: 208px;
14882
+ display: block;
14883
+ flex: none;
14884
+ width: var(--crewlet-qr-code-size);
14885
+ height: var(--crewlet-qr-code-size);
14886
+ border-radius: var(--radius-sm);
14887
+ forced-color-adjust: none;
14888
+ }
14889
+
14890
+
14478
14891
  /* ReadingPane-5UOLPWY5.css */
14479
14892
  /*
14480
14893
  * One item read in full, in one column.
@@ -18962,7 +19375,7 @@ b.crewlet-switcher__match {
18962
19375
  }
18963
19376
 
18964
19377
 
18965
- /* Text-XIPYYZX2.css */
19378
+ /* Text-S3Q4O3IG.css */
18966
19379
  /*
18967
19380
  * The type registers, one rule each.
18968
19381
  *
@@ -19014,6 +19427,17 @@ b.crewlet-switcher__match {
19014
19427
  color: var(--color-text-primary);
19015
19428
  }
19016
19429
 
19430
+ /*
19431
+ * The register around it: nothing is set, so the run keeps its sentence's
19432
+ * size, weight, tracking and ink, and only a modifier (mono, numeric) or a
19433
+ * tone changes it.
19434
+ */
19435
+ .crewlet-text--inherit {
19436
+ font-size: inherit;
19437
+ line-height: inherit;
19438
+ color: inherit;
19439
+ }
19440
+
19017
19441
  .crewlet-text--body {
19018
19442
  font-size: var(--font-size-sm);
19019
19443
  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;