@womp/kookie-ui 0.32.0 → 0.34.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 (136) hide show
  1. package/components.css +288 -35
  2. package/dist/cjs/components/_internal/pane-chrome.js +1 -1
  3. package/dist/cjs/components/_internal/pane-chrome.js.map +2 -2
  4. package/dist/cjs/components/_internal/shell-bottom.d.ts +7 -2
  5. package/dist/cjs/components/_internal/shell-bottom.d.ts.map +1 -1
  6. package/dist/cjs/components/_internal/shell-bottom.js +1 -1
  7. package/dist/cjs/components/_internal/shell-bottom.js.map +3 -3
  8. package/dist/cjs/components/_internal/shell-inspector.d.ts +7 -2
  9. package/dist/cjs/components/_internal/shell-inspector.d.ts.map +1 -1
  10. package/dist/cjs/components/_internal/shell-inspector.js +1 -1
  11. package/dist/cjs/components/_internal/shell-inspector.js.map +3 -3
  12. package/dist/cjs/components/_internal/shell-pane.js +1 -1
  13. package/dist/cjs/components/_internal/shell-pane.js.map +2 -2
  14. package/dist/cjs/components/_internal/shell-sidebar.d.ts +7 -2
  15. package/dist/cjs/components/_internal/shell-sidebar.d.ts.map +1 -1
  16. package/dist/cjs/components/_internal/shell-sidebar.js +1 -1
  17. package/dist/cjs/components/_internal/shell-sidebar.js.map +3 -3
  18. package/dist/cjs/components/_internal/shell-store.d.ts +6 -6
  19. package/dist/cjs/components/_internal/shell-store.d.ts.map +1 -1
  20. package/dist/cjs/components/_internal/shell-store.js +1 -1
  21. package/dist/cjs/components/_internal/shell-store.js.map +3 -3
  22. package/dist/cjs/components/command.d.ts +133 -0
  23. package/dist/cjs/components/command.d.ts.map +1 -0
  24. package/dist/cjs/components/command.js +3 -0
  25. package/dist/cjs/components/command.js.map +7 -0
  26. package/dist/cjs/components/index.d.ts +1 -0
  27. package/dist/cjs/components/index.d.ts.map +1 -1
  28. package/dist/cjs/components/index.js +2 -2
  29. package/dist/cjs/components/index.js.map +4 -4
  30. package/dist/cjs/components/navigator.js +1 -1
  31. package/dist/cjs/components/navigator.js.map +2 -2
  32. package/dist/cjs/components/shell.context.js +1 -1
  33. package/dist/cjs/components/shell.context.js.map +2 -2
  34. package/dist/cjs/components/shell.d.ts +25 -7
  35. package/dist/cjs/components/shell.d.ts.map +1 -1
  36. package/dist/cjs/components/shell.hooks.js +1 -1
  37. package/dist/cjs/components/shell.hooks.js.map +2 -2
  38. package/dist/cjs/components/shell.js +1 -1
  39. package/dist/cjs/components/shell.js.map +3 -3
  40. package/dist/cjs/components/toolbar.js +1 -1
  41. package/dist/cjs/components/toolbar.js.map +2 -2
  42. package/dist/cjs/index.js +5 -5
  43. package/dist/cjs/index.js.map +4 -4
  44. package/dist/esm/chunk-5BXCPANF.js +3 -0
  45. package/dist/esm/{chunk-GHWEXMF6.js.map → chunk-5BXCPANF.js.map} +3 -3
  46. package/dist/esm/{chunk-K35JUIUT.js → chunk-API4DAPY.js} +2 -2
  47. package/dist/esm/{chunk-ZG2XQUOH.js → chunk-B447PNRD.js} +2 -2
  48. package/dist/esm/{chunk-QDAM2I6O.js → chunk-BR6Z2CAE.js} +2 -2
  49. package/dist/esm/{chunk-W35ZKQMQ.js → chunk-CAM66IXC.js} +2 -2
  50. package/dist/esm/chunk-GZPB6MRQ.js +3 -0
  51. package/dist/esm/chunk-GZPB6MRQ.js.map +7 -0
  52. package/dist/esm/{chunk-4G3FJUER.js → chunk-INGT6DB6.js} +2 -2
  53. package/dist/esm/{chunk-4ZHBNYXM.js → chunk-J6B7UCZN.js} +2 -2
  54. package/dist/esm/chunk-KDJM4NQQ.js +3 -0
  55. package/dist/esm/chunk-KDJM4NQQ.js.map +7 -0
  56. package/dist/esm/{chunk-LTUW36HM.js → chunk-KGCGT4MJ.js} +2 -2
  57. package/dist/esm/chunk-NO2JCJWI.js +3 -0
  58. package/dist/esm/chunk-NO2JCJWI.js.map +7 -0
  59. package/dist/esm/{chunk-6WHKGVWY.js → chunk-NZJRXZYX.js} +2 -2
  60. package/dist/esm/{chunk-NFSW36RG.js → chunk-ODHCDJ3Y.js} +2 -2
  61. package/dist/esm/chunk-SHKPQ3RI.js +2 -0
  62. package/dist/esm/chunk-UMQOTGJC.js +3 -0
  63. package/dist/esm/chunk-UMQOTGJC.js.map +7 -0
  64. package/dist/esm/chunk-XD676L6S.js +3 -0
  65. package/dist/esm/chunk-XD676L6S.js.map +7 -0
  66. package/dist/esm/{chunk-6SGJWML7.js → chunk-XKYPJTRN.js} +2 -2
  67. package/dist/esm/components/_internal/pane-chrome.js +1 -1
  68. package/dist/esm/components/_internal/shell-bottom.d.ts +7 -2
  69. package/dist/esm/components/_internal/shell-bottom.d.ts.map +1 -1
  70. package/dist/esm/components/_internal/shell-bottom.js +1 -1
  71. package/dist/esm/components/_internal/shell-inspector.d.ts +7 -2
  72. package/dist/esm/components/_internal/shell-inspector.d.ts.map +1 -1
  73. package/dist/esm/components/_internal/shell-inspector.js +1 -1
  74. package/dist/esm/components/_internal/shell-pane.js +1 -1
  75. package/dist/esm/components/_internal/shell-sidebar.d.ts +7 -2
  76. package/dist/esm/components/_internal/shell-sidebar.d.ts.map +1 -1
  77. package/dist/esm/components/_internal/shell-sidebar.js +1 -1
  78. package/dist/esm/components/_internal/shell-store.d.ts +6 -6
  79. package/dist/esm/components/_internal/shell-store.d.ts.map +1 -1
  80. package/dist/esm/components/_internal/shell-store.js +1 -1
  81. package/dist/esm/components/alert-dialog.js +1 -1
  82. package/dist/esm/components/command.d.ts +133 -0
  83. package/dist/esm/components/command.d.ts.map +1 -0
  84. package/dist/esm/components/command.js +3 -0
  85. package/dist/esm/components/command.js.map +7 -0
  86. package/dist/esm/components/dialog.js +1 -1
  87. package/dist/esm/components/index.d.ts +1 -0
  88. package/dist/esm/components/index.d.ts.map +1 -1
  89. package/dist/esm/components/index.js +1 -1
  90. package/dist/esm/components/navigator.js +1 -1
  91. package/dist/esm/components/sheet.js +1 -1
  92. package/dist/esm/components/shell.context.js +1 -1
  93. package/dist/esm/components/shell.d.ts +25 -7
  94. package/dist/esm/components/shell.d.ts.map +1 -1
  95. package/dist/esm/components/shell.hooks.js +1 -1
  96. package/dist/esm/components/shell.js +1 -1
  97. package/dist/esm/components/sidebar.js +1 -1
  98. package/dist/esm/components/toolbar.js +1 -1
  99. package/dist/esm/hooks/index.js +1 -1
  100. package/dist/esm/index.js +1 -1
  101. package/package.json +1 -1
  102. package/src/components/_internal/shell-bottom.tsx +11 -6
  103. package/src/components/_internal/shell-inspector.tsx +11 -6
  104. package/src/components/_internal/shell-sidebar.tsx +11 -6
  105. package/src/components/_internal/shell-store.tsx +13 -13
  106. package/src/components/command.css +197 -0
  107. package/src/components/command.tsx +432 -0
  108. package/src/components/index.css +1 -0
  109. package/src/components/index.tsx +24 -0
  110. package/src/components/shell.css +133 -47
  111. package/src/components/shell.tsx +50 -32
  112. package/src/styles/tokens/color.css +2 -0
  113. package/styles.css +289 -35
  114. package/tokens/base.css +1 -0
  115. package/tokens.css +1 -0
  116. package/dist/esm/chunk-4B3S2DM7.js +0 -3
  117. package/dist/esm/chunk-4B3S2DM7.js.map +0 -7
  118. package/dist/esm/chunk-AKMRPGNP.js +0 -3
  119. package/dist/esm/chunk-AKMRPGNP.js.map +0 -7
  120. package/dist/esm/chunk-GHWEXMF6.js +0 -3
  121. package/dist/esm/chunk-RSG4XWHU.js +0 -2
  122. package/dist/esm/chunk-ZBSGH74F.js +0 -3
  123. package/dist/esm/chunk-ZBSGH74F.js.map +0 -7
  124. package/dist/esm/chunk-ZHXEEOGI.js +0 -3
  125. package/dist/esm/chunk-ZHXEEOGI.js.map +0 -7
  126. /package/dist/esm/{chunk-K35JUIUT.js.map → chunk-API4DAPY.js.map} +0 -0
  127. /package/dist/esm/{chunk-ZG2XQUOH.js.map → chunk-B447PNRD.js.map} +0 -0
  128. /package/dist/esm/{chunk-QDAM2I6O.js.map → chunk-BR6Z2CAE.js.map} +0 -0
  129. /package/dist/esm/{chunk-W35ZKQMQ.js.map → chunk-CAM66IXC.js.map} +0 -0
  130. /package/dist/esm/{chunk-4G3FJUER.js.map → chunk-INGT6DB6.js.map} +0 -0
  131. /package/dist/esm/{chunk-4ZHBNYXM.js.map → chunk-J6B7UCZN.js.map} +0 -0
  132. /package/dist/esm/{chunk-LTUW36HM.js.map → chunk-KGCGT4MJ.js.map} +0 -0
  133. /package/dist/esm/{chunk-6WHKGVWY.js.map → chunk-NZJRXZYX.js.map} +0 -0
  134. /package/dist/esm/{chunk-NFSW36RG.js.map → chunk-ODHCDJ3Y.js.map} +0 -0
  135. /package/dist/esm/{chunk-RSG4XWHU.js.map → chunk-SHKPQ3RI.js.map} +0 -0
  136. /package/dist/esm/{chunk-6SGJWML7.js.map → chunk-XKYPJTRN.js.map} +0 -0
