@womp/kookie-ui 0.9.1 → 0.11.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 (171) hide show
  1. package/components.css +145 -26
  2. package/dist/cjs/components/app-bar.d.ts +49 -0
  3. package/dist/cjs/components/app-bar.d.ts.map +1 -0
  4. package/dist/cjs/components/app-bar.js +2 -0
  5. package/dist/cjs/components/app-bar.js.map +7 -0
  6. package/dist/cjs/components/chatbar.js +2 -2
  7. package/dist/cjs/components/chatbar.js.map +4 -4
  8. package/dist/cjs/components/context-menu.js +1 -1
  9. package/dist/cjs/components/context-menu.js.map +4 -4
  10. package/dist/cjs/components/dropdown-menu.js +1 -1
  11. package/dist/cjs/components/dropdown-menu.js.map +4 -4
  12. package/dist/cjs/components/index.d.ts +2 -1
  13. package/dist/cjs/components/index.d.ts.map +1 -1
  14. package/dist/cjs/components/index.js +5 -5
  15. package/dist/cjs/components/index.js.map +4 -4
  16. package/dist/cjs/components/message-scroller.d.ts +6 -0
  17. package/dist/cjs/components/message-scroller.d.ts.map +1 -1
  18. package/dist/cjs/components/message-scroller.js +2 -2
  19. package/dist/cjs/components/message-scroller.js.map +4 -4
  20. package/dist/cjs/components/scroll-area.d.ts.map +1 -1
  21. package/dist/cjs/components/scroll-area.js +1 -1
  22. package/dist/cjs/components/scroll-area.js.map +4 -4
  23. package/dist/cjs/components/scroll-area.props.d.ts +10 -0
  24. package/dist/cjs/components/scroll-area.props.d.ts.map +1 -1
  25. package/dist/cjs/components/scroll-area.props.js +1 -1
  26. package/dist/cjs/components/scroll-area.props.js.map +2 -2
  27. package/dist/cjs/components/sidebar.js +1 -1
  28. package/dist/cjs/components/sidebar.js.map +4 -4
  29. package/dist/cjs/components/table.js +1 -1
  30. package/dist/cjs/components/table.js.map +4 -4
  31. package/dist/cjs/components/theme-panel.js +3 -3
  32. package/dist/cjs/components/theme-panel.js.map +4 -4
  33. package/dist/cjs/components/toolbar.d.ts +88 -36
  34. package/dist/cjs/components/toolbar.d.ts.map +1 -1
  35. package/dist/cjs/components/toolbar.js +4 -1
  36. package/dist/cjs/components/toolbar.js.map +4 -4
  37. package/dist/cjs/components/toolbar.props.d.ts +28 -0
  38. package/dist/cjs/components/toolbar.props.d.ts.map +1 -0
  39. package/dist/cjs/components/toolbar.props.js +2 -0
  40. package/dist/cjs/components/toolbar.props.js.map +7 -0
  41. package/dist/cjs/hooks/use-scroll-edges.d.ts +12 -0
  42. package/dist/cjs/hooks/use-scroll-edges.d.ts.map +1 -0
  43. package/dist/cjs/hooks/use-scroll-edges.js +2 -0
  44. package/dist/cjs/hooks/use-scroll-edges.js.map +7 -0
  45. package/dist/cjs/index.js +5 -5
  46. package/dist/cjs/index.js.map +4 -4
  47. package/dist/cjs/props/index.js +1 -1
  48. package/dist/cjs/props/index.js.map +2 -2
  49. package/dist/esm/{chunk-ABSKRHVM.js → chunk-36A5BYCN.js} +2 -2
  50. package/dist/esm/{chunk-73QGEY2I.js → chunk-3R4YLW4A.js} +2 -2
  51. package/dist/esm/{chunk-ZZLZGHZQ.js → chunk-4LDVSTXI.js} +2 -2
  52. package/dist/esm/{chunk-YQRSAP46.js → chunk-53SZ4Y23.js} +2 -2
  53. package/dist/esm/chunk-54JFIP5S.js +2 -0
  54. package/dist/esm/chunk-54JFIP5S.js.map +7 -0
  55. package/dist/esm/chunk-5BWLY27A.js +2 -0
  56. package/dist/esm/chunk-5BWLY27A.js.map +7 -0
  57. package/dist/esm/{chunk-X3JM2565.js → chunk-6G3MPL4A.js} +2 -2
  58. package/dist/esm/{chunk-DSJY5RI3.js → chunk-7JJT42J2.js} +2 -2
  59. package/dist/esm/chunk-C7EVYSWK.js +1 -0
  60. package/dist/esm/chunk-COWZAUZR.js +2 -0
  61. package/dist/esm/chunk-COWZAUZR.js.map +7 -0
  62. package/dist/esm/{chunk-ERY5TH2M.js → chunk-CYU6BLZ5.js} +2 -2
  63. package/dist/esm/{chunk-MWIIGYAJ.js → chunk-D6PHTHF2.js} +2 -2
  64. package/dist/esm/{chunk-YTD3AJAU.js → chunk-ENLFKWYV.js} +2 -2
  65. package/dist/esm/{chunk-4XGGX2V2.js → chunk-ISOLUTZF.js} +2 -2
  66. package/dist/esm/{chunk-LXWPEDPZ.js → chunk-IYCPYN4Z.js} +2 -2
  67. package/dist/esm/{chunk-RJP5QUXO.js → chunk-L5PID6BU.js} +2 -2
  68. package/dist/esm/{chunk-BW5CO4PZ.js → chunk-METRW4HG.js} +2 -2
  69. package/dist/esm/{chunk-PYHEBG5I.js → chunk-MJOXEUBT.js} +2 -2
  70. package/dist/esm/{chunk-PHC3QBV3.js → chunk-MZXHKM4V.js} +2 -2
  71. package/dist/esm/chunk-OIUCV5HK.js +2 -0
  72. package/dist/esm/chunk-OIUCV5HK.js.map +7 -0
  73. package/dist/esm/{chunk-NIM7DIVH.js → chunk-PQKJNRMR.js} +2 -2
  74. package/dist/esm/{chunk-65GAANJD.js → chunk-UYCPDGWA.js} +2 -2
  75. package/dist/esm/chunk-XLMTTSRO.js +2 -0
  76. package/dist/esm/chunk-XLMTTSRO.js.map +7 -0
  77. package/dist/esm/chunk-XPRPYCII.js +2 -0
  78. package/dist/esm/{chunk-5KICSAT7.js.map → chunk-XPRPYCII.js.map} +2 -2
  79. package/dist/esm/{chunk-QWVU2TW4.js → chunk-ZK7A2MZZ.js} +2 -2
  80. package/dist/esm/chunk-ZUOK3KFY.js +2 -0
  81. package/dist/esm/chunk-ZUOK3KFY.js.map +7 -0
  82. package/dist/esm/components/_internal/base-button.js +1 -1
  83. package/dist/esm/components/alert-dialog.js +1 -1
  84. package/dist/esm/components/app-bar.d.ts +49 -0
  85. package/dist/esm/components/app-bar.d.ts.map +1 -0
  86. package/dist/esm/components/app-bar.js +2 -0
  87. package/dist/esm/components/app-bar.js.map +7 -0
  88. package/dist/esm/components/button.js +1 -1
  89. package/dist/esm/components/chatbar.js +1 -1
  90. package/dist/esm/components/checkbox-cards.js +1 -1
  91. package/dist/esm/components/context-menu.js +1 -1
  92. package/dist/esm/components/dialog.js +1 -1
  93. package/dist/esm/components/dropdown-menu.js +1 -1
  94. package/dist/esm/components/icon-button.js +1 -1
  95. package/dist/esm/components/index.d.ts +2 -1
  96. package/dist/esm/components/index.d.ts.map +1 -1
  97. package/dist/esm/components/index.js +1 -1
  98. package/dist/esm/components/message-scroller.d.ts +6 -0
  99. package/dist/esm/components/message-scroller.d.ts.map +1 -1
  100. package/dist/esm/components/message-scroller.js +1 -1
  101. package/dist/esm/components/notice.js +1 -1
  102. package/dist/esm/components/scroll-area.d.ts.map +1 -1
  103. package/dist/esm/components/scroll-area.js +1 -1
  104. package/dist/esm/components/scroll-area.props.d.ts +10 -0
  105. package/dist/esm/components/scroll-area.props.d.ts.map +1 -1
  106. package/dist/esm/components/scroll-area.props.js +1 -1
  107. package/dist/esm/components/sheet.js +1 -1
  108. package/dist/esm/components/shell.js +1 -1
  109. package/dist/esm/components/sidebar.js +1 -1
  110. package/dist/esm/components/table.js +1 -1
  111. package/dist/esm/components/theme-panel.js +1 -1
  112. package/dist/esm/components/toggle-button.js +1 -1
  113. package/dist/esm/components/toggle-icon-button.js +1 -1
  114. package/dist/esm/components/toolbar.d.ts +88 -36
  115. package/dist/esm/components/toolbar.d.ts.map +1 -1
  116. package/dist/esm/components/toolbar.js +1 -1
  117. package/dist/esm/components/toolbar.props.d.ts +28 -0
  118. package/dist/esm/components/toolbar.props.d.ts.map +1 -0
  119. package/dist/esm/components/toolbar.props.js +2 -0
  120. package/dist/esm/components/toolbar.props.js.map +7 -0
  121. package/dist/esm/components/user-card.js +1 -1
  122. package/dist/esm/hooks/use-scroll-edges.d.ts +12 -0
  123. package/dist/esm/hooks/use-scroll-edges.d.ts.map +1 -0
  124. package/dist/esm/hooks/use-scroll-edges.js +2 -0
  125. package/dist/esm/hooks/use-scroll-edges.js.map +7 -0
  126. package/dist/esm/index.js +1 -1
  127. package/dist/esm/props/index.js +1 -1
  128. package/package.json +1 -1
  129. package/src/components/app-bar.css +204 -0
  130. package/src/components/app-bar.tsx +256 -0
  131. package/src/components/index.css +2 -0
  132. package/src/components/index.tsx +13 -1
  133. package/src/components/message-scroller.tsx +24 -5
  134. package/src/components/scroll-area.css +44 -0
  135. package/src/components/scroll-area.props.tsx +8 -0
  136. package/src/components/scroll-area.tsx +7 -1
  137. package/src/components/toolbar.css +87 -160
  138. package/src/components/toolbar.props.tsx +27 -0
  139. package/src/components/toolbar.tsx +259 -193
  140. package/src/hooks/use-scroll-edges.ts +46 -0
  141. package/src/styles/tokens/color.css +19 -0
  142. package/styles.css +159 -26
  143. package/tokens/base.css +14 -0
  144. package/tokens.css +14 -0
  145. package/dist/esm/chunk-47LMOIW3.js +0 -2
  146. package/dist/esm/chunk-47LMOIW3.js.map +0 -7
  147. package/dist/esm/chunk-5KICSAT7.js +0 -2
  148. package/dist/esm/chunk-CTA5ODAO.js +0 -2
  149. package/dist/esm/chunk-CTA5ODAO.js.map +0 -7
  150. package/dist/esm/chunk-JKS2YXMS.js +0 -1
  151. package/dist/esm/chunk-W4WWN3RC.js +0 -2
  152. package/dist/esm/chunk-W4WWN3RC.js.map +0 -7
  153. /package/dist/esm/{chunk-ABSKRHVM.js.map → chunk-36A5BYCN.js.map} +0 -0
  154. /package/dist/esm/{chunk-73QGEY2I.js.map → chunk-3R4YLW4A.js.map} +0 -0
  155. /package/dist/esm/{chunk-ZZLZGHZQ.js.map → chunk-4LDVSTXI.js.map} +0 -0
  156. /package/dist/esm/{chunk-YQRSAP46.js.map → chunk-53SZ4Y23.js.map} +0 -0
  157. /package/dist/esm/{chunk-X3JM2565.js.map → chunk-6G3MPL4A.js.map} +0 -0
  158. /package/dist/esm/{chunk-DSJY5RI3.js.map → chunk-7JJT42J2.js.map} +0 -0
  159. /package/dist/esm/{chunk-JKS2YXMS.js.map → chunk-C7EVYSWK.js.map} +0 -0
  160. /package/dist/esm/{chunk-ERY5TH2M.js.map → chunk-CYU6BLZ5.js.map} +0 -0
  161. /package/dist/esm/{chunk-MWIIGYAJ.js.map → chunk-D6PHTHF2.js.map} +0 -0
  162. /package/dist/esm/{chunk-YTD3AJAU.js.map → chunk-ENLFKWYV.js.map} +0 -0
  163. /package/dist/esm/{chunk-4XGGX2V2.js.map → chunk-ISOLUTZF.js.map} +0 -0
  164. /package/dist/esm/{chunk-LXWPEDPZ.js.map → chunk-IYCPYN4Z.js.map} +0 -0
  165. /package/dist/esm/{chunk-RJP5QUXO.js.map → chunk-L5PID6BU.js.map} +0 -0
  166. /package/dist/esm/{chunk-BW5CO4PZ.js.map → chunk-METRW4HG.js.map} +0 -0
  167. /package/dist/esm/{chunk-PYHEBG5I.js.map → chunk-MJOXEUBT.js.map} +0 -0
  168. /package/dist/esm/{chunk-PHC3QBV3.js.map → chunk-MZXHKM4V.js.map} +0 -0
  169. /package/dist/esm/{chunk-NIM7DIVH.js.map → chunk-PQKJNRMR.js.map} +0 -0
  170. /package/dist/esm/{chunk-65GAANJD.js.map → chunk-UYCPDGWA.js.map} +0 -0
  171. /package/dist/esm/{chunk-QWVU2TW4.js.map → chunk-ZK7A2MZZ.js.map} +0 -0
