@kushagradhawan/kookie-ui 0.1.71 → 0.1.73
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/README.md +4 -0
- package/components.css +69 -382
- package/dist/cjs/components/_internal/base-button.d.ts.map +1 -1
- package/dist/cjs/components/_internal/base-button.js +1 -1
- package/dist/cjs/components/_internal/base-button.js.map +3 -3
- 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-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.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/button.d.ts.map +1 -1
- package/dist/cjs/components/button.js +1 -1
- package/dist/cjs/components/button.js.map +3 -3
- package/dist/cjs/components/chatbar.d.ts.map +1 -1
- package/dist/cjs/components/chatbar.js.map +2 -2
- package/dist/cjs/components/icon-button.d.ts.map +1 -1
- package/dist/cjs/components/icon-button.js +2 -2
- package/dist/cjs/components/icon-button.js.map +3 -3
- package/dist/cjs/components/shell.d.ts.map +1 -1
- package/dist/cjs/components/shell.js +1 -1
- package/dist/cjs/components/shell.js.map +3 -3
- package/dist/cjs/components/toggle-button.d.ts.map +1 -1
- package/dist/cjs/components/toggle-button.js +1 -1
- package/dist/cjs/components/toggle-button.js.map +3 -3
- package/dist/cjs/components/toggle-icon-button.d.ts.map +1 -1
- package/dist/cjs/components/toggle-icon-button.js +1 -1
- package/dist/cjs/components/toggle-icon-button.js.map +3 -3
- package/dist/cjs/hooks/index.d.ts +2 -0
- package/dist/cjs/hooks/index.d.ts.map +1 -1
- package/dist/cjs/hooks/index.js +1 -1
- package/dist/cjs/hooks/index.js.map +3 -3
- package/dist/cjs/hooks/use-live-announcer.d.ts.map +1 -1
- package/dist/cjs/hooks/use-live-announcer.js +2 -2
- package/dist/cjs/hooks/use-live-announcer.js.map +3 -3
- package/dist/cjs/hooks/use-toggle-state.d.ts +37 -0
- package/dist/cjs/hooks/use-toggle-state.d.ts.map +1 -0
- package/dist/cjs/hooks/use-toggle-state.js +2 -0
- package/dist/cjs/hooks/use-toggle-state.js.map +7 -0
- package/dist/cjs/hooks/use-tooltip-wrapper.d.ts +29 -0
- package/dist/cjs/hooks/use-tooltip-wrapper.d.ts.map +1 -0
- package/dist/cjs/hooks/use-tooltip-wrapper.js +2 -0
- package/dist/cjs/hooks/use-tooltip-wrapper.js.map +7 -0
- package/dist/esm/components/_internal/base-button.d.ts.map +1 -1
- package/dist/esm/components/_internal/base-button.js +1 -1
- package/dist/esm/components/_internal/base-button.js.map +3 -3
- 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-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.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/button.d.ts.map +1 -1
- package/dist/esm/components/button.js +1 -1
- package/dist/esm/components/button.js.map +3 -3
- package/dist/esm/components/chatbar.d.ts.map +1 -1
- package/dist/esm/components/chatbar.js.map +2 -2
- package/dist/esm/components/icon-button.d.ts.map +1 -1
- package/dist/esm/components/icon-button.js +2 -2
- package/dist/esm/components/icon-button.js.map +3 -3
- package/dist/esm/components/shell.d.ts.map +1 -1
- package/dist/esm/components/shell.js +1 -1
- package/dist/esm/components/shell.js.map +3 -3
- package/dist/esm/components/toggle-button.d.ts.map +1 -1
- package/dist/esm/components/toggle-button.js +1 -1
- package/dist/esm/components/toggle-button.js.map +3 -3
- package/dist/esm/components/toggle-icon-button.d.ts.map +1 -1
- package/dist/esm/components/toggle-icon-button.js +1 -1
- package/dist/esm/components/toggle-icon-button.js.map +3 -3
- package/dist/esm/hooks/index.d.ts +2 -0
- package/dist/esm/hooks/index.d.ts.map +1 -1
- package/dist/esm/hooks/index.js +1 -1
- package/dist/esm/hooks/index.js.map +3 -3
- package/dist/esm/hooks/use-live-announcer.d.ts.map +1 -1
- package/dist/esm/hooks/use-live-announcer.js +2 -2
- package/dist/esm/hooks/use-live-announcer.js.map +3 -3
- package/dist/esm/hooks/use-toggle-state.d.ts +37 -0
- package/dist/esm/hooks/use-toggle-state.d.ts.map +1 -0
- package/dist/esm/hooks/use-toggle-state.js +2 -0
- package/dist/esm/hooks/use-toggle-state.js.map +7 -0
- package/dist/esm/hooks/use-tooltip-wrapper.d.ts +29 -0
- package/dist/esm/hooks/use-tooltip-wrapper.d.ts.map +1 -0
- package/dist/esm/hooks/use-tooltip-wrapper.js +2 -0
- package/dist/esm/hooks/use-tooltip-wrapper.js.map +7 -0
- package/package.json +4 -4
- 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-button.css +136 -614
- package/src/components/_internal/base-button.tsx +15 -13
- package/src/components/_internal/shell-bottom.tsx +31 -5
- package/src/components/_internal/shell-inspector.tsx +31 -5
- package/src/components/_internal/shell-sidebar.tsx +34 -6
- package/src/components/button.tsx +13 -42
- package/src/components/chatbar.tsx +1 -13
- package/src/components/icon-button.tsx +20 -44
- package/src/components/image.css +10 -8
- package/src/components/shell.css +10 -11
- package/src/components/shell.tsx +59 -11
- package/src/components/toggle-button.tsx +30 -59
- package/src/components/toggle-icon-button.tsx +29 -51
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-live-announcer.ts +34 -7
- package/src/hooks/use-toggle-state.ts +72 -0
- package/src/hooks/use-tooltip-wrapper.ts +28 -0
- package/src/styles/tokens/color.css +11 -1
- package/styles.css +76 -383
- package/tokens/base.css +7 -1
- package/tokens.css +7 -1
|
@@ -83,9 +83,7 @@ const BaseButton = React.forwardRef<BaseButtonElement, BaseButtonProps>((props,
|
|
|
83
83
|
// This helps developers migrate to the new material prop
|
|
84
84
|
React.useEffect(() => {
|
|
85
85
|
if (props.panelBackground !== undefined) {
|
|
86
|
-
console.warn(
|
|
87
|
-
'Warning: The `panelBackground` prop is deprecated and will be removed in a future version. Use `material` prop instead.',
|
|
88
|
-
);
|
|
86
|
+
console.warn('Warning: The `panelBackground` prop is deprecated and will be removed in a future version. Use `material` prop instead.');
|
|
89
87
|
}
|
|
90
88
|
}, [props.panelBackground]);
|
|
91
89
|
|
|
@@ -96,6 +94,10 @@ const BaseButton = React.forwardRef<BaseButtonElement, BaseButtonProps>((props,
|
|
|
96
94
|
// This prevents layout thrashing when using translucent materials
|
|
97
95
|
const buttonRef = React.useRef<HTMLElement>(null);
|
|
98
96
|
|
|
97
|
+
// Use a ref to track current material value to avoid stale closures in setTimeout
|
|
98
|
+
const materialRef = React.useRef(effectiveMaterial);
|
|
99
|
+
materialRef.current = effectiveMaterial;
|
|
100
|
+
|
|
99
101
|
React.useEffect(() => {
|
|
100
102
|
const button = buttonRef.current;
|
|
101
103
|
if (!button) return;
|
|
@@ -106,14 +108,17 @@ const BaseButton = React.forwardRef<BaseButtonElement, BaseButtonProps>((props,
|
|
|
106
108
|
// Add will-change when material is translucent to optimize rendering
|
|
107
109
|
button.style.setProperty('will-change', 'backdrop-filter');
|
|
108
110
|
|
|
111
|
+
// Track timeout for cleanup
|
|
112
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
113
|
+
|
|
109
114
|
// Clean up will-change after transition completes to prevent memory leaks
|
|
110
115
|
const cleanup = () => {
|
|
111
|
-
const transitionDuration =
|
|
112
|
-
getComputedStyle(button).getPropertyValue('--duration-2') || '75ms';
|
|
116
|
+
const transitionDuration = getComputedStyle(button).getPropertyValue('--duration-2') || '75ms';
|
|
113
117
|
const duration = parseInt(transitionDuration) || 75;
|
|
114
118
|
|
|
115
|
-
setTimeout(() => {
|
|
116
|
-
|
|
119
|
+
timeoutId = setTimeout(() => {
|
|
120
|
+
// Use ref to get current value, not stale closure value
|
|
121
|
+
if (button && materialRef.current !== 'translucent') {
|
|
117
122
|
button.style.setProperty('will-change', 'auto');
|
|
118
123
|
}
|
|
119
124
|
}, duration);
|
|
@@ -124,6 +129,7 @@ const BaseButton = React.forwardRef<BaseButtonElement, BaseButtonProps>((props,
|
|
|
124
129
|
observer.observe(button, { attributes: true, attributeFilter: ['data-material'] });
|
|
125
130
|
|
|
126
131
|
return () => {
|
|
132
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
127
133
|
observer.disconnect();
|
|
128
134
|
button.style.setProperty('will-change', 'auto');
|
|
129
135
|
};
|
|
@@ -138,8 +144,7 @@ const BaseButton = React.forwardRef<BaseButtonElement, BaseButtonProps>((props,
|
|
|
138
144
|
|
|
139
145
|
// Only pass disabled for elements that support it
|
|
140
146
|
// This prevents invalid HTML attributes on unsupported elements
|
|
141
|
-
const shouldPassDisabled =
|
|
142
|
-
asChild || !as || ['button', 'input', 'textarea', 'select'].includes(as);
|
|
147
|
+
const shouldPassDisabled = asChild || !as || ['button', 'input', 'textarea', 'select'].includes(as);
|
|
143
148
|
|
|
144
149
|
// Determine if we are rendering a real <button> element so we can set a safe
|
|
145
150
|
// default type. Native <button> defaults to type="submit" which can cause
|
|
@@ -221,10 +226,7 @@ const BaseButton = React.forwardRef<BaseButtonElement, BaseButtonProps>((props,
|
|
|
221
226
|
{/* Centered spinner overlay during loading state */}
|
|
222
227
|
<Flex asChild align="center" justify="center" position="absolute" inset="0">
|
|
223
228
|
<span>
|
|
224
|
-
<Spinner
|
|
225
|
-
size={mapResponsiveProp(size, mapButtonSizeToSpinnerSize)}
|
|
226
|
-
aria-hidden="true"
|
|
227
|
-
/>
|
|
229
|
+
<Spinner size={mapResponsiveProp(size, mapButtonSizeToSpinnerSize)} aria-hidden="true" />
|
|
228
230
|
</span>
|
|
229
231
|
</Flex>
|
|
230
232
|
</>
|
|
@@ -172,13 +172,39 @@ export const Bottom = React.forwardRef<HTMLDivElement, BottomPublicProps>((initi
|
|
|
172
172
|
}
|
|
173
173
|
}, [shell.bottomMode, open, defaultOpen, onOpenChange]);
|
|
174
174
|
|
|
175
|
+
// Track previous mode to only fire callbacks on actual user-initiated state transitions.
|
|
176
|
+
// We wait for breakpointReady to ensure the initial state sync from useResponsiveInitialState
|
|
177
|
+
// is complete before enabling callbacks. This avoids spurious callbacks during initialization.
|
|
178
|
+
const prevBottomModeRef = React.useRef<PaneMode | null>(null);
|
|
179
|
+
const hasInitializedRef = React.useRef(false);
|
|
175
180
|
React.useEffect(() => {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
181
|
+
const currentMode = shell.bottomMode;
|
|
182
|
+
|
|
183
|
+
// Wait for breakpoint to be ready before enabling callbacks
|
|
184
|
+
if (!shell.currentBreakpointReady) {
|
|
185
|
+
prevBottomModeRef.current = currentMode;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Skip the first run after breakpoint is ready - this captures the post-sync state
|
|
190
|
+
if (!hasInitializedRef.current) {
|
|
191
|
+
hasInitializedRef.current = true;
|
|
192
|
+
prevBottomModeRef.current = currentMode;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const prevMode = prevBottomModeRef.current;
|
|
197
|
+
|
|
198
|
+
// Only fire on actual state transitions
|
|
199
|
+
if (prevMode !== null && prevMode !== currentMode) {
|
|
200
|
+
if (currentMode === 'expanded') {
|
|
201
|
+
onExpand?.();
|
|
202
|
+
} else if (currentMode === 'collapsed') {
|
|
203
|
+
onCollapse?.();
|
|
204
|
+
}
|
|
205
|
+
prevBottomModeRef.current = currentMode;
|
|
180
206
|
}
|
|
181
|
-
}, [shell.bottomMode, onExpand, onCollapse]);
|
|
207
|
+
}, [shell.bottomMode, shell.currentBreakpointReady, onExpand, onCollapse]);
|
|
182
208
|
|
|
183
209
|
const isExpanded = shell.bottomMode === 'expanded';
|
|
184
210
|
|
|
@@ -173,13 +173,39 @@ export const Inspector = React.forwardRef<HTMLDivElement, InspectorPublicProps>(
|
|
|
173
173
|
}
|
|
174
174
|
}, [shell.inspectorMode, open, defaultOpen, onOpenChange]);
|
|
175
175
|
|
|
176
|
+
// Track previous mode to only fire callbacks on actual user-initiated state transitions.
|
|
177
|
+
// We wait for breakpointReady to ensure the initial state sync from useResponsiveInitialState
|
|
178
|
+
// is complete before enabling callbacks. This avoids spurious callbacks during initialization.
|
|
179
|
+
const prevInspectorModeRef = React.useRef<PaneMode | null>(null);
|
|
180
|
+
const hasInitializedRef = React.useRef(false);
|
|
176
181
|
React.useEffect(() => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
182
|
+
const currentMode = shell.inspectorMode;
|
|
183
|
+
|
|
184
|
+
// Wait for breakpoint to be ready before enabling callbacks
|
|
185
|
+
if (!shell.currentBreakpointReady) {
|
|
186
|
+
prevInspectorModeRef.current = currentMode;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Skip the first run after breakpoint is ready - this captures the post-sync state
|
|
191
|
+
if (!hasInitializedRef.current) {
|
|
192
|
+
hasInitializedRef.current = true;
|
|
193
|
+
prevInspectorModeRef.current = currentMode;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const prevMode = prevInspectorModeRef.current;
|
|
198
|
+
|
|
199
|
+
// Only fire on actual state transitions
|
|
200
|
+
if (prevMode !== null && prevMode !== currentMode) {
|
|
201
|
+
if (currentMode === 'expanded') {
|
|
202
|
+
onExpand?.();
|
|
203
|
+
} else if (currentMode === 'collapsed') {
|
|
204
|
+
onCollapse?.();
|
|
205
|
+
}
|
|
206
|
+
prevInspectorModeRef.current = currentMode;
|
|
181
207
|
}
|
|
182
|
-
}, [shell.inspectorMode, onExpand, onCollapse]);
|
|
208
|
+
}, [shell.inspectorMode, shell.currentBreakpointReady, onExpand, onCollapse]);
|
|
183
209
|
|
|
184
210
|
const isExpanded = shell.inspectorMode === 'expanded';
|
|
185
211
|
|
|
@@ -188,14 +188,42 @@ export const Sidebar = React.forwardRef<HTMLDivElement, SidebarPublicProps>((ini
|
|
|
188
188
|
}
|
|
189
189
|
}, [shell.sidebarMode, state, onStateChange]);
|
|
190
190
|
|
|
191
|
-
//
|
|
191
|
+
// Track previous mode to only fire callbacks on actual user-initiated state transitions.
|
|
192
|
+
// We wait for breakpointReady to ensure the initial state sync from useResponsiveInitialState
|
|
193
|
+
// is complete before enabling callbacks. This avoids spurious callbacks during initialization.
|
|
194
|
+
const prevSidebarModeRef = React.useRef<SidebarMode | null>(null);
|
|
195
|
+
const hasInitializedRef = React.useRef(false);
|
|
192
196
|
React.useEffect(() => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
const currentMode = shell.sidebarMode as SidebarMode;
|
|
198
|
+
|
|
199
|
+
// Wait for breakpoint to be ready before enabling callbacks
|
|
200
|
+
if (!shell.currentBreakpointReady) {
|
|
201
|
+
prevSidebarModeRef.current = currentMode;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Skip the first run after breakpoint is ready - this captures the post-sync state
|
|
206
|
+
if (!hasInitializedRef.current) {
|
|
207
|
+
hasInitializedRef.current = true;
|
|
208
|
+
prevSidebarModeRef.current = currentMode;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const prevMode = prevSidebarModeRef.current;
|
|
213
|
+
|
|
214
|
+
// Only fire on actual state transitions
|
|
215
|
+
if (prevMode !== null && prevMode !== currentMode) {
|
|
216
|
+
// onExpand: when becoming visible (collapsed → thin/expanded)
|
|
217
|
+
if (prevMode === 'collapsed' && currentMode !== 'collapsed') {
|
|
218
|
+
onExpand?.();
|
|
219
|
+
}
|
|
220
|
+
// onCollapse: when becoming hidden (any → collapsed)
|
|
221
|
+
else if (currentMode === 'collapsed') {
|
|
222
|
+
onCollapse?.();
|
|
223
|
+
}
|
|
224
|
+
prevSidebarModeRef.current = currentMode;
|
|
197
225
|
}
|
|
198
|
-
}, [shell.sidebarMode, onExpand, onCollapse]);
|
|
226
|
+
}, [shell.sidebarMode, shell.currentBreakpointReady, onExpand, onCollapse]);
|
|
199
227
|
|
|
200
228
|
// Option A: thin is width-only; content remains visible whenever not collapsed
|
|
201
229
|
const isContentVisible = shell.sidebarMode !== 'collapsed';
|
|
@@ -3,6 +3,7 @@ import classNames from 'classnames';
|
|
|
3
3
|
|
|
4
4
|
import { BaseButton } from './_internal/base-button.js';
|
|
5
5
|
import { Tooltip } from './tooltip.js';
|
|
6
|
+
import { useTooltipWrapper } from '../hooks/use-tooltip-wrapper.js';
|
|
6
7
|
import type { BaseButtonProps } from './_internal/base-button.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -84,9 +85,7 @@ type ButtonProps<C extends React.ElementType = 'button'> = ButtonOwnProps & {
|
|
|
84
85
|
* Button component type that supports polymorphic rendering
|
|
85
86
|
* @template C - The element type to render as
|
|
86
87
|
*/
|
|
87
|
-
type ButtonComponent = <C extends React.ElementType = 'button'>(
|
|
88
|
-
props: ButtonProps<C> & { ref?: React.ForwardedRef<ButtonElement> },
|
|
89
|
-
) => React.ReactElement | null;
|
|
88
|
+
type ButtonComponent = <C extends React.ElementType = 'button'>(props: ButtonProps<C> & { ref?: React.ForwardedRef<ButtonElement> }) => React.ReactElement | null;
|
|
90
89
|
|
|
91
90
|
/**
|
|
92
91
|
* Button component for triggering actions throughout your interface
|
|
@@ -113,27 +112,11 @@ type ButtonComponent = <C extends React.ElementType = 'button'>(
|
|
|
113
112
|
*/
|
|
114
113
|
const Button = React.forwardRef(
|
|
115
114
|
(
|
|
116
|
-
{
|
|
117
|
-
className,
|
|
118
|
-
tooltip,
|
|
119
|
-
tooltipSide = 'top',
|
|
120
|
-
tooltipAlign = 'center',
|
|
121
|
-
tooltipDelayDuration,
|
|
122
|
-
tooltipDisableHoverableContent,
|
|
123
|
-
overrideStyles,
|
|
124
|
-
...props
|
|
125
|
-
}: ButtonProps,
|
|
115
|
+
{ className, style, tooltip, tooltipSide = 'top', tooltipAlign = 'center', tooltipDelayDuration, tooltipDisableHoverableContent, overrideStyles, ...props }: ButtonProps,
|
|
126
116
|
forwardedRef: React.ForwardedRef<ButtonElement>,
|
|
127
117
|
) => {
|
|
128
|
-
//
|
|
129
|
-
const tooltipId =
|
|
130
|
-
const hasTooltip = Boolean(tooltip);
|
|
131
|
-
|
|
132
|
-
// Prepare accessibility props for tooltip integration
|
|
133
|
-
const tooltipAccessibilityProps = React.useMemo(
|
|
134
|
-
() => (hasTooltip ? { 'aria-describedby': tooltipId } : {}),
|
|
135
|
-
[hasTooltip, tooltipId],
|
|
136
|
-
);
|
|
118
|
+
// Use shared tooltip wrapper hook for accessibility props
|
|
119
|
+
const { tooltipId, hasTooltip, accessibilityProps: tooltipAccessibilityProps } = useTooltipWrapper(tooltip);
|
|
137
120
|
|
|
138
121
|
// Create the base button element with tooltip accessibility props
|
|
139
122
|
// Map overrideStyles to CSS variables consumed by the override variant rules
|
|
@@ -152,7 +135,7 @@ const Button = React.forwardRef(
|
|
|
152
135
|
setVar(`--button-override-${prefix}filter`, s.filter);
|
|
153
136
|
setVar(`--button-override-${prefix}outline`, s.outline);
|
|
154
137
|
setVar(`--button-override-${prefix}outline-offset`, s.outlineOffset);
|
|
155
|
-
setVar(`--button-override-${prefix}opacity`, s.opacity
|
|
138
|
+
setVar(`--button-override-${prefix}opacity`, s.opacity);
|
|
156
139
|
};
|
|
157
140
|
|
|
158
141
|
apply('', overrideStyles.normal);
|
|
@@ -167,35 +150,23 @@ const Button = React.forwardRef(
|
|
|
167
150
|
setVar('--button-override-focus-outline-offset', overrideStyles.focus.outlineOffset);
|
|
168
151
|
}
|
|
169
152
|
|
|
170
|
-
return vars as
|
|
153
|
+
return vars as React.CSSProperties;
|
|
171
154
|
}, [overrideStyles]);
|
|
172
155
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
ref={forwardedRef}
|
|
178
|
-
className={classNames('rt-Button', className)}
|
|
179
|
-
style={overrideVars ? { ...overrideVars, ...(props as any).style } : (props as any).style}
|
|
180
|
-
/>
|
|
181
|
-
);
|
|
156
|
+
// Combine override styles with user-provided styles
|
|
157
|
+
const combinedStyle = React.useMemo(() => (overrideVars ? { ...overrideVars, ...style } : style), [overrideVars, style]);
|
|
158
|
+
|
|
159
|
+
const button = <BaseButton {...props} {...tooltipAccessibilityProps} ref={forwardedRef} className={classNames('rt-Button', className)} style={combinedStyle} />;
|
|
182
160
|
|
|
183
161
|
// If no tooltip is provided, return the button as-is for better performance
|
|
184
|
-
if (!
|
|
162
|
+
if (!hasTooltip) {
|
|
185
163
|
return button;
|
|
186
164
|
}
|
|
187
165
|
|
|
188
166
|
// Wrap with Tooltip when tooltip content is provided
|
|
189
167
|
// This creates a compound component that handles both button and tooltip functionality
|
|
190
168
|
return (
|
|
191
|
-
<Tooltip
|
|
192
|
-
content={tooltip}
|
|
193
|
-
side={tooltipSide}
|
|
194
|
-
align={tooltipAlign}
|
|
195
|
-
delayDuration={tooltipDelayDuration}
|
|
196
|
-
disableHoverableContent={tooltipDisableHoverableContent}
|
|
197
|
-
id={tooltipId}
|
|
198
|
-
>
|
|
169
|
+
<Tooltip content={tooltip} side={tooltipSide} align={tooltipAlign} delayDuration={tooltipDelayDuration} disableHoverableContent={tooltipDisableHoverableContent} id={tooltipId}>
|
|
199
170
|
{button}
|
|
200
171
|
</Tooltip>
|
|
201
172
|
);
|
|
@@ -1130,19 +1130,7 @@ type SendProps = Omit<IconButtonProps, 'aria-label' | 'aria-labelledby'> & {
|
|
|
1130
1130
|
};
|
|
1131
1131
|
|
|
1132
1132
|
const Send = React.forwardRef<HTMLButtonElement, SendProps>((props, forwardedRef) => {
|
|
1133
|
-
const {
|
|
1134
|
-
asChild,
|
|
1135
|
-
clearOnSend = true,
|
|
1136
|
-
disabled,
|
|
1137
|
-
children,
|
|
1138
|
-
className,
|
|
1139
|
-
style,
|
|
1140
|
-
size: sizeProp,
|
|
1141
|
-
variant: variantProp,
|
|
1142
|
-
'aria-label': ariaLabel,
|
|
1143
|
-
'aria-labelledby': ariaLabelledby,
|
|
1144
|
-
...buttonProps
|
|
1145
|
-
} = props;
|
|
1133
|
+
const { asChild, clearOnSend = true, disabled, children, className, style, size: sizeProp, variant: variantProp, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, ...buttonProps } = props;
|
|
1146
1134
|
const ctx = useChatbarContext();
|
|
1147
1135
|
|
|
1148
1136
|
const trimmed = ctx.value.trim();
|
|
@@ -3,6 +3,7 @@ import classNames from 'classnames';
|
|
|
3
3
|
|
|
4
4
|
import { BaseButton } from './_internal/base-button.js';
|
|
5
5
|
import { Tooltip } from './tooltip.js';
|
|
6
|
+
import { useTooltipWrapper } from '../hooks/use-tooltip-wrapper.js';
|
|
6
7
|
import type { BaseButtonProps } from './_internal/base-button.js';
|
|
7
8
|
|
|
8
9
|
type IconButtonElement = React.ElementRef<typeof BaseButton>;
|
|
@@ -57,9 +58,7 @@ type IconButtonProps<C extends React.ElementType = 'button'> = IconButtonOwnProp
|
|
|
57
58
|
* IconButton component type that supports polymorphic rendering
|
|
58
59
|
* @template C - The element type to render as
|
|
59
60
|
*/
|
|
60
|
-
type IconButtonComponent = <C extends React.ElementType = 'button'>(
|
|
61
|
-
props: IconButtonProps<C> & { ref?: React.ForwardedRef<IconButtonElement> },
|
|
62
|
-
) => React.ReactElement | null;
|
|
61
|
+
type IconButtonComponent = <C extends React.ElementType = 'button'>(props: IconButtonProps<C> & { ref?: React.ForwardedRef<IconButtonElement> }) => React.ReactElement | null;
|
|
63
62
|
|
|
64
63
|
/**
|
|
65
64
|
* IconButton component for compact, accessible icon-only interactions
|
|
@@ -103,68 +102,45 @@ type IconButtonComponent = <C extends React.ElementType = 'button'>(
|
|
|
103
102
|
*/
|
|
104
103
|
const IconButton = React.forwardRef(
|
|
105
104
|
(
|
|
106
|
-
{
|
|
107
|
-
className,
|
|
108
|
-
tooltip,
|
|
109
|
-
tooltipSide = 'top',
|
|
110
|
-
tooltipAlign = 'center',
|
|
111
|
-
tooltipDelayDuration,
|
|
112
|
-
tooltipDisableHoverableContent,
|
|
113
|
-
...props
|
|
114
|
-
}: IconButtonProps,
|
|
105
|
+
{ className, tooltip, tooltipSide = 'top', tooltipAlign = 'center', tooltipDelayDuration, tooltipDisableHoverableContent, ...props }: IconButtonProps,
|
|
115
106
|
forwardedRef: React.ForwardedRef<IconButtonElement>,
|
|
116
107
|
) => {
|
|
117
|
-
//
|
|
118
|
-
const tooltipId =
|
|
108
|
+
// Use shared tooltip wrapper hook for accessibility props
|
|
109
|
+
const { tooltipId, hasTooltip, accessibilityProps: tooltipAccessibilityProps } = useTooltipWrapper(tooltip);
|
|
110
|
+
|
|
119
111
|
// Runtime accessibility validation to ensure WCAG compliance
|
|
120
112
|
// This helps catch accessibility issues during development
|
|
121
113
|
const hasAriaLabel = 'aria-label' in props && props['aria-label'];
|
|
122
114
|
const hasAriaLabelledBy = 'aria-labelledby' in props && props['aria-labelledby'];
|
|
123
115
|
const hasChildren = 'children' in props && props.children;
|
|
124
116
|
|
|
125
|
-
//
|
|
117
|
+
// Validate accessible name - throw in development, log error in production
|
|
126
118
|
if (!hasAriaLabel && !hasAriaLabelledBy && !hasChildren) {
|
|
127
|
-
|
|
119
|
+
const errorMessage =
|
|
128
120
|
'IconButton: Icon buttons must have an accessible name. Please provide either:' +
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
);
|
|
133
|
-
}
|
|
121
|
+
'\n- aria-label prop with descriptive text' +
|
|
122
|
+
'\n- aria-labelledby prop referencing a label element' +
|
|
123
|
+
'\n- or visible text children';
|
|
134
124
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
125
|
+
if (process.env.NODE_ENV === 'development') {
|
|
126
|
+
throw new Error(errorMessage);
|
|
127
|
+
} else {
|
|
128
|
+
console.error(errorMessage);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
141
131
|
|
|
142
132
|
// Create the base icon button element with accessibility props
|
|
143
|
-
const iconButton = (
|
|
144
|
-
<BaseButton
|
|
145
|
-
{...props}
|
|
146
|
-
{...tooltipAccessibilityProps}
|
|
147
|
-
ref={forwardedRef}
|
|
148
|
-
className={classNames('rt-IconButton', className)}
|
|
149
|
-
/>
|
|
150
|
-
);
|
|
133
|
+
const iconButton = <BaseButton {...props} {...tooltipAccessibilityProps} ref={forwardedRef} className={classNames('rt-IconButton', className)} />;
|
|
151
134
|
|
|
152
135
|
// If no tooltip is provided, return the icon button as-is for better performance
|
|
153
|
-
if (!
|
|
136
|
+
if (!hasTooltip) {
|
|
154
137
|
return iconButton;
|
|
155
138
|
}
|
|
156
139
|
|
|
157
140
|
// Wrap with Tooltip when tooltip content is provided
|
|
158
141
|
// This creates a compound component that handles both button and tooltip functionality
|
|
159
142
|
return (
|
|
160
|
-
<Tooltip
|
|
161
|
-
content={tooltip}
|
|
162
|
-
side={tooltipSide}
|
|
163
|
-
align={tooltipAlign}
|
|
164
|
-
delayDuration={tooltipDelayDuration}
|
|
165
|
-
disableHoverableContent={tooltipDisableHoverableContent}
|
|
166
|
-
id={tooltipId}
|
|
167
|
-
>
|
|
143
|
+
<Tooltip content={tooltip} side={tooltipSide} align={tooltipAlign} delayDuration={tooltipDelayDuration} disableHoverableContent={tooltipDisableHoverableContent} id={tooltipId}>
|
|
168
144
|
{iconButton}
|
|
169
145
|
</Tooltip>
|
|
170
146
|
);
|
package/src/components/image.css
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
.rt-Image {
|
|
64
64
|
border: 1px solid CanvasText;
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
/* Enhanced focus visibility in forced colors mode */
|
|
68
68
|
.rt-Image:where(:focus-visible) {
|
|
69
69
|
outline: 2px solid Highlight;
|
|
@@ -78,7 +78,9 @@
|
|
|
78
78
|
*/
|
|
79
79
|
.rt-Image:where(:any-link, button, label) {
|
|
80
80
|
cursor: pointer;
|
|
81
|
-
transition:
|
|
81
|
+
transition:
|
|
82
|
+
var(--transition-card),
|
|
83
|
+
filter var(--motion-duration-small) var(--motion-ease-standard); /* Smooth transitions for interactive states */
|
|
82
84
|
|
|
83
85
|
/*
|
|
84
86
|
* Hover effects with progressive enhancement
|
|
@@ -87,7 +89,7 @@
|
|
|
87
89
|
@media (hover: hover) {
|
|
88
90
|
&:where(:hover) {
|
|
89
91
|
box-shadow: var(--shadow-3); /* Subtle elevation on hover */
|
|
90
|
-
filter: brightness(1.
|
|
92
|
+
filter: brightness(1.08) contrast(1.02); /* Slight brightness/contrast boost */
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
|
|
@@ -116,13 +118,15 @@
|
|
|
116
118
|
*/
|
|
117
119
|
:where(:any-link, button, label) {
|
|
118
120
|
cursor: pointer;
|
|
119
|
-
transition:
|
|
121
|
+
transition:
|
|
122
|
+
var(--transition-card),
|
|
123
|
+
filter var(--motion-duration-small) var(--motion-ease-standard); /* Smooth transitions for interactive states */
|
|
120
124
|
|
|
121
125
|
/* Hover effects for wrapper elements */
|
|
122
126
|
@media (hover: hover) {
|
|
123
127
|
&:where(:hover) {
|
|
124
128
|
/* box-shadow: var(--shadow-3); */
|
|
125
|
-
filter: brightness(1.
|
|
129
|
+
filter: brightness(1.08) contrast(1.02);
|
|
126
130
|
}
|
|
127
131
|
}
|
|
128
132
|
|
|
@@ -141,8 +145,6 @@
|
|
|
141
145
|
outline-offset: -2px;
|
|
142
146
|
}
|
|
143
147
|
|
|
144
|
-
|
|
145
|
-
|
|
146
148
|
/*
|
|
147
149
|
* Object-fit variants for responsive image scaling
|
|
148
150
|
* These classes control how images are resized to fit their containers
|
|
@@ -217,7 +219,7 @@
|
|
|
217
219
|
color: var(--gray-11); /* Subtle but readable color */
|
|
218
220
|
margin-top: var(--space-2); /* Consistent spacing from image */
|
|
219
221
|
text-align: center; /* Centered alignment for visual balance */
|
|
220
|
-
|
|
222
|
+
|
|
221
223
|
/*
|
|
222
224
|
* Text wrapping and hyphenation for better layout
|
|
223
225
|
* Prevents caption text from breaking the layout
|
package/src/components/shell.css
CHANGED
|
@@ -189,17 +189,16 @@
|
|
|
189
189
|
width: var(--sidebar-thin-size, 64px);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
.rt-ShellSidebar[data-mode='collapsed'] {
|
|
193
|
-
width: 0px;
|
|
194
|
-
/* Delay container collapse until content fade completes */
|
|
195
|
-
transition-delay: var(--motion-duration-small);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
192
|
/* Keep collapsed sidebar out of flow to avoid layout blips when exiting peek */
|
|
199
193
|
.rt-ShellSidebar[data-mode='collapsed'] {
|
|
194
|
+
width: 0px;
|
|
200
195
|
position: absolute;
|
|
201
196
|
inset-block: 0;
|
|
202
197
|
inset-inline-start: 0;
|
|
198
|
+
flex-shrink: 0;
|
|
199
|
+
flex-basis: 0;
|
|
200
|
+
/* Delay container collapse until content fade completes */
|
|
201
|
+
transition-delay: var(--motion-duration-small);
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
.rt-ShellSidebarContent {
|
|
@@ -293,16 +292,16 @@
|
|
|
293
292
|
width: var(--inspector-size, 320px);
|
|
294
293
|
}
|
|
295
294
|
|
|
295
|
+
/* Keep collapsed inspector out of flow to avoid layout issues */
|
|
296
296
|
.rt-ShellInspector[data-mode='collapsed'] {
|
|
297
297
|
width: 0px;
|
|
298
|
-
/* Delay container collapse until content fade completes */
|
|
299
|
-
transition-delay: var(--motion-duration-small);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.rt-ShellInspector[data-mode='collapsed'] {
|
|
303
298
|
position: absolute;
|
|
304
299
|
inset-block: 0;
|
|
305
300
|
inset-inline-end: 0;
|
|
301
|
+
flex-shrink: 0;
|
|
302
|
+
flex-basis: 0;
|
|
303
|
+
/* Delay container collapse until content fade completes */
|
|
304
|
+
transition-delay: var(--motion-duration-small);
|
|
306
305
|
}
|
|
307
306
|
|
|
308
307
|
.rt-ShellInspectorContent {
|
package/src/components/shell.tsx
CHANGED
|
@@ -265,13 +265,57 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
|
|
|
265
265
|
(el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Inspector' && typeof (el as any).props?.open !== 'undefined' && Boolean((el as any).props?.open),
|
|
266
266
|
);
|
|
267
267
|
|
|
268
|
+
// Detect Sidebar initial state from props
|
|
269
|
+
const getSidebarInitialState = (): SidebarMode => {
|
|
270
|
+
const sidebarEl = initialChildren.find((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Sidebar');
|
|
271
|
+
if (!sidebarEl) return 'expanded';
|
|
272
|
+
const props = (sidebarEl as any).props;
|
|
273
|
+
// Check controlled state first
|
|
274
|
+
if (typeof props?.state !== 'undefined') {
|
|
275
|
+
if (typeof props.state === 'string') return props.state as SidebarMode;
|
|
276
|
+
// Responsive object - use 'initial' breakpoint or first defined value
|
|
277
|
+
if (typeof props.state === 'object') {
|
|
278
|
+
return (props.state.initial ?? Object.values(props.state)[0] ?? 'expanded') as SidebarMode;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Check defaultState
|
|
282
|
+
if (typeof props?.defaultState !== 'undefined') {
|
|
283
|
+
if (typeof props.defaultState === 'string') return props.defaultState as SidebarMode;
|
|
284
|
+
if (typeof props.defaultState === 'object') {
|
|
285
|
+
return (props.defaultState.initial ?? Object.values(props.defaultState)[0] ?? 'expanded') as SidebarMode;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return 'expanded';
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// Detect Bottom initial state from props
|
|
292
|
+
const getBottomInitialState = (): PaneMode => {
|
|
293
|
+
const bottomEl = initialChildren.find((el) => React.isValidElement(el) && (el as any).type?.displayName === 'Shell.Bottom');
|
|
294
|
+
if (!bottomEl) return 'collapsed';
|
|
295
|
+
const props = (bottomEl as any).props;
|
|
296
|
+
// Check controlled open first
|
|
297
|
+
if (typeof props?.open !== 'undefined') {
|
|
298
|
+
if (typeof props.open === 'boolean') return props.open ? 'expanded' : 'collapsed';
|
|
299
|
+
// Responsive object - use 'initial' breakpoint or first defined value
|
|
300
|
+
if (typeof props.open === 'object') {
|
|
301
|
+
const val = props.open.initial ?? Object.values(props.open)[0];
|
|
302
|
+
return val ? 'expanded' : 'collapsed';
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Check defaultOpen
|
|
306
|
+
if (typeof props?.defaultOpen !== 'undefined') {
|
|
307
|
+
return props.defaultOpen ? 'expanded' : 'collapsed';
|
|
308
|
+
}
|
|
309
|
+
return 'collapsed';
|
|
310
|
+
};
|
|
311
|
+
|
|
268
312
|
// Pane state management via reducer
|
|
269
313
|
const [paneState, dispatchPane] = React.useReducer(paneReducer, {
|
|
270
314
|
leftMode: hasPanelDefaultOpen || hasRailDefaultOpen ? 'expanded' : 'collapsed',
|
|
271
315
|
panelMode: hasPanelDefaultOpen ? 'expanded' : 'collapsed',
|
|
272
|
-
sidebarMode:
|
|
316
|
+
sidebarMode: getSidebarInitialState(),
|
|
273
317
|
inspectorMode: hasInspectorDefaultOpen || hasInspectorOpenControlled ? 'expanded' : 'collapsed',
|
|
274
|
-
bottomMode:
|
|
318
|
+
bottomMode: getBottomInitialState(),
|
|
275
319
|
});
|
|
276
320
|
const setLeftMode = React.useCallback((mode: PaneMode) => dispatchPane({ type: 'SET_LEFT_MODE', mode }), []);
|
|
277
321
|
const setPanelMode = React.useCallback((mode: PaneMode) => dispatchPane({ type: 'SET_PANEL_MODE', mode }), []);
|
|
@@ -450,17 +494,21 @@ const Root = React.forwardRef<HTMLDivElement, ShellRootProps>(({ className, chil
|
|
|
450
494
|
const peekCtxValue = React.useMemo(() => ({ peekTarget, setPeekTarget, peekPane, clearPeek }), [peekTarget, setPeekTarget, peekPane, clearPeek]);
|
|
451
495
|
const actionsCtxValue = React.useMemo(() => ({ togglePane, expandPane, collapsePane, setSidebarToggleComputer }), [togglePane, expandPane, collapsePane, setSidebarToggleComputer]);
|
|
452
496
|
|
|
497
|
+
// Memoized full context value for ShellProvider to prevent unnecessary effect re-runs
|
|
498
|
+
const shellContextValue = React.useMemo(
|
|
499
|
+
() => ({
|
|
500
|
+
...baseContextValue,
|
|
501
|
+
peekTarget,
|
|
502
|
+
setPeekTarget,
|
|
503
|
+
peekPane,
|
|
504
|
+
clearPeek,
|
|
505
|
+
}),
|
|
506
|
+
[baseContextValue, peekTarget, setPeekTarget, peekPane, clearPeek],
|
|
507
|
+
);
|
|
508
|
+
|
|
453
509
|
return (
|
|
454
510
|
<div {...props} ref={ref} className={classNames('rt-ShellRoot', className)} style={{ ...heightStyle, ...props.style }}>
|
|
455
|
-
<ShellProvider
|
|
456
|
-
value={{
|
|
457
|
-
...baseContextValue,
|
|
458
|
-
peekTarget,
|
|
459
|
-
setPeekTarget,
|
|
460
|
-
peekPane,
|
|
461
|
-
clearPeek,
|
|
462
|
-
}}
|
|
463
|
-
>
|
|
511
|
+
<ShellProvider value={shellContextValue}>
|
|
464
512
|
<PresentationContext.Provider value={presentationCtxValue}>
|
|
465
513
|
<LeftModeContext.Provider value={leftModeCtxValue}>
|
|
466
514
|
<PanelModeContext.Provider value={panelModeCtxValue}>
|