@@ -26,8 +26,8 @@
26
26
  /* ── Frame ─────────────────────────────────────────────────────────────────── */
27
27
 
28
28
  .kui-ShellRoot {
29
- /* Read by inset panes. Declared here, so the documented override on the Root works. */
30
- --shell-inset-gap: var(--space-3);
29
+ /* The air around a pane pulled off the frame (`flush={false}`). Declared here, so the documented override on the Root works. */
30
+ --shell-gap: var(--space-3);
31
31
  /* Written inline by the Root while a Rail shows; stated here so a nested Shell never inherits its parent's. */
32
32
  --shell-rail-offset: 0px;
33
33
  /* How far the stuck header reaches down the screen while the page scrolls. Stated here so a nested Shell never inherits its parent's. */
@@ -938,68 +938,154 @@
938
938
  position: fixed;
939
939
  }
940
940
 
941
- /* ── Inset ─────────────────────────────────────────────────────────────────── */
941
+ /* ── Flush and off the frame (from v2, 0.33.0) ─────────────────────────────── */
942
942
 
943
943
  /*
944
- * The backdrop behind the middle row while any pane is inset. A grid item of
945
- * its own, so the header and the bottom row keep the page. `:has()` paints it
946
- * from the server markup, before any script; `data-has-inset` is the Root's
947
- * own record, which also covers a pane wrapped in another element.
944
+ * Every pane says one thing about itself: whether it joins the app frame (`flush`, the default)
945
+ * or is pulled off it as a card (`flush={false}`). Whether a card floats or sits on the Shell's
946
+ * background is not stated anywhere; it falls out of the content's own answer, here, so it is
947
+ * right at first paint:
948
+ *
949
+ * - A flush pane is level with the page: square, no fill, no shadow, and one hairline in
950
+ * `--color-border` on its inner edge. Each seam is drawn by exactly one pane, and the content
951
+ * draws none, so two neighbours never double a line.
952
+ * - With the content flush, a card floats over it: the content's grid area grows underneath.
953
+ * - With the content off the frame, nothing floats. The Shell paints its background and every
954
+ * pane, flush or not, sits on it; the content is a card, and a flush pane draws no hairline
955
+ * toward it, because a line beside a card's own edge is a second boundary.
948
956
  */