@@ -0,0 +1,204 @@
1
+ /* stylelint-disable selector-max-specificity */
2
+ /* AppBar Component */
3
+
4
+ .rt-AppBar {
5
+ position: absolute;
6
+ z-index: 1;
7
+
8
+ /* --rt-app-bar-height and --rt-app-bar-width are set on parent element via JS */
9
+ /* (the old --rt-toolbar-* names are still set too, for layouts written before the rename) */
10
+ /* Use pt/pb/pl/pr="var(--rt-app-bar-height)" on content to avoid overlap */
11
+ }
12
+
13
+ /* Grid layout for true centering */
14
+ .rt-AppBarInner {
15
+ display: grid;
16
+ grid-template-columns: 1fr auto 1fr;
17
+ align-items: center;
18
+ width: 100%;
19
+ height: 100%;
20
+ gap: var(--space-2);
21
+ }
22
+
23
+ /* Vertical app bars use row-based grid */
24
+ .rt-AppBar:where([data-anchor='left'], [data-anchor='right']) .rt-AppBarInner {
25
+ grid-template-columns: 1fr;
26
+ grid-template-rows: 1fr auto 1fr;
27
+ }
28
+
29
+ .rt-AppBar:where([data-anchor='left'], [data-anchor='right']) .rt-AppBarLeft {
30
+ grid-column: 1;
31
+ grid-row: 1;
32
+ }
33
+
34
+ .rt-AppBar:where([data-anchor='left'], [data-anchor='right']) .rt-AppBarCenter {
35
+ grid-column: 1;
36
+ grid-row: 2;
37
+ }
38
+
39
+ .rt-AppBar:where([data-anchor='left'], [data-anchor='right']) .rt-AppBarRight {
40
+ grid-column: 1;
41
+ grid-row: 3;
42
+ }
43
+
44
+ /* * * * * * * * * * * * * * * * * * * */
45
+ /* */
46
+ /* Anchor positions */
47
+ /* */
48
+ /* * * * * * * * * * * * * * * * * * * */
49
+
50
+ .rt-AppBar:where([data-anchor='top']) {
51
+ top: 0;
52
+ left: 0;
53
+ right: 0;
54
+ }
55
+
56
+ .rt-AppBar:where([data-anchor='bottom']) {
57
+ bottom: 0;
58
+ left: 0;
59
+ right: 0;
60
+ }
61
+
62
+ .rt-AppBar:where([data-anchor='left']) {
63
+ top: 0;
64
+ bottom: 0;
65
+ left: 0;
66
+ }
67
+
68
+ .rt-AppBar:where([data-anchor='right']) {
69
+ top: 0;
70
+ bottom: 0;
71
+ right: 0;
72
+ }
73
+
74
+ /* * * * * * * * * * * * * * * * * * * */
75
+ /* */
76
+ /* Inset mode (default) */
77
+ /* Edge-to-edge positioning */
78
+ /* */
79
+ /* * * * * * * * * * * * * * * * * * * */
80
+
81
+ /* Remove border-radius in inset mode */
82
+ .rt-AppBar:where(:not([data-floating])) {
83
+ --base-card-border-radius: 0;
84
+ border-radius: 0;
85
+ }
86
+
87
+ /* Override Card's box-shadow to only show exposed edge */
88
+ /* Top anchor: only show bottom border */
89
+ .rt-AppBar:where([data-anchor='top']:not([data-floating])) {
90
+ &::after {
91
+ box-shadow: inset 0 -1px 0 0 var(--gray-6);
92
+ }
93
+ :where([data-panel-background='translucent'], [data-material='translucent']) &::after,
94
+ &:where([data-panel-background='translucent'], [data-material='translucent'])::after {
95
+ box-shadow: inset 0 -1px 0 0 var(--gray-a6);
96
+ }
97
+ }
98
+
99
+ /* Bottom anchor: only show top border */
100
+ .rt-AppBar:where([data-anchor='bottom']:not([data-floating])) {
101
+ &::after {
102
+ box-shadow: inset 0 1px 0 0 var(--gray-6);
103
+ }
104
+ :where([data-panel-background='translucent'], [data-material='translucent']) &::after,
105
+ &:where([data-panel-background='translucent'], [data-material='translucent'])::after {
106
+ box-shadow: inset 0 1px 0 0 var(--gray-a6);
107
+ }
108
+ }
109
+
110
+ /* Left anchor: only show right border */
111
+ .rt-AppBar:where([data-anchor='left']:not([data-floating])) {
112
+ &::after {
113
+ box-shadow: inset -1px 0 0 0 var(--gray-6);
114
+ }
115
+ :where([data-panel-background='translucent'], [data-material='translucent']) &::after,
116
+ &:where([data-panel-background='translucent'], [data-material='translucent'])::after {
117
+ box-shadow: inset -1px 0 0 0 var(--gray-a6);
118
+ }
119
+ }
120
+
121
+ /* Right anchor: only show left border */
122
+ .rt-AppBar:where([data-anchor='right']:not([data-floating])) {
123
+ &::after {
124
+ box-shadow: inset 1px 0 0 0 var(--gray-6);
125
+ }
126
+ :where([data-panel-background='translucent'], [data-material='translucent']) &::after,
127
+ &:where([data-panel-background='translucent'], [data-material='translucent'])::after {
128
+ box-shadow: inset 1px 0 0 0 var(--gray-a6);
129
+ }
130
+ }
131
+
132
+ /* Ghost variant in normal mode has no border */
133
+ .rt-AppBar:where(.rt-variant-ghost:not([data-floating]))::after {
134
+ box-shadow: none;
135
+ }
136
+
137
+ /* Soft variant in normal mode has no border */
138
+ .rt-AppBar:where(.rt-variant-soft:not([data-floating]))::after {
139
+ box-shadow: none;
140
+ }
141
+
142
+ /* * * * * * * * * * * * * * * * * * * */
143
+ /* */
144
+ /* Floating mode */
145
+ /* Full card treatment */
146
+ /* */
147
+ /* * * * * * * * * * * * * * * * * * * */
148
+
149
+ /* Floating mode: margin controlled via offset prop in JS */
150
+ /* Inherits Card's border-radius (not overridden like inset mode) */
151
+
152
+ /* * * * * * * * * * * * * * * * * * * */
153
+ /* */
154
+ /* Title truncation */
155
+ /* */
156
+ /* * * * * * * * * * * * * * * * * * * */
157
+
158
+ .rt-AppBarTitle {
159
+ /* Ensure title can truncate within flex container */
160
+ min-width: 0;
161
+ flex-shrink: 1;
162
+ }
163
+
164
+ /* * * * * * * * * * * * * * * * * * * */
165
+ /* */
166
+ /* Section alignment */
167
+ /* */
168
+ /* * * * * * * * * * * * * * * * * * * */
169
+
170
+ .rt-AppBarSection {
171
+ min-width: 0;
172
+ }
173
+
174
+ .rt-AppBarLeft {
175
+ grid-column: 1;
176
+ justify-self: start;
177
+ }
178
+
179
+ .rt-AppBarCenter {
180
+ grid-column: 2;
181
+ justify-self: center;
182
+ }
183
+
184
+ /* Ensure Right always stays in the right column */
185
+ .rt-AppBarRight {
186
+ grid-column: 3;
187
+ justify-self: end;
188
+ }
189
+
190
+ /* Vertical app bar section alignment */
191
+ .rt-AppBar:where([data-anchor='left'], [data-anchor='right']) .rt-AppBarLeft {
192
+ align-self: start;
193
+ justify-self: center;
194
+ }
195
+
196
+ .rt-AppBar:where([data-anchor='left'], [data-anchor='right']) .rt-AppBarCenter {
197
+ align-self: center;
198
+ justify-self: center;
199
+ }
200
+
201
+ .rt-AppBar:where([data-anchor='left'], [data-anchor='right']) .rt-AppBarRight {
202
+ align-self: end;
203
+ justify-self: center;
204
+ }
@@ -0,0 +1,256 @@
1
+ import * as React from 'react';
2
+ import classNames from 'classnames';
3
+
4
+ import { Card } from './card.js';
5
+ import { Flex } from './flex.js';
6
+ import { Heading } from './heading.js';
7
+
8
+ import type { CardProps } from './card.js';
9
+ import type { FlexProps } from './flex.js';
10
+
11
+ // ============================================================================
12
+ // AppBar Root
13
+ // ============================================================================
14
+
15
+ type AppBarRootElement = React.ElementRef<'div'>;
16
+ type Anchor = 'top' | 'bottom' | 'left' | 'right';
17
+ type SpaceScale = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
18
+
19
+ interface AppBarRootProps extends Omit<CardProps, 'asChild'> {
20
+ /**
21
+ * Which edge to anchor to. Sets position: sticky and the appropriate edge.
22
+ * @default "top"
23
+ */
24
+ anchor?: Anchor;
25
+ /**
26
+ * Floating mode adds margin from edges for a detached look with absolute positioning.
27
+ * When false (default), uses inset mode with negative margins to go edge-to-edge inside Cards.
28
+ * @default false
29
+ */
30
+ floating?: boolean;
31
+ /**
32
+ * Margin offset from edges when floating. Uses space scale values.
33
+ * @default "2"
34
+ */
35
+ offset?: SpaceScale;
36
+ }
37
+
38
+ /**
39
+ * An app bar: the band anchored to one edge of a pane, with left, center and right sections
40
+ * and a title. It publishes its height (or width) to its parent as `--rt-app-bar-height`
41
+ * (`--rt-app-bar-width`) so content can pad itself clear of it.
42
+ *
43
+ * This component was called `Toolbar` until 0.11.0. It is not a `role="toolbar"`: for a row of
44
+ * controls with one tab stop and arrow-key navigation, use `Toolbar`.
45
+ */
46
+ const AppBarRoot = React.forwardRef<AppBarRootElement, AppBarRootProps>(
47
+ (props, forwardedRef) => {
48
+ const {
49
+ anchor = 'top',
50
+ floating = false,
51
+ offset = '2',
52
+ size = '1',
53
+ variant = 'ghost',
54
+ material,
55
+ className,
56
+ style,
57
+ children,
58
+ ...restProps
59
+ } = props;
60
+
61
+ const isHorizontal = anchor === 'top' || anchor === 'bottom';
62
+ const internalRef = React.useRef<HTMLDivElement>(null);
63
+
64
+ // Merge refs
65
+ const mergedRef = React.useCallback(
66
+ (node: HTMLDivElement | null) => {
67
+ (internalRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
68
+ if (typeof forwardedRef === 'function') {
69
+ forwardedRef(node);
70
+ } else if (forwardedRef) {
71
+ forwardedRef.current = node;
72
+ }
73
+ },
74
+ [forwardedRef],
75
+ );
76
+
77
+ // Expose height/width as CSS variable on parent element
78
+ React.useEffect(() => {
79
+ const element = internalRef.current;
80
+ if (!element) return;
81
+
82
+ const parent = element.parentElement;
83
+ if (!parent) return;
84
+
85
+ const update = () => {
86
+ let dimension = isHorizontal ? element.offsetHeight : element.offsetWidth;
87
+
88
+ // For floating mode, include the margin in the total space
89
+ if (floating) {
90
+ const computedStyle = getComputedStyle(element);
91
+ const margin = parseFloat(computedStyle.marginTop) || 0;
92
+ dimension += margin * 2;
93
+ }
94
+
95
+ // `--rt-toolbar-*` are the names this bar published while it was called Toolbar.
96
+ // They are still written so existing layouts keep their offset; prefer `--rt-app-bar-*`.
97
+ if (isHorizontal) {
98
+ parent.style.setProperty('--rt-app-bar-height', `${dimension}px`);
99
+ parent.style.setProperty('--rt-toolbar-height', `${dimension}px`);
100
+ } else {
101
+ parent.style.setProperty('--rt-app-bar-width', `${dimension}px`);
102
+ parent.style.setProperty('--rt-toolbar-width', `${dimension}px`);
103
+ }
104
+ };
105
+
106
+ update();
107
+
108
+ const resizeObserver = new ResizeObserver(update);
109
+ resizeObserver.observe(element);
110
+
111
+ return () => resizeObserver.disconnect();
112
+ }, [isHorizontal, floating]);
113
+
114
+ const computedStyle = floating
115
+ ? { margin: `var(--space-${offset})`, ...style }
116
+ : style;
117
+
118
+ return (
119
+ <Card
120
+ ref={mergedRef}
121
+ size={size}
122
+ variant={variant}
123
+ material={material}
124
+ data-anchor={anchor}
125
+ data-floating={floating || undefined}
126
+ className={classNames('rt-AppBar', className)}
127
+ style={computedStyle}
128
+ {...restProps}
129
+ >
130
+ <div className="rt-AppBarInner">
131
+ {children}
132
+ </div>
133
+ </Card>
134
+ );
135
+ },
136
+ );
137
+ AppBarRoot.displayName = 'AppBar.Root';
138
+
139
+ // ============================================================================
140
+ // AppBar Left (for horizontal) / Start (alias)
141
+ // ============================================================================
142
+
143
+ type AppBarSectionElement = React.ElementRef<'div'>;
144
+ type AppBarSectionProps = FlexProps;
145
+
146
+ const AppBarLeft = React.forwardRef<AppBarSectionElement, AppBarSectionProps>(
147
+ ({ gap = '2', align = 'center', className, children, ...props }, forwardedRef) => (
148
+ <Flex
149
+ ref={forwardedRef}
150
+ align={align}
151
+ gap={gap}
152
+ className={classNames('rt-AppBarSection', 'rt-AppBarLeft', className)}
153
+ {...props}
154
+ >
155
+ {children}
156
+ </Flex>
157
+ ),
158
+ );
159
+ AppBarLeft.displayName = 'AppBar.Left';
160
+
161
+ // ============================================================================
162
+ // AppBar Center
163
+ // ============================================================================
164
+
165
+ const AppBarCenter = React.forwardRef<AppBarSectionElement, AppBarSectionProps>(
166
+ ({ gap = '2', align = 'center', justify = 'center', className, children, ...props }, forwardedRef) => (
167
+ <Flex
168
+ ref={forwardedRef}
169
+ align={align}
170
+ justify={justify}
171
+ gap={gap}
172
+ className={classNames('rt-AppBarSection', 'rt-AppBarCenter', className)}
173
+ {...props}
174
+ >
175
+ {children}
176
+ </Flex>
177
+ ),
178
+ );
179
+ AppBarCenter.displayName = 'AppBar.Center';
180
+
181
+ // ============================================================================
182
+ // AppBar Right (for horizontal) / End (alias)
183
+ // ============================================================================
184
+
185
+ const AppBarRight = React.forwardRef<AppBarSectionElement, AppBarSectionProps>(
186
+ ({ gap = '2', align = 'center', justify = 'end', className, children, ...props }, forwardedRef) => (
187
+ <Flex
188
+ ref={forwardedRef}
189
+ align={align}
190
+ justify={justify}
191
+ gap={gap}
192
+ className={classNames('rt-AppBarSection', 'rt-AppBarRight', className)}
193
+ {...props}
194
+ >
195
+ {children}
196
+ </Flex>
197
+ ),
198
+ );
199
+ AppBarRight.displayName = 'AppBar.Right';
200
+
201
+ // ============================================================================
202
+ // AppBar Title
203
+ // ============================================================================
204
+
205
+ type AppBarTitleElement = React.ElementRef<typeof Heading>;
206
+ type AppBarTitleProps = React.ComponentPropsWithoutRef<typeof Heading>;
207
+
208
+ const AppBarTitle = React.forwardRef<AppBarTitleElement, AppBarTitleProps>(
209
+ ({ className, size = '2', weight = 'medium', truncate = true, children, ...props }, forwardedRef) => (
210
+ <Heading
211
+ ref={forwardedRef}
212
+ size={size}
213
+ weight={weight}
214
+ truncate={truncate}
215
+ className={classNames('rt-AppBarTitle', className)}
216
+ {...props}
217
+ >
218
+ {children}
219
+ </Heading>
220
+ ),
221
+ );
222
+ AppBarTitle.displayName = 'AppBar.Title';
223
+
224
+ // ============================================================================
225
+ // Exports
226
+ // ============================================================================
227
+
228
+ const AppBar = AppBarRoot as typeof AppBarRoot & {
229
+ Root: typeof AppBarRoot;
230
+ Left: typeof AppBarLeft;
231
+ Center: typeof AppBarCenter;
232
+ Right: typeof AppBarRight;
233
+ Title: typeof AppBarTitle;
234
+ };
235
+
236
+ AppBar.Root = AppBarRoot;
237
+ AppBar.Left = AppBarLeft;
238
+ AppBar.Center = AppBarCenter;
239
+ AppBar.Right = AppBarRight;
240
+ AppBar.Title = AppBarTitle;
241
+
242
+ export {
243
+ AppBar,
244
+ AppBarRoot as Root,
245
+ AppBarLeft as Left,
246
+ AppBarCenter as Center,
247
+ AppBarRight as Right,
248
+ AppBarTitle as Title,
249
+ };
250
+ export type {
251
+ AppBarRootProps as RootProps,
252
+ AppBarSectionProps as LeftProps,
253
+ AppBarSectionProps as CenterProps,
254
+ AppBarSectionProps as RightProps,
255
+ AppBarTitleProps as TitleProps,
256
+ };
@@ -57,6 +57,8 @@
57
57
  @import './text-area.css';
58
58
  @import './text-field.css';
59
59
  @import './theme-panel.css';
60
+ /* AppBar keeps the place its old toolbar.css had: it extends Card and must come after card.css */
61
+ @import './app-bar.css';
60
62
  @import './toolbar.css';
61
63
  @import './tooltip.css';
62
64
  @import './user-card.css';
@@ -1,6 +1,7 @@
1
1
  export { AccessibleIcon, type AccessibleIconProps } from './accessible-icon.js';
2
2
  export * as Accordion from './accordion.js';
3
3
  export * as AlertDialog from './alert-dialog.js';
4
+ export { AppBar } from './app-bar.js';
4
5
  export { AspectRatio, type AspectRatioProps } from './aspect-ratio.js';
5
6
  export { Avatar, type AvatarProps } from './avatar.js';
6
7
  export { Badge, type BadgeProps } from './badge.js';
@@ -71,7 +72,18 @@ export {
71
72
  export { Theme, ThemeContext, type ThemeContextValue, type ThemeProps, useThemeContext } from './theme.js';
72
73
  export { ToggleButton, type ToggleButtonProps } from './toggle-button.js';
73
74
  export { ToggleIconButton, type ToggleIconButtonProps } from './toggle-icon-button.js';
74
- export { Toolbar } from './toolbar.js';
75
+ export {
76
+ Toolbar,
77
+ ToolbarGroup,
78
+ ToolbarButton,
79
+ ToolbarSeparator,
80
+ ToolbarTitle,
81
+ type ToolbarProps,
82
+ type ToolbarGroupProps,
83
+ type ToolbarButtonProps,
84
+ type ToolbarSeparatorProps,
85
+ type ToolbarTitleProps,
86
+ } from './toolbar.js';
75
87
  export { Tooltip, type TooltipProps } from './tooltip.js';
76
88
  export { UserCard, type UserCardProps } from './user-card.js';
77
89
  export { VirtualMenu, type VirtualMenuProps, type VirtualMenuItemProps, type VirtualMenuRenderItemProps } from './virtual-menu.js';
@@ -40,7 +40,10 @@ import {
40
40
  type MessageScrollerVisibilityState,
41
41
  } from '@shadcn/react/message-scroller';
42
42
 
43
+ import { useComposedRefs } from 'radix-ui/internal';
44
+
43
45
  import { IconButton, type IconButtonProps } from './icon-button.js';
46
+ import { useScrollEdges } from '../hooks/use-scroll-edges.js';
44
47
 
45
48
  interface RootProps extends React.ComponentPropsWithoutRef<'div'> {
46
49
  /**
@@ -78,14 +81,30 @@ interface ViewportProps extends React.ComponentPropsWithoutRef<'div'> {
78
81
  'aria-label'?: string;
79
82
  /** Keep the reader's place when older messages are added above. Default `true`. */
80
83
  preserveScrollOnPrepend?: boolean;
84
+ /**
85
+ * Fades messages toward the top and bottom edges while there is more past them. A floating
86
+ * band over an edge (a toolbar, a goal tray, a composer) can set `--scroll-fade-start` or
87
+ * `--scroll-fade-end` to its height, so messages fade as they pass under it. Default `true`.
88
+ */
89
+ fade?: boolean;
81
90
  }
82
91
 
83
92
  /** The scrolling element. */
84
- const Viewport = React.forwardRef<HTMLDivElement, ViewportProps>(({ className, 'aria-label': label, ...props }, forwardedRef) => (
85
- // The primitive names an unnamed viewport "Messages", in English. An empty name is skipped by
86
- // accessible-name computation, so a transcript is only named when the app names it.
87
- <Primitive.Viewport aria-label={label ?? ''} {...props} ref={forwardedRef} className={classNames('rt-MessageScrollerViewport', className)} />
88
- ));
93
+ const Viewport = React.forwardRef<HTMLDivElement, ViewportProps>(({ className, 'aria-label': label, fade = true, ...props }, forwardedRef) => {
94
+ const setEdgesElement = useScrollEdges<HTMLDivElement>(fade);
95
+ const ref = useComposedRefs(forwardedRef, setEdgesElement);
96
+ return (
97
+ // The primitive names an unnamed viewport "Messages", in English. An empty name is skipped by
98
+ // accessible-name computation, so a transcript is only named when the app names it.
99
+ <Primitive.Viewport
100
+ aria-label={label ?? ''}
101
+ {...props}
102
+ ref={ref}
103
+ data-fade={fade ? '' : undefined}
104
+ className={classNames('rt-MessageScrollerViewport', className)}
105
+ />
106
+ );
107
+ });
89
108
  Viewport.displayName = 'MessageScroller.Viewport';
90
109
 
91
110
  type ContentProps = React.ComponentPropsWithoutRef<'div'>;
@@ -144,3 +144,47 @@
144
144
  }
145
145
  }
