@kushagradhawan/kookie-ui 0.1.50 → 0.1.52
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.
- package/components.css +582 -116
- package/dist/cjs/components/_internal/shell-bottom.d.ts +31 -5
- package/dist/cjs/components/_internal/shell-bottom.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-bottom.js +1 -1
- package/dist/cjs/components/_internal/shell-bottom.js.map +3 -3
- package/dist/cjs/components/_internal/shell-handles.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-handles.js +1 -1
- package/dist/cjs/components/_internal/shell-handles.js.map +3 -3
- package/dist/cjs/components/_internal/shell-inspector.d.ts +23 -5
- package/dist/cjs/components/_internal/shell-inspector.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-inspector.js +1 -1
- package/dist/cjs/components/_internal/shell-inspector.js.map +3 -3
- package/dist/cjs/components/_internal/shell-sidebar.d.ts +24 -6
- package/dist/cjs/components/_internal/shell-sidebar.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-sidebar.js +1 -1
- package/dist/cjs/components/_internal/shell-sidebar.js.map +3 -3
- package/dist/cjs/components/chatbar.d.ts +21 -2
- package/dist/cjs/components/chatbar.d.ts.map +1 -1
- package/dist/cjs/components/chatbar.js +1 -1
- package/dist/cjs/components/chatbar.js.map +3 -3
- package/dist/cjs/components/shell.context.d.ts +88 -1
- package/dist/cjs/components/shell.context.d.ts.map +1 -1
- package/dist/cjs/components/shell.context.js +1 -1
- package/dist/cjs/components/shell.context.js.map +3 -3
- package/dist/cjs/components/shell.d.ts +51 -13
- package/dist/cjs/components/shell.d.ts.map +1 -1
- package/dist/cjs/components/shell.hooks.d.ts +7 -1
- package/dist/cjs/components/shell.hooks.d.ts.map +1 -1
- package/dist/cjs/components/shell.hooks.js +1 -1
- package/dist/cjs/components/shell.hooks.js.map +3 -3
- package/dist/cjs/components/shell.js +1 -1
- package/dist/cjs/components/shell.js.map +3 -3
- package/dist/cjs/components/shell.types.d.ts +1 -0
- package/dist/cjs/components/shell.types.d.ts.map +1 -1
- package/dist/cjs/components/shell.types.js +1 -1
- package/dist/cjs/components/shell.types.js.map +2 -2
- package/dist/esm/components/_internal/shell-bottom.d.ts +31 -5
- package/dist/esm/components/_internal/shell-bottom.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-bottom.js +1 -1
- package/dist/esm/components/_internal/shell-bottom.js.map +3 -3
- package/dist/esm/components/_internal/shell-handles.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-handles.js +1 -1
- package/dist/esm/components/_internal/shell-handles.js.map +3 -3
- package/dist/esm/components/_internal/shell-inspector.d.ts +23 -5
- package/dist/esm/components/_internal/shell-inspector.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-inspector.js +1 -1
- package/dist/esm/components/_internal/shell-inspector.js.map +3 -3
- package/dist/esm/components/_internal/shell-sidebar.d.ts +24 -6
- package/dist/esm/components/_internal/shell-sidebar.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-sidebar.js +1 -1
- package/dist/esm/components/_internal/shell-sidebar.js.map +3 -3
- package/dist/esm/components/chatbar.d.ts +21 -2
- package/dist/esm/components/chatbar.d.ts.map +1 -1
- package/dist/esm/components/chatbar.js +1 -1
- package/dist/esm/components/chatbar.js.map +3 -3
- package/dist/esm/components/shell.context.d.ts +88 -1
- package/dist/esm/components/shell.context.d.ts.map +1 -1
- package/dist/esm/components/shell.context.js +1 -1
- package/dist/esm/components/shell.context.js.map +3 -3
- package/dist/esm/components/shell.d.ts +51 -13
- package/dist/esm/components/shell.d.ts.map +1 -1
- package/dist/esm/components/shell.hooks.d.ts +7 -1
- package/dist/esm/components/shell.hooks.d.ts.map +1 -1
- package/dist/esm/components/shell.hooks.js +1 -1
- package/dist/esm/components/shell.hooks.js.map +3 -3
- package/dist/esm/components/shell.js +1 -1
- package/dist/esm/components/shell.js.map +3 -3
- package/dist/esm/components/shell.types.d.ts +1 -0
- package/dist/esm/components/shell.types.d.ts.map +1 -1
- package/dist/esm/components/shell.types.js.map +2 -2
- package/package.json +14 -3
- package/schemas/base-button.json +1 -1
- package/schemas/button.json +1 -1
- package/schemas/icon-button.json +1 -1
- package/schemas/index.json +6 -6
- package/schemas/toggle-button.json +1 -1
- package/schemas/toggle-icon-button.json +1 -1
- package/src/components/_internal/base-menu.css +16 -16
- package/src/components/_internal/base-sidebar-menu.css +23 -20
- package/src/components/_internal/base-sidebar.css +13 -0
- package/src/components/_internal/shell-bottom.tsx +176 -49
- package/src/components/_internal/shell-handles.tsx +29 -4
- package/src/components/_internal/shell-inspector.tsx +175 -43
- package/src/components/_internal/shell-sidebar.tsx +177 -93
- package/src/components/chatbar.css +240 -21
- package/src/components/chatbar.tsx +280 -291
- package/src/components/sheet.css +8 -16
- package/src/components/shell.context.tsx +79 -3
- package/src/components/shell.css +0 -1
- package/src/components/shell.hooks.ts +35 -0
- package/src/components/shell.tsx +574 -235
- package/src/components/shell.types.ts +2 -0
- package/src/components/sidebar.css +2 -2
- package/styles.css +582 -116
package/schemas/index.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"title": "Kookie UI Button Components",
|
|
4
4
|
"description": "Complete JSON Schema collection for all button components in Kookie UI",
|
|
5
5
|
"version": "1.0.0",
|
|
6
|
-
"generatedAt": "2025-09-
|
|
6
|
+
"generatedAt": "2025-09-18T07:33:50.471Z",
|
|
7
7
|
"source": "Zod schemas",
|
|
8
8
|
"components": {
|
|
9
9
|
"base-button": {
|
|
@@ -287,7 +287,7 @@
|
|
|
287
287
|
"title": "Base-button Component Props",
|
|
288
288
|
"description": "Props schema for the base-button component in Kookie UI",
|
|
289
289
|
"version": "1.0.0",
|
|
290
|
-
"generatedAt": "2025-09-
|
|
290
|
+
"generatedAt": "2025-09-18T07:33:50.462Z",
|
|
291
291
|
"source": "Zod schema"
|
|
292
292
|
},
|
|
293
293
|
"button": {
|
|
@@ -822,7 +822,7 @@
|
|
|
822
822
|
"title": "Button Component Props",
|
|
823
823
|
"description": "Props schema for the button component in Kookie UI",
|
|
824
824
|
"version": "1.0.0",
|
|
825
|
-
"generatedAt": "2025-09-
|
|
825
|
+
"generatedAt": "2025-09-18T07:33:50.467Z",
|
|
826
826
|
"source": "Zod schema"
|
|
827
827
|
},
|
|
828
828
|
"icon-button": {
|
|
@@ -1140,7 +1140,7 @@
|
|
|
1140
1140
|
"title": "Icon-button Component Props",
|
|
1141
1141
|
"description": "Props schema for the icon-button component in Kookie UI",
|
|
1142
1142
|
"version": "1.0.0",
|
|
1143
|
-
"generatedAt": "2025-09-
|
|
1143
|
+
"generatedAt": "2025-09-18T07:33:50.468Z",
|
|
1144
1144
|
"source": "Zod schema"
|
|
1145
1145
|
},
|
|
1146
1146
|
"toggle-button": {
|
|
@@ -1683,7 +1683,7 @@
|
|
|
1683
1683
|
"title": "Toggle-button Component Props",
|
|
1684
1684
|
"description": "Props schema for the toggle-button component in Kookie UI",
|
|
1685
1685
|
"version": "1.0.0",
|
|
1686
|
-
"generatedAt": "2025-09-
|
|
1686
|
+
"generatedAt": "2025-09-18T07:33:50.469Z",
|
|
1687
1687
|
"source": "Zod schema"
|
|
1688
1688
|
},
|
|
1689
1689
|
"toggle-icon-button": {
|
|
@@ -2009,7 +2009,7 @@
|
|
|
2009
2009
|
"title": "Toggle-icon-button Component Props",
|
|
2010
2010
|
"description": "Props schema for the toggle-icon-button component in Kookie UI",
|
|
2011
2011
|
"version": "1.0.0",
|
|
2012
|
-
"generatedAt": "2025-09-
|
|
2012
|
+
"generatedAt": "2025-09-18T07:33:50.470Z",
|
|
2013
2013
|
"source": "Zod schema"
|
|
2014
2014
|
}
|
|
2015
2015
|
}
|
|
@@ -538,6 +538,6 @@
|
|
|
538
538
|
"title": "Toggle-button Component Props",
|
|
539
539
|
"description": "Props schema for the toggle-button component in Kookie UI",
|
|
540
540
|
"version": "1.0.0",
|
|
541
|
-
"generatedAt": "2025-09-
|
|
541
|
+
"generatedAt": "2025-09-18T07:33:50.469Z",
|
|
542
542
|
"source": "Zod schema"
|
|
543
543
|
}
|
|
@@ -321,6 +321,6 @@
|
|
|
321
321
|
"title": "Toggle-icon-button Component Props",
|
|
322
322
|
"description": "Props schema for the toggle-icon-button component in Kookie UI",
|
|
323
323
|
"version": "1.0.0",
|
|
324
|
-
"generatedAt": "2025-09-
|
|
324
|
+
"generatedAt": "2025-09-18T07:33:50.470Z",
|
|
325
325
|
"source": "Zod schema"
|
|
326
326
|
}
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
box-sizing: border-box;
|
|
52
52
|
|
|
53
53
|
:where(.rt-BaseMenuContent:has(.rt-ScrollAreaScrollbar[data-orientation='vertical'])) & {
|
|
54
|
-
padding-
|
|
54
|
+
padding-inline-end: var(--space-3);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
min-height: var(--base-menu-item-height);
|
|
63
63
|
padding-top: var(--base-menu-item-padding-y);
|
|
64
64
|
padding-bottom: var(--base-menu-item-padding-y);
|
|
65
|
-
padding-
|
|
66
|
-
padding-
|
|
65
|
+
padding-inline-start: var(--base-menu-item-padding-left);
|
|
66
|
+
padding-inline-end: var(--base-menu-item-padding-right);
|
|
67
67
|
box-sizing: border-box;
|
|
68
68
|
position: relative;
|
|
69
69
|
outline: none;
|
|
@@ -95,19 +95,19 @@
|
|
|
95
95
|
.rt-BaseMenuShortcut {
|
|
96
96
|
display: flex;
|
|
97
97
|
align-items: center;
|
|
98
|
-
margin-
|
|
99
|
-
padding-
|
|
100
|
-
margin-
|
|
98
|
+
margin-inline-start: auto;
|
|
99
|
+
padding-inline-start: var(--space-4);
|
|
100
|
+
margin-inline-end: calc(-2px * var(--scaling)); /* Pulls closer to edge */
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
.rt-BaseMenuSubTriggerIcon {
|
|
104
104
|
color: var(--gray-12);
|
|
105
|
-
margin-
|
|
105
|
+
margin-inline-end: calc(-2px * var(--scaling));
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
.rt-BaseMenuItemIndicator {
|
|
109
109
|
position: absolute;
|
|
110
|
-
|
|
110
|
+
inset-inline-start: 0;
|
|
111
111
|
width: var(--base-menu-item-padding-left);
|
|
112
112
|
display: inline-flex;
|
|
113
113
|
align-items: center;
|
|
@@ -118,8 +118,8 @@
|
|
|
118
118
|
height: 1px;
|
|
119
119
|
margin-top: var(--space-2);
|
|
120
120
|
margin-bottom: var(--space-2);
|
|
121
|
-
margin-
|
|
122
|
-
margin-
|
|
121
|
+
margin-inline-start: var(--base-menu-item-padding-left);
|
|
122
|
+
margin-inline-end: var(--base-menu-item-padding-right);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
.rt-BaseMenuLabel {
|
|
@@ -128,8 +128,8 @@
|
|
|
128
128
|
min-height: var(--base-menu-item-height);
|
|
129
129
|
padding-top: var(--base-menu-item-padding-y);
|
|
130
130
|
padding-bottom: var(--base-menu-item-padding-y);
|
|
131
|
-
padding-
|
|
132
|
-
padding-
|
|
131
|
+
padding-inline-start: var(--base-menu-item-padding-left);
|
|
132
|
+
padding-inline-end: var(--base-menu-item-padding-right);
|
|
133
133
|
box-sizing: border-box;
|
|
134
134
|
color: var(--gray-a10);
|
|
135
135
|
user-select: none;
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
--base-menu-content-padding: var(--space-2);
|
|
166
166
|
--base-menu-item-padding-left: calc(var(--space-5) / 1.2);
|
|
167
167
|
--base-menu-item-padding-right: var(--space-2);
|
|
168
|
-
--base-menu-item-padding-y: var(--space-
|
|
168
|
+
--base-menu-item-padding-y: var(--space-1);
|
|
169
169
|
--base-menu-item-height: var(--space-5);
|
|
170
170
|
border-radius: var(--radius-3);
|
|
171
171
|
|
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
font-size: var(--font-size-1);
|
|
189
189
|
line-height: var(--line-height-1);
|
|
190
190
|
letter-spacing: var(--letter-spacing-1);
|
|
191
|
-
padding-
|
|
191
|
+
padding-inline-start: calc(var(--base-menu-item-padding-left) + var(--component-gap-2));
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
& :where(.rt-BaseMenuItemIndicatorIcon, .rt-BaseMenuSubTriggerIcon) {
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
--base-menu-content-padding: var(--space-3);
|
|
210
210
|
--base-menu-item-padding-left: var(--space-3);
|
|
211
211
|
--base-menu-item-padding-right: var(--space-3);
|
|
212
|
-
--base-menu-item-padding-y: var(--space-
|
|
212
|
+
--base-menu-item-padding-y: var(--space-1);
|
|
213
213
|
--base-menu-item-height: var(--space-6);
|
|
214
214
|
border-radius: var(--radius-5);
|
|
215
215
|
|
|
@@ -254,7 +254,7 @@
|
|
|
254
254
|
--base-menu-item-padding-left: var(--space-3);
|
|
255
255
|
--base-menu-item-padding-right: var(--space-3);
|
|
256
256
|
--base-menu-item-padding-y: var(--space-2);
|
|
257
|
-
--base-menu-item-height: var(--space-
|
|
257
|
+
--base-menu-item-height: var(--space-7);
|
|
258
258
|
border-radius: var(--radius-6);
|
|
259
259
|
|
|
260
260
|
& :where(.rt-BaseMenuItem) {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
min-height: 0;
|
|
17
17
|
|
|
18
18
|
:where(.rt-SidebarContent:has(.rt-ScrollAreaScrollbar[data-orientation='vertical'])) & {
|
|
19
|
-
padding-
|
|
19
|
+
padding-inline-end: var(--space-3);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
min-height: var(--base-menu-item-height);
|
|
34
34
|
padding-top: var(--base-menu-item-padding-y);
|
|
35
35
|
padding-bottom: var(--base-menu-item-padding-y);
|
|
36
|
-
padding-
|
|
37
|
-
padding-
|
|
36
|
+
padding-inline-start: var(--base-menu-item-padding-left);
|
|
37
|
+
padding-inline-end: var(--base-menu-item-padding-right);
|
|
38
38
|
box-sizing: border-box;
|
|
39
39
|
position: relative;
|
|
40
40
|
outline: none;
|
|
@@ -42,11 +42,14 @@
|
|
|
42
42
|
background: none;
|
|
43
43
|
border: none;
|
|
44
44
|
width: 100%;
|
|
45
|
-
text-align:
|
|
45
|
+
text-align: start;
|
|
46
46
|
/* No default border radius - inherited from size-specific rules */
|
|
47
47
|
|
|
48
|
-
/*
|
|
49
|
-
transition:
|
|
48
|
+
/* Step 3: restrict to paint-only transitions to avoid Chrome compositing nudge */
|
|
49
|
+
transition:
|
|
50
|
+
background-color var(--motion-duration-micro) var(--motion-ease-standard),
|
|
51
|
+
color var(--motion-duration-small) var(--motion-ease-standard),
|
|
52
|
+
font-weight var(--motion-duration-small) var(--motion-ease-standard);
|
|
50
53
|
|
|
51
54
|
/* Fix sticky text highlighting after selection in Firefox */
|
|
52
55
|
user-select: none;
|
|
@@ -146,14 +149,14 @@
|
|
|
146
149
|
}
|
|
147
150
|
|
|
148
151
|
.rt-SidebarMenuSubList {
|
|
149
|
-
padding-
|
|
150
|
-
border-
|
|
151
|
-
margin-
|
|
152
|
+
padding-inline-start: var(--space-4);
|
|
153
|
+
border-inline-start: 1px solid var(--gray-a5);
|
|
154
|
+
margin-inline-start: var(--space-3);
|
|
152
155
|
}
|
|
153
156
|
|
|
154
157
|
/* Sub-menu items have consistent heights based on size - match dropdown menu exactly */
|
|
155
158
|
:where(.rt-SidebarContent.rt-r-size-1) :where(.rt-SidebarMenuSubList) .rt-SidebarMenuButton {
|
|
156
|
-
padding-
|
|
159
|
+
padding-inline-start: var(--space-3);
|
|
157
160
|
padding-top: calc(var(--space-1) * 0.75);
|
|
158
161
|
padding-bottom: calc(var(--space-1) * 0.75);
|
|
159
162
|
min-height: var(--space-5); /* 20px */
|
|
@@ -161,10 +164,10 @@
|
|
|
161
164
|
}
|
|
162
165
|
|
|
163
166
|
:where(.rt-SidebarContent.rt-r-size-2) :where(.rt-SidebarMenuSubList) .rt-SidebarMenuButton {
|
|
164
|
-
padding-
|
|
167
|
+
padding-inline-start: var(--space-3);
|
|
165
168
|
padding-top: var(--space-1);
|
|
166
169
|
padding-bottom: var(--space-1);
|
|
167
|
-
min-height: var(--space-6); /*
|
|
170
|
+
min-height: var(--space-6); /* 32px */
|
|
168
171
|
font-size: var(--font-size-2);
|
|
169
172
|
}
|
|
170
173
|
|
|
@@ -175,8 +178,8 @@
|
|
|
175
178
|
min-height: var(--base-menu-item-height);
|
|
176
179
|
padding-top: var(--base-menu-item-padding-y);
|
|
177
180
|
padding-bottom: var(--base-menu-item-padding-y);
|
|
178
|
-
padding-
|
|
179
|
-
padding-
|
|
181
|
+
padding-inline-start: var(--base-menu-item-padding-left);
|
|
182
|
+
padding-inline-end: var(--base-menu-item-padding-right);
|
|
180
183
|
box-sizing: border-box;
|
|
181
184
|
color: var(--gray-a10);
|
|
182
185
|
user-select: none;
|
|
@@ -192,27 +195,27 @@
|
|
|
192
195
|
.rt-SidebarMenuButton:where(:has(.rt-SidebarMenuShortcut, .rt-SidebarMenuBadge)) {
|
|
193
196
|
/* Use base menu padding tokens */
|
|
194
197
|
:where(.rt-SidebarContent.rt-r-size-1) & {
|
|
195
|
-
padding-
|
|
198
|
+
padding-inline-end: var(--base-menu-item-padding-y); /* Matches top/bottom padding */
|
|
196
199
|
}
|
|
197
200
|
|
|
198
201
|
:where(.rt-SidebarContent.rt-r-size-2) & {
|
|
199
|
-
padding-
|
|
202
|
+
padding-inline-end: var(--base-menu-item-padding-y); /* Matches top/bottom padding */
|
|
200
203
|
}
|
|
201
204
|
}
|
|
202
205
|
|
|
203
206
|
.rt-SidebarMenuShortcut {
|
|
204
207
|
display: flex;
|
|
205
208
|
align-items: center;
|
|
206
|
-
margin-
|
|
207
|
-
padding-
|
|
209
|
+
margin-inline-start: auto;
|
|
210
|
+
padding-inline-start: var(--space-4);
|
|
208
211
|
color: var(--gray-a11);
|
|
209
212
|
}
|
|
210
213
|
|
|
211
214
|
.rt-SidebarMenuBadge {
|
|
212
215
|
display: flex;
|
|
213
216
|
align-items: center;
|
|
214
|
-
margin-
|
|
215
|
-
padding-
|
|
217
|
+
margin-inline-start: auto;
|
|
218
|
+
padding-inline-start: var(--space-2);
|
|
216
219
|
}
|
|
217
220
|
|
|
218
221
|
/* Add balanced spacing for sidebar menu items while preserving base menu border radius */
|
|
@@ -78,6 +78,10 @@
|
|
|
78
78
|
box-shadow: none !important;
|
|
79
79
|
border-radius: 0 !important;
|
|
80
80
|
|
|
81
|
+
/* Step 2: stabilize text rasterization to avoid Chrome AA flicker */
|
|
82
|
+
-webkit-font-smoothing: antialiased;
|
|
83
|
+
text-rendering: optimizeLegibility;
|
|
84
|
+
|
|
81
85
|
/* Add gap between groups using flex */
|
|
82
86
|
/* stylelint-disable-next-line selector-max-specificity */
|
|
83
87
|
& .rt-BaseMenuViewport {
|
|
@@ -99,6 +103,15 @@
|
|
|
99
103
|
flex-direction: column;
|
|
100
104
|
min-height: 0;
|
|
101
105
|
}
|
|
106
|
+
|
|
107
|
+
/* Step 1: promote menu viewport to its own layer (Chrome jitter test) */
|
|
108
|
+
& :where(.rt-BaseMenuViewport) {
|
|
109
|
+
transform: translateZ(0);
|
|
110
|
+
backface-visibility: hidden;
|
|
111
|
+
will-change: transform;
|
|
112
|
+
/* Step 4: isolate painting to prevent cross-layer nudges */
|
|
113
|
+
contain: paint;
|
|
114
|
+
}
|
|
102
115
|
}
|
|
103
116
|
|
|
104
117
|
/* Sidebar Footer */
|
|
@@ -3,7 +3,7 @@ import classNames from 'classnames';
|
|
|
3
3
|
import * as Sheet from '../sheet.js';
|
|
4
4
|
import { VisuallyHidden } from '../visually-hidden.js';
|
|
5
5
|
import { useShell } from '../shell.context.js';
|
|
6
|
-
import { useResponsivePresentation } from '../shell.hooks.js';
|
|
6
|
+
import { useResponsivePresentation, useResponsiveValue } from '../shell.hooks.js';
|
|
7
7
|
import { PaneResizeContext } from './shell-resize.js';
|
|
8
8
|
import { BottomHandle, PaneHandle } from './shell-handles.js';
|
|
9
9
|
import { BREAKPOINTS } from '../shell.types.js';
|
|
@@ -11,9 +11,7 @@ import type { Breakpoint, PaneMode, PaneSizePersistence, ResponsivePresentation
|
|
|
11
11
|
|
|
12
12
|
interface PaneProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
13
13
|
presentation?: ResponsivePresentation;
|
|
14
|
-
mode
|
|
15
|
-
defaultMode?: any;
|
|
16
|
-
onModeChange?: (mode: PaneMode) => void;
|
|
14
|
+
// legacy mode removed
|
|
17
15
|
expandedSize?: number;
|
|
18
16
|
minSize?: number;
|
|
19
17
|
maxSize?: number;
|
|
@@ -32,16 +30,32 @@ interface PaneProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
|
32
30
|
persistence?: PaneSizePersistence;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
type
|
|
33
|
+
type BottomOpenChangeMeta = { reason: 'init' | 'toggle' | 'responsive' };
|
|
34
|
+
type BottomControlledProps = { open: boolean | Partial<Record<Breakpoint, boolean>>; onOpenChange?: (open: boolean, meta: BottomOpenChangeMeta) => void; defaultOpen?: never };
|
|
35
|
+
type BottomUncontrolledProps = { defaultOpen?: boolean; onOpenChange?: (open: boolean, meta: BottomOpenChangeMeta) => void; open?: never };
|
|
36
|
+
type BottomSizeControlledProps = { size: number | string; defaultSize?: never };
|
|
37
|
+
type BottomSizeUncontrolledProps = { defaultSize?: number | string; size?: never };
|
|
38
|
+
type BottomSizeChangeMeta = { reason: 'init' | 'resize' | 'controlled' };
|
|
39
|
+
type BottomPublicProps = PaneProps &
|
|
40
|
+
(BottomControlledProps | BottomUncontrolledProps) &
|
|
41
|
+
(BottomSizeControlledProps | BottomSizeUncontrolledProps) & {
|
|
42
|
+
onSizeChange?: (size: number, meta: BottomSizeChangeMeta) => void;
|
|
43
|
+
sizeUpdate?: 'throttle' | 'debounce';
|
|
44
|
+
sizeUpdateMs?: number;
|
|
45
|
+
};
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
type BottomComponent = React.ForwardRefExoticComponent<BottomPublicProps & React.RefAttributes<HTMLDivElement>> & { Handle: typeof BottomHandle };
|
|
48
|
+
|
|
49
|
+
export const Bottom = React.forwardRef<HTMLDivElement, BottomPublicProps>(
|
|
38
50
|
(
|
|
39
51
|
{
|
|
40
52
|
className,
|
|
41
53
|
presentation = 'fixed',
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
// removed legacy props
|
|
55
|
+
// new API
|
|
56
|
+
defaultOpen,
|
|
57
|
+
open,
|
|
58
|
+
onOpenChange,
|
|
45
59
|
expandedSize = 200,
|
|
46
60
|
minSize = 100,
|
|
47
61
|
maxSize = 400,
|
|
@@ -80,58 +94,107 @@ export const Bottom = React.forwardRef<HTMLDivElement, PaneProps>(
|
|
|
80
94
|
const handleChildren = childArray.filter((el: React.ReactElement) => React.isValidElement(el) && el.type === BottomHandle);
|
|
81
95
|
const contentChildren = childArray.filter((el: React.ReactElement) => !(React.isValidElement(el) && el.type === BottomHandle));
|
|
82
96
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
// Throttled/debounced emitter for onSizeChange
|
|
98
|
+
const emitSizeChange = React.useMemo(() => {
|
|
99
|
+
const cb = (props as any).onSizeChange as undefined | ((s: number, meta: BottomSizeChangeMeta) => void);
|
|
100
|
+
const strategy = (props as any).sizeUpdate as undefined | 'throttle' | 'debounce';
|
|
101
|
+
const ms = (props as any).sizeUpdateMs ?? 50;
|
|
102
|
+
if (!cb) return () => {};
|
|
103
|
+
if (strategy === 'debounce') {
|
|
104
|
+
let t: any = null;
|
|
105
|
+
return (s: number, meta: BottomSizeChangeMeta) => {
|
|
106
|
+
if (t) clearTimeout(t);
|
|
107
|
+
t = setTimeout(() => {
|
|
108
|
+
cb(s, meta);
|
|
109
|
+
}, ms);
|
|
110
|
+
};
|
|
88
111
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
112
|
+
if (strategy === 'throttle') {
|
|
113
|
+
let last = 0;
|
|
114
|
+
return (s: number, meta: BottomSizeChangeMeta) => {
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
if (now - last >= ms) {
|
|
117
|
+
last = now;
|
|
118
|
+
cb(s, meta);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
97
121
|
}
|
|
98
|
-
return
|
|
99
|
-
}, [
|
|
122
|
+
return (s: number, meta: BottomSizeChangeMeta) => cb(s, meta);
|
|
123
|
+
}, [(props as any).onSizeChange, (props as any).sizeUpdate, (props as any).sizeUpdateMs]);
|
|
100
124
|
|
|
101
125
|
const didInitRef = React.useRef(false);
|
|
126
|
+
const didInitFromDefaultOpenRef = React.useRef(false);
|
|
127
|
+
const resolvedDefaultOpen = useResponsiveValue(defaultOpen as any);
|
|
102
128
|
React.useEffect(() => {
|
|
103
129
|
if (didInitRef.current) return;
|
|
130
|
+
if (!shell.currentBreakpointReady) return;
|
|
104
131
|
didInitRef.current = true;
|
|
105
|
-
if (
|
|
106
|
-
const initial =
|
|
107
|
-
|
|
132
|
+
if (typeof open === 'undefined' && typeof defaultOpen !== 'undefined') {
|
|
133
|
+
const initial = Boolean(resolvedDefaultOpen);
|
|
134
|
+
shell.setBottomMode(initial ? 'expanded' : 'collapsed');
|
|
135
|
+
didInitFromDefaultOpenRef.current = true;
|
|
108
136
|
}
|
|
109
|
-
}, []);
|
|
137
|
+
}, [shell.currentBreakpointReady, open, defaultOpen, resolvedDefaultOpen]);
|
|
110
138
|
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
139
|
+
// Dev guards
|
|
140
|
+
const wasControlledRef = React.useRef<boolean | null>(null);
|
|
141
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
142
|
+
if (typeof open !== 'undefined' && typeof defaultOpen !== 'undefined') {
|
|
143
|
+
// eslint-disable-next-line no-console
|
|
144
|
+
console.error('Shell.Bottom: Do not pass both `open` and `defaultOpen`. Choose one.');
|
|
145
|
+
}
|
|
146
|
+
if (typeof (props as any).size !== 'undefined' && typeof (props as any).defaultSize !== 'undefined') {
|
|
147
|
+
// eslint-disable-next-line no-console
|
|
148
|
+
console.error('Shell.Bottom: Do not pass both `size` and `defaultSize`. Choose one.');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
123
151
|
|
|
124
152
|
React.useEffect(() => {
|
|
125
|
-
|
|
126
|
-
|
|
153
|
+
const isControlled = typeof open !== 'undefined';
|
|
154
|
+
if (wasControlledRef.current === null) {
|
|
155
|
+
wasControlledRef.current = isControlled;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (wasControlledRef.current !== isControlled) {
|
|
159
|
+
// eslint-disable-next-line no-console
|
|
160
|
+
console.warn('Shell.Bottom: Switching between controlled and uncontrolled `open` is not supported.');
|
|
161
|
+
wasControlledRef.current = isControlled;
|
|
127
162
|
}
|
|
128
|
-
}, [
|
|
163
|
+
}, [open]);
|
|
164
|
+
|
|
165
|
+
// Controlled sync (responsive handled below)
|
|
166
|
+
React.useEffect(() => {
|
|
167
|
+
if (typeof open === 'undefined') return;
|
|
168
|
+
shell.setBottomMode(open ? 'expanded' : 'collapsed');
|
|
169
|
+
}, [open]);
|
|
170
|
+
|
|
171
|
+
const responsiveNotifiedRef = React.useRef(false);
|
|
172
|
+
|
|
173
|
+
// Controlled responsive open
|
|
174
|
+
const resolvedOpen = useResponsiveValue(open);
|
|
175
|
+
React.useEffect(() => {
|
|
176
|
+
if (typeof resolvedOpen === 'undefined') return;
|
|
177
|
+
const shouldExpand = Boolean(resolvedOpen);
|
|
178
|
+
shell.setBottomMode(shouldExpand ? 'expanded' : 'collapsed');
|
|
179
|
+
}, [resolvedOpen]);
|
|
129
180
|
|
|
181
|
+
const initNotifiedRef = React.useRef(false);
|
|
182
|
+
const lastBottomModeRef = React.useRef<PaneMode | null>(null);
|
|
130
183
|
React.useEffect(() => {
|
|
131
|
-
if (
|
|
132
|
-
|
|
184
|
+
if (!initNotifiedRef.current && typeof open === 'undefined' && defaultOpen && shell.bottomMode === 'expanded') {
|
|
185
|
+
onOpenChange?.(true, { reason: 'init' });
|
|
186
|
+
initNotifiedRef.current = true;
|
|
133
187
|
}
|
|
134
|
-
|
|
188
|
+
if (typeof open === 'undefined') {
|
|
189
|
+
if (lastBottomModeRef.current !== null && lastBottomModeRef.current !== shell.bottomMode) {
|
|
190
|
+
if (!responsiveNotifiedRef.current) {
|
|
191
|
+
onOpenChange?.(shell.bottomMode === 'expanded', { reason: 'toggle' });
|
|
192
|
+
}
|
|
193
|
+
responsiveNotifiedRef.current = false;
|
|
194
|
+
}
|
|
195
|
+
lastBottomModeRef.current = shell.bottomMode;
|
|
196
|
+
}
|
|
197
|
+
}, [shell.bottomMode, open, defaultOpen, onOpenChange]);
|
|
135
198
|
|
|
136
199
|
React.useEffect(() => {
|
|
137
200
|
if (shell.bottomMode === 'expanded') {
|
|
@@ -194,6 +257,7 @@ export const Bottom = React.forwardRef<HTMLDivElement, PaneProps>(
|
|
|
194
257
|
onResizeStart,
|
|
195
258
|
onResizeEnd: (size) => {
|
|
196
259
|
onResizeEnd?.(size);
|
|
260
|
+
emitSizeChange(size, { reason: 'resize' });
|
|
197
261
|
persistenceAdapter?.save?.(size);
|
|
198
262
|
},
|
|
199
263
|
target: 'bottom',
|
|
@@ -209,6 +273,69 @@ export const Bottom = React.forwardRef<HTMLDivElement, PaneProps>(
|
|
|
209
273
|
</PaneResizeContext.Provider>
|
|
210
274
|
) : null;
|
|
211
275
|
|
|
276
|
+
// Strip control/size props from DOM spread (moved above overlay return to keep hook order consistent)
|
|
277
|
+
const {
|
|
278
|
+
defaultOpen: _bottomDefaultOpenIgnored,
|
|
279
|
+
open: _bottomOpenIgnored,
|
|
280
|
+
onOpenChange: _bottomOnOpenChangeIgnored,
|
|
281
|
+
size: _bottomSizeIgnored,
|
|
282
|
+
defaultSize: _bottomDefaultSizeIgnored,
|
|
283
|
+
onSizeChange: _bottomOnSizeChangeIgnored,
|
|
284
|
+
sizeUpdate: _szu,
|
|
285
|
+
sizeUpdateMs: _szums,
|
|
286
|
+
...bottomDomProps
|
|
287
|
+
} = props as any;
|
|
288
|
+
|
|
289
|
+
// Normalize CSS lengths to px (moved above overlay return to keep hook order consistent)
|
|
290
|
+
const normalizeToPx = React.useCallback((value: number | string | undefined): number | undefined => {
|
|
291
|
+
if (value == null) return undefined;
|
|
292
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
293
|
+
const str = String(value).trim();
|
|
294
|
+
if (!str) return undefined;
|
|
295
|
+
if (str.endsWith('px')) return Number.parseFloat(str);
|
|
296
|
+
if (str.endsWith('rem')) {
|
|
297
|
+
const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize || '16') || 16;
|
|
298
|
+
return Number.parseFloat(str) * rem;
|
|
299
|
+
}
|
|
300
|
+
if (str.endsWith('%')) {
|
|
301
|
+
const pct = Number.parseFloat(str);
|
|
302
|
+
const base = document.documentElement.clientHeight || window.innerHeight || 0;
|
|
303
|
+
return (pct / 100) * base;
|
|
304
|
+
}
|
|
305
|
+
const n = Number.parseFloat(str);
|
|
306
|
+
return Number.isFinite(n) ? n : undefined;
|
|
307
|
+
}, []);
|
|
308
|
+
|
|
309
|
+
// Apply defaultSize on mount when uncontrolled (moved above overlay return)
|
|
310
|
+
React.useEffect(() => {
|
|
311
|
+
if (!localRef.current) return;
|
|
312
|
+
if (typeof (props as any).size === 'undefined' && typeof (props as any).defaultSize !== 'undefined') {
|
|
313
|
+
const px = normalizeToPx((props as any).defaultSize);
|
|
314
|
+
if (typeof px === 'number' && Number.isFinite(px)) {
|
|
315
|
+
const minPx = typeof minSize === 'number' ? minSize : undefined;
|
|
316
|
+
const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
|
|
317
|
+
const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
|
|
318
|
+
localRef.current.style.setProperty('--bottom-size', `${clamped}px`);
|
|
319
|
+
emitSizeChange(clamped, { reason: 'init' });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
323
|
+
}, []);
|
|
324
|
+
|
|
325
|
+
// Controlled size sync (moved above overlay return)
|
|
326
|
+
React.useEffect(() => {
|
|
327
|
+
if (!localRef.current) return;
|
|
328
|
+
if (typeof (props as any).size === 'undefined') return;
|
|
329
|
+
const px = normalizeToPx((props as any).size);
|
|
330
|
+
if (typeof px === 'number' && Number.isFinite(px)) {
|
|
331
|
+
const minPx = typeof minSize === 'number' ? minSize : undefined;
|
|
332
|
+
const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
|
|
333
|
+
const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
|
|
334
|
+
localRef.current.style.setProperty('--bottom-size', `${clamped}px`);
|
|
335
|
+
emitSizeChange(clamped, { reason: 'controlled' });
|
|
336
|
+
}
|
|
337
|
+
}, [(props as any).size, minSize, maxSize, normalizeToPx, emitSizeChange]);
|
|
338
|
+
|
|
212
339
|
if (isOverlay) {
|
|
213
340
|
const open = shell.bottomMode === 'expanded';
|
|
214
341
|
return (
|
|
@@ -225,13 +352,13 @@ export const Bottom = React.forwardRef<HTMLDivElement, PaneProps>(
|
|
|
225
352
|
|
|
226
353
|
return (
|
|
227
354
|
<div
|
|
228
|
-
{...
|
|
355
|
+
{...bottomDomProps}
|
|
229
356
|
ref={setRef}
|
|
230
357
|
className={classNames('rt-ShellBottom', className)}
|
|
231
358
|
data-mode={shell.bottomMode}
|
|
232
359
|
data-peek={shell.peekTarget === 'bottom' || undefined}
|
|
233
|
-
data-presentation={resolvedPresentation}
|
|
234
|
-
data-open={(isStacked && isExpanded) || undefined}
|
|
360
|
+
data-presentation={shell.currentBreakpointReady ? resolvedPresentation : undefined}
|
|
361
|
+
data-open={(shell.currentBreakpointReady && isStacked && isExpanded) || undefined}
|
|
235
362
|
style={{
|
|
236
363
|
...style,
|
|
237
364
|
['--bottom-size' as any]: `${expandedSize}px`,
|