949
- .kui-ShellRoot:has(> [data-inset])::before,
950
- .kui-ShellRoot[data-has-inset]::before {
951
- content: '';
952
- grid-row: 2;
953
- grid-column: 1 / -1;
954
- background: var(--neutral-2);
957
+
958
+ .kui-ShellHeader[data-flush],
959
+ .kui-ShellPane[data-flush],
960
+ .kui-ShellContent[data-flush] {
961
+ border: 0 solid var(--color-border);
962
+ border-radius: 0;
963
+ }
964
+
965
+ .kui-ShellHeader[data-flush] {
966
+ border-block-end-width: 1px;
967
+ }
968
+
969
+ .kui-ShellRail[data-flush],
970
+ .kui-ShellPanel[data-flush],
971
+ .kui-ShellSidebar[data-flush] {
972
+ border-inline-end-width: 1px;
973
+ }
974
+
975
+ .kui-ShellInspector[data-flush] {
976
+ border-inline-start-width: 1px;
955
977
  }
956
978
 
957
- .kui-ShellPane[data-inset],
958
- .kui-ShellContent[data-inset] {
959
- margin: var(--shell-inset-gap);
979
+ .kui-ShellBottom[data-flush] {
980
+ border-block-start-width: 1px;
981
+ }
982
+
983
+ /* A seam needs something on the other side. Against a card content there is only the gap. The
984
+ Panel and the Rail face the content only when no flush pane stands between them. */
985
+ .kui-ShellRoot:has(> .kui-ShellContent:not([data-flush])) > .kui-ShellHeader[data-flush] {
986
+ border-block-end-width: 0;
987
+ }
988
+
989
+ .kui-ShellRoot:has(> .kui-ShellContent:not([data-flush])) > .kui-ShellSidebar[data-flush],
990
+ .kui-ShellRoot:has(> .kui-ShellContent:not([data-flush])):not(:has(> .kui-ShellSidebar[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']))) > .kui-ShellPanel[data-flush],
991
+ .kui-ShellRoot:has(> .kui-ShellContent:not([data-flush])):not(:has(> .kui-ShellPanel[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']), > .kui-ShellSidebar[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']))) > .kui-ShellRail[data-flush] {
992
+ border-inline-end-width: 0;
993
+ }
994
+
995
+ .kui-ShellRoot:has(> .kui-ShellContent:not([data-flush])) > .kui-ShellInspector[data-flush] {
996
+ border-inline-start-width: 0;
997
+ }
998
+
999
+ .kui-ShellRoot:has(> .kui-ShellContent:not([data-flush])) > .kui-ShellBottom[data-flush] {
1000
+ border-block-start-width: 0;
1001
+ }
1002
+
1003
+ /* Off the frame: a card, with its own air on every side. */
1004
+ .kui-ShellHeader:not([data-flush]),
1005
+ .kui-ShellPane:not([data-flush]),
1006
+ .kui-ShellContent:not([data-flush]) {
1007
+ margin: var(--shell-gap);
960
1008
  border-radius: var(--radius-surface-1);
961
- background: var(--color-page);
1009
+ background-color: var(--color-page);
962
1010
  box-shadow: var(--surface-chrome);
963
1011
  }
964
1012
 
1013
+ /* A header card rests level with the panes below it, so its shadow falls behind them, not on them. */
1014
+ .kui-ShellHeader:not([data-flush]) {
1015
+ z-index: 1;
1016
+ }
1017
+
1018
+ /* With the content off the frame, the Shell paints the background the cards sit on. */
1019
+ .kui-ShellRoot:has(> .kui-ShellContent:not([data-flush])) {
1020
+ background-color: var(--neutral-2);
1021
+ }
1022
+
965
1023
  /*
966
- * Two inset panes side by side share one gap rather than doubling it: the
967
- * later pane in the row drops the margin on the side that meets the earlier
968
- * one. Only the pane right beside it counts, and only while it is shown in
969
- * the frame (the JS mirror's data-presentation and data-mode), so a closed,
970
- * hidden or floating pane never takes the gap away. Before hydration the
971
- * gap is doubled.
1024
+ * Two cards side by side share one gap rather than doubling it: the later card in the row drops
1025
+ * its margin on the side that meets the earlier one, so card to card and card to edge come out
1026
+ * equal. Only a pane shown in the frame counts.
972
1027
  */
973
- .kui-ShellRoot:has(> .kui-ShellRail[data-inset][data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])) > .kui-ShellPanel[data-inset][data-presentation='fixed'],
974
- .kui-ShellRoot:has(> .kui-ShellPanel[data-inset][data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])) > .kui-ShellSidebar[data-inset][data-presentation='fixed'],
975
- .kui-ShellRoot:has(> .kui-ShellRail[data-inset][data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])):not(
976
- :has(> .kui-ShellPanel[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']))
977
- )
978
- > .kui-ShellSidebar[data-inset][data-presentation='fixed'],
979
- .kui-ShellRoot:has(> .kui-ShellSidebar[data-inset][data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])) > .kui-ShellContent[data-inset],
980
- .kui-ShellRoot:has(> .kui-ShellPanel[data-inset][data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])):not(
981
- :has(> .kui-ShellSidebar[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']))
982
- )
983
- > .kui-ShellContent[data-inset],
984
- .kui-ShellRoot:has(> .kui-ShellRail[data-inset][data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])):not(
985
- :has(> .kui-ShellPanel[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']), > .kui-ShellSidebar[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']))
986
- )
987
- > .kui-ShellContent[data-inset],
988
- .kui-ShellRoot:has(> .kui-ShellContent[data-inset]) > .kui-ShellInspector[data-inset][data-presentation='fixed'] {
1028
+ .kui-ShellRoot:has(> .kui-ShellRail:not([data-flush])[data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])) > .kui-ShellPanel:not([data-flush])[data-presentation='fixed'],
1029
+ .kui-ShellRoot:has(> .kui-ShellPanel:not([data-flush])[data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])) > .kui-ShellSidebar:not([data-flush])[data-presentation='fixed'],
1030
+ .kui-ShellRoot:has(> .kui-ShellRail:not([data-flush])[data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])):not(:has(> .kui-ShellPanel[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']))) > .kui-ShellSidebar:not([data-flush])[data-presentation='fixed'],
1031
+ .kui-ShellRoot:has(> .kui-ShellSidebar:not([data-flush])[data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])) > .kui-ShellContent:not([data-flush]),
1032
+ .kui-ShellRoot:has(> .kui-ShellPanel:not([data-flush])[data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])):not(:has(> .kui-ShellSidebar[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']))) > .kui-ShellContent:not([data-flush]),
1033
+ .kui-ShellRoot:has(> .kui-ShellRail:not([data-flush])[data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])):not(:has(> .kui-ShellPanel[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']), > .kui-ShellSidebar[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']))) > .kui-ShellContent:not([data-flush]),
1034
+ .kui-ShellRoot:has(> .kui-ShellContent:not([data-flush])) > .kui-ShellInspector:not([data-flush])[data-presentation='fixed'] {
989
1035
  margin-inline-start: 0;
990
1036
  }
991
1037
 
992
- .kui-ShellRoot:has(
993
- > .kui-ShellRail[data-inset][data-presentation='fixed'][data-mode]:not([data-mode='collapsed']),
994
- > .kui-ShellPanel[data-inset][data-presentation='fixed'][data-mode]:not([data-mode='collapsed']),
995
- > .kui-ShellSidebar[data-inset][data-presentation='fixed'][data-mode]:not([data-mode='collapsed']),
996
- > .kui-ShellContent[data-inset],
997
- > .kui-ShellInspector[data-inset][data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])
998
- )
999
- > .kui-ShellBottom[data-inset] {
1038
+ .kui-ShellRoot:has(> .kui-ShellHeader:not([data-flush])) > :is(.kui-ShellPane:not(.kui-ShellBottom), .kui-ShellContent):not([data-flush]),
1039
+ .kui-ShellRoot:has(> :is(.kui-ShellRail, .kui-ShellPanel, .kui-ShellSidebar, .kui-ShellInspector):not([data-flush])[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']), > .kui-ShellContent:not([data-flush])) > .kui-ShellBottom:not([data-flush]) {
1000
1040
  margin-block-start: 0;
1001
1041
  }
1002
1042
 
1043
+ /* A card content pays no air toward a flush side pane in the frame: the pane's padding is the
1044
+ air. Toward a flush header it keeps its gap, since a header's row has no padding below it. */
1045
+ .kui-ShellRoot:has(> .kui-ShellSidebar[data-flush][data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])) > .kui-ShellContent:not([data-flush]),
1046
+ .kui-ShellRoot:has(> .kui-ShellPanel[data-flush][data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])):not(:has(> .kui-ShellSidebar[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']))) > .kui-ShellContent:not([data-flush]),
1047
+ .kui-ShellRoot:has(> .kui-ShellRail[data-flush][data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])):not(:has(> .kui-ShellPanel[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']), > .kui-ShellSidebar[data-presentation='fixed'][data-mode]:not([data-mode='collapsed']))) > .kui-ShellContent:not([data-flush]) {
1048
+ margin-inline-start: 0;
1049
+ }
1050
+
1051
+ .kui-ShellRoot:has(> .kui-ShellInspector[data-flush][data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])) > .kui-ShellContent:not([data-flush]) {
1052
+ margin-inline-end: 0;
1053
+ }
1054
+
1055
+ .kui-ShellRoot:has(> .kui-ShellBottom[data-flush][data-presentation='fixed'][data-mode]:not([data-mode='collapsed'])) > .kui-ShellContent:not([data-flush]) {
1056
+ margin-block-end: 0;
1057
+ }
1058
+
1059
+ /*
1060
+ * Floating: with the content flush, a card is over it, and the content's grid area grows to the
1061
+ * card's own line so the content runs underneath. It grows only on the sides something floats
1062
+ * on. The Rail and the Panel float over the content only when no flush pane stands between.
1063
+ * The panes already sit above the content (`z-index` 1 against the content's isolated layer).
1064
+ */
1065
+ .kui-ShellRoot:has(> .kui-ShellSidebar:not([data-flush])) > .kui-ShellContent[data-flush] {
1066
+ grid-column-start: sidebar-start;
1067
+ }
1068
+
1069
+ .kui-ShellRoot:has(> .kui-ShellPanel:not([data-flush])):not(:has(> .kui-ShellSidebar[data-flush])) > .kui-ShellContent[data-flush] {
1070
+ grid-column-start: panel-start;
1071
+ }
1072
+
1073
+ .kui-ShellRoot:has(> .kui-ShellRail:not([data-flush])):not(:has(> .kui-ShellPanel[data-flush], > .kui-ShellSidebar[data-flush])) > .kui-ShellContent[data-flush] {
1074
+ grid-column-start: rail-start;
1075
+ }
1076
+
1077
+ .kui-ShellRoot:has(> .kui-ShellInspector:not([data-flush])) > .kui-ShellContent[data-flush] {
1078
+ grid-column-end: inspector-end;
1079
+ }
1080
+
1081
+ .kui-ShellRoot:has(> .kui-ShellHeader:not([data-flush])) > .kui-ShellContent[data-flush] {
1082
+ grid-row-start: header-start;
1083
+ }
1084
+
1085
+ .kui-ShellRoot:has(> .kui-ShellBottom:not([data-flush])) > .kui-ShellContent[data-flush] {
1086
+ grid-row-end: bottom-end;
1087
+ }
1088
+
1003
1089
  /* ── On a phone, the page scrolls (0.27.0) ────────────────────────────────── */
1004
1090
 
1005
1091
  /*
@@ -36,16 +36,16 @@ import { usePane, paneAttributes, usePresentationTable } from './_internal/shell
36
36
  import { splitHandles, useInitialPaneSize, useResizablePane } from './_internal/shell-resizable.js';
37
37
  import {
38
38
  FIXED_TABLE,
39
- ShellInsetContext,
39
+ ShellFrameContext,
40
40
  ShellStoreContext,
41
41
  createShellStore,
42
42
  setShellPeek,
43
43
  subscribeShellStore,
44
- useInsetRegistration,
44
+ useFrameRegistration,
45
45
  usePaneEntry,
46
46
  useShellStore,
47
47
  warnOnce,
48
- type InsetPaneId,
48
+ type FramePaneId,
49
49
  } from './_internal/shell-store.js';
50
50
  import { navigationRest, detailRest, toPaneValue, startAnchoredGrowth, withoutRemovedPaneProps } from './_internal/shell-rest.js';
51
51
  import { useShell, useSidebarMode } from './shell.context.js';
@@ -145,20 +145,20 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
145
145
  const rootRef = React.useRef<HTMLDivElement | null>(null);
146
146
  const composedRef = useComposedRefs(ref, rootRef);
147
147
 
148
- // Inset: the Root paints the backdrop while any pane is inset.
149
- const [insetPanes, setInsetPanes] = React.useState<ReadonlySet<InsetPaneId>>(() => new Set());
150
- const registerInset = React.useCallback((id: InsetPaneId) => {
151
- setInsetPanes((prev) => (prev.has(id) ? prev : new Set(prev).add(id)));
148
+ // The panes pulled off the frame (`flush={false}`). Only the Rail's is read, for the offset below.
149
+ const [offFrame, setOffFrame] = React.useState<ReadonlySet<FramePaneId>>(() => new Set());
150
+ const registerOffFrame = React.useCallback((id: FramePaneId) => {
151
+ setOffFrame((prev) => (prev.has(id) ? prev : new Set(prev).add(id)));
152
152
  }, []);
153
- const unregisterInset = React.useCallback((id: InsetPaneId) => {
154
- setInsetPanes((prev) => {
153
+ const unregisterOffFrame = React.useCallback((id: FramePaneId) => {
154
+ setOffFrame((prev) => {
155
155
  if (!prev.has(id)) return prev;
156
156
  const next = new Set(prev);
157
157
  next.delete(id);
158
158
  return next;
159
159
  });
160
160
  }, []);
161
- const insetContext = React.useMemo(() => ({ registerInset, unregisterInset }), [registerInset, unregisterInset]);
161
+ const frameContext = React.useMemo(() => ({ registerOffFrame, unregisterOffFrame }), [registerOffFrame, unregisterOffFrame]);
162
162
 
163
163
  const closeOverlays = React.useCallback(() => {
164
164
  for (const entry of store.entries.values()) {
@@ -444,10 +444,10 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
444
444
  };
445
445
 
446
446
  // A floating Panel sits beside the Rail when the Rail is showing, and
447
- // beside its card when the Rail is inset.
447
+ // beside its card when the Rail is off the frame.
448
448
  const rail = store.entries.get('rail');
449
449
  const railShown = rail !== undefined && rail.value !== undefined && rail.value !== 'closed';
450
- const railOffset = railShown ? (insetPanes.has('rail') ? `calc(${rail.size}px + 2 * var(--shell-inset-gap))` : `${rail.size}px`) : undefined;
450
+ const railOffset = railShown ? (offFrame.has('rail') ? `calc(${rail.size}px + 2 * var(--shell-gap))` : `${rail.size}px`) : undefined;
451
451
 
452
452
  // `full` writes no inline height: the stylesheet states it as 100vh with a
453
453
  // 100dvh override where supported, and an inline value would beat both.
@@ -459,7 +459,7 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
459
459
  <ShellContentContext.Provider value={false}>
460
460
  <ShellWindowContext.Provider value={windowShell}>
461
461
  <ShellHeaderHeightContext.Provider value={setHeaderHeight}>
462
- <ShellInsetContext.Provider value={insetContext}>
462
+ <ShellFrameContext.Provider value={frameContext}>
463
463
  {/* A Shell inside a portalled surface's Theme starts with no band, like any other. */}
464
464
  <PaneBandSizeContext.Provider value={undefined}>
465
465
  {/* The Root is not a widget: it hears Escape bubbling up from the panes,
@@ -472,7 +472,6 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
472
472
  data-height={height === 'auto' && !contained ? 'auto' : undefined}
473
473
  data-contained={contained || undefined}
474
474
  data-window={windowShell || undefined}
475
- data-has-inset={insetPanes.size > 0 || undefined}
476
475
  data-overlay-live={liveCount > 0 || undefined}
477
476
  onKeyDown={handleKeyDown}
478
477
  onFocus={handleFocus}
@@ -490,7 +489,7 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
490
489
  <div className="kui-ShellScrim" aria-hidden onClick={closeOverlays} />
491
490
  </div>
492
491
  </PaneBandSizeContext.Provider>
493
- </ShellInsetContext.Provider>
492
+ </ShellFrameContext.Provider>
494
493
  </ShellHeaderHeightContext.Provider>
495
494
  </ShellWindowContext.Provider>
496
495
  </ShellContentContext.Provider>
@@ -504,9 +503,14 @@ Root.displayName = 'Shell.Root';
504
503
 
505
504
  interface ShellHeaderProps extends React.ComponentPropsWithoutRef<'header'> {
506
505
  height?: number;
506
+ /**
507
+ * Sets whether the header joins the app frame. The default is `true`: a hairline under it. Set
508
+ * `flush={false}` to make it a card off the frame.
509
+ */
510
+ flush?: boolean;
507
511
  }
508
512
 
509
- const Header = React.forwardRef<HTMLElement, ShellHeaderProps>(({ className, height = DEFAULT_HEADER_HEIGHT, style, ...props }, ref) => {
513
+ const Header = React.forwardRef<HTMLElement, ShellHeaderProps>(({ className, height = DEFAULT_HEADER_HEIGHT, flush = true, style, ...props }, ref) => {
510
514
  // Rows that stick beneath the header while the page scrolls need its height. The stylesheet
511
515
  // assumes the default, so only another height is told to the Root.
512
516
  const setHeaderHeight = React.useContext(ShellHeaderHeightContext);
@@ -520,6 +524,7 @@ const Header = React.forwardRef<HTMLElement, ShellHeaderProps>(({ className, hei
520
524
  {...props}
521
525
  ref={ref}
522
526
  className={classNames('kui-ShellHeader', className)}
527
+ data-flush={flush || undefined}
523
528
  style={
524
529
  {
525
530
  ...style,
@@ -541,7 +546,6 @@ interface LeftProps extends React.ComponentPropsWithoutRef<'div'> {
541
546
  collapsible?: boolean;
542
547
  onExpand?: () => void;
543
548
  onCollapse?: () => void;
544
- inset?: boolean;
545
549
  }
546
550
 
547
551
  /**
@@ -572,14 +576,19 @@ type RailProps = React.ComponentPropsWithoutRef<'div'> & {
572
576
  onExpand?: () => void;
573
577
  /** @deprecated Use `onOpenChange`. */
574
578
  onCollapse?: () => void;
575
- /** When true, adds margin and triggers the gray backdrop on the Shell. */
576
- inset?: boolean;
579
+ /**
580
+ * Sets whether this pane joins the app frame. The default is `true`: the pane sits against its
581
+ * neighbours with one hairline between them. Set `flush={false}` to pull it off the frame as a
582
+ * card. It then floats over the content if the content is flush, and otherwise sits on the
583
+ * Shell's background beside a card content.
584
+ */
585
+ flush?: boolean;
577
586
  } & (RailControlledProps | RailUncontrolledProps);
578
587
 
579
588
  const RAIL_PROP_NAMES = { controlled: 'open', default: 'defaultOpen' };
580
589
 
581
590
  const Rail = React.forwardRef<HTMLDivElement, RailProps>((props, ref) => {
582
- const { className, presentation, expandedSize = 64, collapsible: _collapsible, onExpand, onCollapse, children, style, open, defaultOpen, onOpenChange, inset, id, ...domProps } = props;
591
+ const { className, presentation, expandedSize = 64, collapsible: _collapsible, onExpand, onCollapse, children, style, open, defaultOpen, onOpenChange, flush = true, id, ...domProps } = props;
583
592
  const { table, attributes } = usePresentationTable(presentation, 'fixed');
584
593
  const pane = usePane({
585
594
  name: 'rail',
@@ -595,7 +604,7 @@ const Rail = React.forwardRef<HTMLDivElement, RailProps>((props, ref) => {
595
604
  onCollapse,
596
605
  size: expandedSize,
597
606
  });
598
- useInsetRegistration('rail', inset);
607
+ useFrameRegistration('rail', flush);
599
608
  const composedRef = useComposedRefs(ref, pane.setElement);
600
609
 
601
610
  return (
@@ -606,7 +615,7 @@ const Rail = React.forwardRef<HTMLDivElement, RailProps>((props, ref) => {
606
615
  ref={composedRef}
607
616
  className={classNames('kui-ShellPane', 'kui-ShellRail', className)}
608
617
  {...paneAttributes(pane, attributes)}
609
- data-inset={inset || undefined}
618
+ data-flush={flush || undefined}
610
619
  tabIndex={pane.overlaying ? -1 : domProps.tabIndex}
611
620
  style={{ ...style, '--rail-size': `${expandedSize}px` } as CSSPropertiesWithVars}
612
621
  >
@@ -644,8 +653,13 @@ type PanelPublicProps = PaneBaseProps &
644
653
  onSizeChange?: (size: number, meta: PanelSizeChangeMeta) => void;
645
654
  sizeUpdate?: 'throttle' | 'debounce';
646
655
  sizeUpdateMs?: number;
647
- /** When true, adds margin and triggers the gray backdrop on the Shell. */
648
- inset?: boolean;
656
+ /**
657
+ * Sets whether this pane joins the app frame. The default is `true`: the pane sits against its
658
+ * neighbours with one hairline between them. Set `flush={false}` to pull it off the frame as a
659
+ * card. It then floats over the content if the content is flush, and otherwise sits on the
660
+ * Shell's background beside a card content.
661
+ */
662
+ flush?: boolean;
649
663
  };
650
664
  type PanelComponent = React.ForwardRefExoticComponent<PanelPublicProps & React.RefAttributes<HTMLDivElement>> & {
651
665
  Handle: HandleComponent;
@@ -688,7 +702,7 @@ const Panel = React.forwardRef<HTMLDivElement, PanelPublicProps>((props, ref) =>
688
702
  onSizeChange,
689
703
  sizeUpdate,
690
704
  sizeUpdateMs = 50,
691
- inset,
705
+ flush = true,
692
706
  id,
693
707
  ...domProps
694
708
  } = props;
@@ -713,7 +727,7 @@ const Panel = React.forwardRef<HTMLDivElement, PanelPublicProps>((props, ref) =>
713
727
  onCollapse,
714
728
  size: expandedSize,
715
729
  });
716
- useInsetRegistration('panel', inset);
730
+ useFrameRegistration('panel', flush);
717
731
 
718
732
  React.useEffect(() => {
719
733
  if (snapPoints !== undefined || collapseThreshold !== undefined || paneId !== undefined || persistence !== undefined) {
@@ -774,7 +788,7 @@ const Panel = React.forwardRef<HTMLDivElement, PanelPublicProps>((props, ref) =>
774
788
  ref={composedRef}
775
789
  className={classNames('kui-ShellPane', 'kui-ShellPanel', className)}
776
790
  {...paneAttributes(pane, presentationAttributes)}
777
- data-inset={inset || undefined}
791
+ data-flush={flush || undefined}
778
792
  tabIndex={pane.overlaying ? -1 : domProps.tabIndex}
779
793
  style={
780
794
  {
@@ -799,18 +813,22 @@ Panel.Handle = PanelHandle;
799
813
  /* ── Content ───────────────────────────────────────────────────────────────── */
800
814
 
801
815
  interface ShellContentProps extends React.ComponentPropsWithoutRef<'main'> {
802
- /** When true, adds margin and triggers the gray backdrop on the Shell. */
803
- inset?: boolean;
816
+ /**
817
+ * Sets whether the work area joins the app frame. The default is `true`. Set `flush={false}`
818
+ * to make it a card on the Shell's background; the panes beside it then sit on that background
819
+ * too, and draw no hairline toward it.
820
+ */
821
+ flush?: boolean;
804
822
  }
805
823
 
806
824
  /**
807
825
  * The work area. It scrolls as it is; with a `Shell.Scroll` child it becomes a column and the
808
826
  * Scroll scrolls instead, so a `Shell.PaneHeader float` beside it stays pinned over the page.
809
827
  */
810
- const Content = React.forwardRef<HTMLElement, ShellContentProps>(({ className, inset, children, ...props }, ref) => {
811
- useInsetRegistration('content', inset);
828
+ const Content = React.forwardRef<HTMLElement, ShellContentProps>(({ className, flush = true, children, ...props }, ref) => {
829
+ useFrameRegistration('content', flush);
812
830
  return (
813
- <main {...props} ref={ref} className={classNames('kui-ShellContent', className)} data-inset={inset || undefined}>
831
+ <main {...props} ref={ref} className={classNames('kui-ShellContent', className)} data-flush={flush || undefined}>
814
832
  {/* Each pane holds its own page store, so a band's title can mirror the page beside it. */}
815
833
  <ShellContentContext.Provider value={true}>
816
834
  <PageScope>{children}</PageScope>
@@ -8,6 +8,8 @@
8
8
  --color-page: white;
9
9
  --color-overlay: var(--black-a6);
10
10
  --color-surface: white;
11
+ /* A rule between two regions, such as the hairline between two flush Shell panes. The same role and value as v2's. */
12
+ --color-border: var(--neutral-6, var(--gray-6));
11
13
  /* A background for an app page that holds raised surfaces. No component reads it. */
12
14
  --color-ground: var(--neutral-1, var(--gray-1));
13
15