146
146
  }
147
+
148
+ /***************************************************************************************************
149
+ * *
150
+ * SCROLL FADE *
151
+ * *
152
+ ***************************************************************************************************/
153
+
154
+ /*
155
+ * Content dissolves toward an edge that has more past it. A mask, not a painted gradient: a paint
156
+ * has to fade toward a colour, and the right colour differs on every surface, while a mask fades
157
+ * the content itself and lets whatever is behind show through.
158
+ *
159
+ * `--scroll-overflow-start/-end` are how far content is hidden past each edge, written by
160
+ * Kookie's scroll-edges hook. `min()` against the fade length is the whole behaviour: an edge
161
+ * with nothing past it fades over 0px, and the fade ramps in over the first pixels of scrolling.
162
+ * A floating band over an edge sets `--scroll-fade-start` or `--scroll-fade-end` to its height.
163
+ *
164
+ * Shared by ScrollArea (on its viewport) and MessageScroller (on its viewport). Scrollbars are
165
+ * siblings of the viewport, outside the mask, so the thumb stays crisp.
166
+ */
167
+ :where(.rt-ScrollAreaRoot[data-fade]) > .rt-ScrollAreaViewport,
168
+ .rt-MessageScrollerViewport:where([data-fade]) {
169
+ mask-image: linear-gradient(
170
+ to bottom,
171
+ transparent 0%,
172
+ black min(var(--scroll-overflow-start, 0px), var(--scroll-fade-start, var(--scroll-fade-size))),
173
+ black calc(100% - min(var(--scroll-overflow-end, 0px), var(--scroll-fade-end, var(--scroll-fade-size)))),
174
+ transparent 100%
175
+ );
176
+ }
177
+
178
+ :root,
179
+ .radix-themes {
180
+ --scroll-fade-size: var(--space-6);
181
+ }
182
+
183
+ /*
184
+ * A band is above the scroller it floats over and above nothing else. The reach hooks inherit,
185
+ * so a scroller nested inside another takes the designed length, not its ancestor's band.
186
+ */
187
+ :where(.rt-ScrollAreaViewport, .rt-MessageScrollerViewport) :where(.rt-ScrollAreaRoot, .rt-MessageScrollerViewport) {
188
+ --scroll-fade-start: initial;
189
+ --scroll-fade-end: initial;
190
+ }
@@ -11,9 +11,17 @@ const scrollAreaPropDefs = {
11
11
  size: { type: 'enum', className: 'rt-r-size', values: sizes, default: '1', responsive: true },
12
12
  ...radiusPropDef,
13
13
  scrollbars: { type: 'enum', values: scrollbarsValues, default: 'both' },
14
+ /**
15
+ * Fades the content toward the top and bottom edges while there is more content past them.
16
+ * An edge with nothing hidden past it does not fade. A floating band over an edge can set
17
+ * `--scroll-fade-start` or `--scroll-fade-end` to its height, so content fades as it passes
18
+ * under the band.
19
+ */
20
+ fade: { type: 'boolean', default: false },
14
21
  } satisfies {
15
22
  size: PropDef<(typeof sizes)[number]>;
16
23
  scrollbars: PropDef<(typeof scrollbarsValues)[number]>;
24
+ fade: PropDef<boolean>;
17
25
  };
18
26
 
19
27
  export { scrollAreaPropDefs };
@@ -1,8 +1,10 @@
1
1
  import * as React from 'react';
2
2
  import classNames from 'classnames';
3
3
  import { ScrollArea as ScrollAreaPrimitive } from 'radix-ui';
4
+ import { useComposedRefs } from 'radix-ui/internal';
4
5
 
5
6
  import { scrollAreaPropDefs } from './scroll-area.props.js';
7
+ import { useScrollEdges } from '../hooks/use-scroll-edges.js';
6
8
  import { extractMarginProps } from '../helpers/extract-margin-props.js';
7
9
  import { getMarginStyles } from '../helpers/get-margin-styles.js';
8
10
  import { getResponsiveClassNames } from '../helpers/get-responsive-styles.js';
@@ -35,14 +37,18 @@ const ScrollArea = React.forwardRef<ScrollAreaElement, ScrollAreaProps>((props,
35
37
  size = scrollAreaPropDefs.size.default,
36
38
  radius = scrollAreaPropDefs.radius.default,
37
39
  scrollbars = scrollAreaPropDefs.scrollbars.default,
40
+ fade = scrollAreaPropDefs.fade.default,
38
41
  ...viewportProps
39
42
  } = marginRest;
43
+ const setEdgesElement = useScrollEdges<HTMLDivElement>(fade);
44
+ const viewportRef = useComposedRefs(forwardedRef, setEdgesElement);
40
45
 
41
46
  return (
42
47
  <ScrollAreaPrimitive.Root
43
48
  type={type}
44
49
  scrollHideDelay={scrollHideDelay}
45
50
  className={classNames('rt-ScrollAreaRoot', marginClassNames, className)}
51
+ data-fade={fade ? '' : undefined}
46
52
  style={mergeStyles(marginCustomProperties, style)}
47
53
  asChild={asChild}
48
54
  >
@@ -50,7 +56,7 @@ const ScrollArea = React.forwardRef<ScrollAreaElement, ScrollAreaProps>((props,
50
56
  <>
51
57
  <ScrollAreaPrimitive.Viewport
52
58
  {...viewportProps}
53
- ref={forwardedRef}
59
+ ref={viewportRef}
54
60
  className="rt-ScrollAreaViewport"
55
61
  >
56
62
  {children}