@proyecto-viviana/solidaria-components 0.1.3 → 0.2.2
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/dist/Color.d.ts +6 -2
- package/dist/Color.d.ts.map +1 -1
- package/dist/ComboBox.d.ts +3 -3
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/GridList.d.ts +2 -2
- package/dist/GridList.d.ts.map +1 -1
- package/dist/ListBox.d.ts +5 -5
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/Menu.d.ts +3 -3
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Select.d.ts +3 -3
- package/dist/Select.d.ts.map +1 -1
- package/dist/Table.d.ts +2 -2
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +1 -1
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/index.js +15 -15
- package/dist/index.js.map +2 -2
- package/dist/index.jsx +9056 -0
- package/dist/index.jsx.map +7 -0
- package/dist/index.ssr.js +15 -15
- package/dist/index.ssr.js.map +2 -2
- package/package.json +8 -10
- package/src/Autocomplete.tsx +0 -174
- package/src/Breadcrumbs.tsx +0 -264
- package/src/Button.tsx +0 -238
- package/src/Calendar.tsx +0 -471
- package/src/Checkbox.tsx +0 -387
- package/src/Color.tsx +0 -1370
- package/src/ComboBox.tsx +0 -824
- package/src/DateField.tsx +0 -337
- package/src/DatePicker.tsx +0 -367
- package/src/Dialog.tsx +0 -262
- package/src/Disclosure.tsx +0 -439
- package/src/GridList.tsx +0 -511
- package/src/Landmark.tsx +0 -203
- package/src/Link.tsx +0 -201
- package/src/ListBox.tsx +0 -346
- package/src/Menu.tsx +0 -544
- package/src/Meter.tsx +0 -157
- package/src/Modal.tsx +0 -433
- package/src/NumberField.tsx +0 -542
- package/src/Popover.tsx +0 -540
- package/src/ProgressBar.tsx +0 -162
- package/src/RadioGroup.tsx +0 -356
- package/src/RangeCalendar.tsx +0 -462
- package/src/SearchField.tsx +0 -479
- package/src/Select.tsx +0 -734
- package/src/Separator.tsx +0 -130
- package/src/Slider.tsx +0 -500
- package/src/Switch.tsx +0 -213
- package/src/Table.tsx +0 -857
- package/src/Tabs.tsx +0 -552
- package/src/TagGroup.tsx +0 -421
- package/src/TextField.tsx +0 -271
- package/src/TimeField.tsx +0 -455
- package/src/Toast.tsx +0 -503
- package/src/Toolbar.tsx +0 -160
- package/src/Tooltip.tsx +0 -423
- package/src/Tree.tsx +0 -551
- package/src/VisuallyHidden.tsx +0 -60
- package/src/contexts.ts +0 -74
- package/src/index.ts +0 -620
- package/src/utils.tsx +0 -329
package/src/Tooltip.tsx
DELETED
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tooltip component for solidaria-components
|
|
3
|
-
*
|
|
4
|
-
* A tooltip displays a description of an element on hover or focus.
|
|
5
|
-
* Port of react-aria-components/src/Tooltip.tsx
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
type JSX,
|
|
10
|
-
type ParentComponent,
|
|
11
|
-
createContext,
|
|
12
|
-
useContext,
|
|
13
|
-
createMemo,
|
|
14
|
-
createSignal,
|
|
15
|
-
createEffect,
|
|
16
|
-
onCleanup,
|
|
17
|
-
Show,
|
|
18
|
-
} from 'solid-js';
|
|
19
|
-
import { isServer } from 'solid-js/web';
|
|
20
|
-
import {
|
|
21
|
-
createTooltipTriggerState,
|
|
22
|
-
type TooltipTriggerState,
|
|
23
|
-
type TooltipTriggerProps as StateProps,
|
|
24
|
-
} from '@proyecto-viviana/solid-stately';
|
|
25
|
-
import {
|
|
26
|
-
createTooltip,
|
|
27
|
-
createTooltipTrigger,
|
|
28
|
-
type TooltipTriggerProps as AriaProps,
|
|
29
|
-
OverlayContainer,
|
|
30
|
-
} from '@proyecto-viviana/solidaria';
|
|
31
|
-
import {
|
|
32
|
-
type RenderChildren,
|
|
33
|
-
type ClassNameOrFunction,
|
|
34
|
-
type StyleOrFunction,
|
|
35
|
-
type SlotProps,
|
|
36
|
-
useRenderProps,
|
|
37
|
-
filterDOMProps,
|
|
38
|
-
} from './utils';
|
|
39
|
-
|
|
40
|
-
// ============================================
|
|
41
|
-
// TYPES
|
|
42
|
-
// ============================================
|
|
43
|
-
|
|
44
|
-
export interface TooltipRenderProps {
|
|
45
|
-
/** Whether the tooltip is currently entering (for animations). */
|
|
46
|
-
isEntering: boolean;
|
|
47
|
-
/** Whether the tooltip is currently exiting (for animations). */
|
|
48
|
-
isExiting: boolean;
|
|
49
|
-
/** The placement of the tooltip relative to the trigger. */
|
|
50
|
-
placement: 'top' | 'bottom' | 'left' | 'right' | null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface TooltipTriggerComponentProps extends StateProps, AriaProps {
|
|
54
|
-
/** The children of the tooltip trigger (trigger element and tooltip). */
|
|
55
|
-
children: JSX.Element;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface TooltipProps extends SlotProps {
|
|
59
|
-
/** The children of the tooltip. A function may be provided to receive render props. */
|
|
60
|
-
children?: RenderChildren<TooltipRenderProps>;
|
|
61
|
-
/** The CSS className for the element. */
|
|
62
|
-
class?: ClassNameOrFunction<TooltipRenderProps>;
|
|
63
|
-
/** The inline style for the element. */
|
|
64
|
-
style?: StyleOrFunction<TooltipRenderProps>;
|
|
65
|
-
/** Whether the tooltip is open (controlled). */
|
|
66
|
-
isOpen?: boolean;
|
|
67
|
-
/** Whether the tooltip is open by default (uncontrolled). */
|
|
68
|
-
defaultOpen?: boolean;
|
|
69
|
-
/** The placement of the tooltip relative to the trigger. */
|
|
70
|
-
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
71
|
-
/** Whether to render the tooltip in a portal. */
|
|
72
|
-
shouldFlip?: boolean;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ============================================
|
|
76
|
-
// CONTEXT
|
|
77
|
-
// ============================================
|
|
78
|
-
|
|
79
|
-
interface TooltipTriggerContextValue {
|
|
80
|
-
state: TooltipTriggerState;
|
|
81
|
-
tooltipProps: { id: string };
|
|
82
|
-
triggerRef: () => HTMLElement | null | undefined;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const TooltipTriggerContext = createContext<TooltipTriggerContextValue | null>(null);
|
|
86
|
-
|
|
87
|
-
// ============================================
|
|
88
|
-
// COMPONENTS
|
|
89
|
-
// ============================================
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* TooltipTrigger wraps around a trigger element and a Tooltip.
|
|
93
|
-
* It handles opening and closing the Tooltip when the user hovers
|
|
94
|
-
* over or focuses the trigger.
|
|
95
|
-
*
|
|
96
|
-
* @example
|
|
97
|
-
* ```tsx
|
|
98
|
-
* <TooltipTrigger>
|
|
99
|
-
* <Button>Hover me</Button>
|
|
100
|
-
* <Tooltip>This is a tooltip</Tooltip>
|
|
101
|
-
* </TooltipTrigger>
|
|
102
|
-
* ```
|
|
103
|
-
*/
|
|
104
|
-
export const TooltipTrigger: ParentComponent<TooltipTriggerComponentProps> = (props) => {
|
|
105
|
-
let triggerRef: HTMLElement | null = null;
|
|
106
|
-
|
|
107
|
-
const state = createTooltipTriggerState({
|
|
108
|
-
get delay() { return props.delay; },
|
|
109
|
-
get closeDelay() { return props.closeDelay; },
|
|
110
|
-
get isOpen() { return props.isOpen; },
|
|
111
|
-
get defaultOpen() { return props.defaultOpen; },
|
|
112
|
-
get onOpenChange() { return props.onOpenChange; },
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const { triggerProps, tooltipProps } = createTooltipTrigger(
|
|
116
|
-
{
|
|
117
|
-
get isDisabled() { return props.isDisabled; },
|
|
118
|
-
get trigger() { return props.trigger; },
|
|
119
|
-
get shouldCloseOnPress() { return props.shouldCloseOnPress; },
|
|
120
|
-
},
|
|
121
|
-
state,
|
|
122
|
-
() => triggerRef
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
const context: TooltipTriggerContextValue = {
|
|
126
|
-
state,
|
|
127
|
-
tooltipProps,
|
|
128
|
-
triggerRef: () => triggerRef,
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// Clone children and inject trigger props into the first child
|
|
132
|
-
const processChildren = () => {
|
|
133
|
-
const children = props.children;
|
|
134
|
-
if (Array.isArray(children)) {
|
|
135
|
-
// First child is the trigger, rest are tooltip(s)
|
|
136
|
-
const [trigger, ...rest] = children;
|
|
137
|
-
return (
|
|
138
|
-
<>
|
|
139
|
-
<TriggerWrapper
|
|
140
|
-
triggerProps={triggerProps}
|
|
141
|
-
ref={(el) => { triggerRef = el; }}
|
|
142
|
-
>
|
|
143
|
-
{trigger}
|
|
144
|
-
</TriggerWrapper>
|
|
145
|
-
{rest}
|
|
146
|
-
</>
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
return children;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
return (
|
|
153
|
-
<TooltipTriggerContext.Provider value={context}>
|
|
154
|
-
{processChildren()}
|
|
155
|
-
</TooltipTriggerContext.Provider>
|
|
156
|
-
);
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Wrapper component that spreads trigger props onto its child
|
|
161
|
-
*/
|
|
162
|
-
const TriggerWrapper: ParentComponent<{
|
|
163
|
-
triggerProps: JSX.HTMLAttributes<HTMLElement>;
|
|
164
|
-
ref: (el: HTMLElement) => void;
|
|
165
|
-
}> = (props) => {
|
|
166
|
-
// Get the child element and clone it with trigger props
|
|
167
|
-
const child = () => props.children as JSX.Element;
|
|
168
|
-
|
|
169
|
-
// We wrap in a span with display:contents to not affect layout.
|
|
170
|
-
// However, display:contents makes getBoundingClientRect return zeros,
|
|
171
|
-
// so we pass a ref callback that finds the first actual element child.
|
|
172
|
-
const handleRef = (span: HTMLSpanElement) => {
|
|
173
|
-
// Find the first element child that has dimensions (not display:contents)
|
|
174
|
-
const findVisibleChild = (el: Element): HTMLElement | null => {
|
|
175
|
-
if (el instanceof HTMLElement) {
|
|
176
|
-
const rect = el.getBoundingClientRect();
|
|
177
|
-
if (rect.width > 0 && rect.height > 0) {
|
|
178
|
-
return el;
|
|
179
|
-
}
|
|
180
|
-
// Check children
|
|
181
|
-
for (const child of el.children) {
|
|
182
|
-
const found = findVisibleChild(child);
|
|
183
|
-
if (found) return found;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return null;
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// Use requestAnimationFrame to ensure children are rendered and have dimensions
|
|
190
|
-
// This is necessary because SolidJS may not have computed child layout yet
|
|
191
|
-
const resolveRef = () => {
|
|
192
|
-
const visibleChild = findVisibleChild(span);
|
|
193
|
-
if (visibleChild) {
|
|
194
|
-
props.ref(visibleChild);
|
|
195
|
-
} else {
|
|
196
|
-
// Fallback to span itself
|
|
197
|
-
props.ref(span);
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
// Try immediately first (in case layout is already computed)
|
|
202
|
-
const immediateChild = findVisibleChild(span);
|
|
203
|
-
if (immediateChild) {
|
|
204
|
-
props.ref(immediateChild);
|
|
205
|
-
} else {
|
|
206
|
-
// Defer to next frame when layout should be computed
|
|
207
|
-
requestAnimationFrame(resolveRef);
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
return (
|
|
212
|
-
<span
|
|
213
|
-
{...props.triggerProps}
|
|
214
|
-
ref={handleRef}
|
|
215
|
-
style={{ display: 'contents' }}
|
|
216
|
-
>
|
|
217
|
-
{child()}
|
|
218
|
-
</span>
|
|
219
|
-
);
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* A tooltip displays a description of an element on hover or focus.
|
|
224
|
-
*
|
|
225
|
-
* @example
|
|
226
|
-
* ```tsx
|
|
227
|
-
* <TooltipTrigger>
|
|
228
|
-
* <Button>Hover me</Button>
|
|
229
|
-
* <Tooltip>This is helpful information</Tooltip>
|
|
230
|
-
* </TooltipTrigger>
|
|
231
|
-
* ```
|
|
232
|
-
*/
|
|
233
|
-
export function Tooltip(props: TooltipProps): JSX.Element {
|
|
234
|
-
const context = useContext(TooltipTriggerContext);
|
|
235
|
-
|
|
236
|
-
// Support standalone usage
|
|
237
|
-
const localState = createTooltipTriggerState({
|
|
238
|
-
get isOpen() { return props.isOpen; },
|
|
239
|
-
get defaultOpen() { return props.defaultOpen; },
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
const state = () => context?.state ?? localState;
|
|
243
|
-
const placement = () => props.placement ?? 'top';
|
|
244
|
-
|
|
245
|
-
// Only render when open
|
|
246
|
-
const isOpen = () => state().isOpen();
|
|
247
|
-
|
|
248
|
-
return (
|
|
249
|
-
<Show when={isOpen()}>
|
|
250
|
-
<TooltipContent
|
|
251
|
-
{...props}
|
|
252
|
-
state={state()}
|
|
253
|
-
contextTooltipProps={context?.tooltipProps ?? {}}
|
|
254
|
-
placement={placement()}
|
|
255
|
-
triggerRef={context?.triggerRef ?? (() => null)}
|
|
256
|
-
/>
|
|
257
|
-
</Show>
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Internal component that renders the tooltip content
|
|
263
|
-
*/
|
|
264
|
-
function TooltipContent(
|
|
265
|
-
props: TooltipProps & {
|
|
266
|
-
state: TooltipTriggerState;
|
|
267
|
-
contextTooltipProps: { id?: string };
|
|
268
|
-
placement: 'top' | 'bottom' | 'left' | 'right';
|
|
269
|
-
triggerRef: () => HTMLElement | null | undefined;
|
|
270
|
-
}
|
|
271
|
-
): JSX.Element {
|
|
272
|
-
if (isServer) {
|
|
273
|
-
return null as unknown as JSX.Element;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
let tooltipRef!: HTMLDivElement;
|
|
277
|
-
const { tooltipProps: ariaTooltipProps } = createTooltip({}, props.state);
|
|
278
|
-
|
|
279
|
-
// Signal to track position styles
|
|
280
|
-
// Start visible at 0,0 and update position asynchronously
|
|
281
|
-
// This ensures the tooltip is immediately accessible (for screen readers and tests)
|
|
282
|
-
// while the visual position gets calculated
|
|
283
|
-
const [positionStyles, setPositionStyles] = createSignal({
|
|
284
|
-
top: '0px',
|
|
285
|
-
left: '0px',
|
|
286
|
-
visibility: 'visible' as 'hidden' | 'visible',
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
const values = createMemo<TooltipRenderProps>(() => ({
|
|
290
|
-
isEntering: false, // TODO: animation support
|
|
291
|
-
isExiting: false,
|
|
292
|
-
placement: props.placement,
|
|
293
|
-
}));
|
|
294
|
-
|
|
295
|
-
const renderProps = useRenderProps(
|
|
296
|
-
{
|
|
297
|
-
class: props.class,
|
|
298
|
-
style: props.style,
|
|
299
|
-
children: props.children,
|
|
300
|
-
defaultClassName: 'solidaria-Tooltip',
|
|
301
|
-
},
|
|
302
|
-
values
|
|
303
|
-
);
|
|
304
|
-
|
|
305
|
-
// Calculate position based on trigger element
|
|
306
|
-
// Using position: fixed so we use viewport coordinates directly from getBoundingClientRect
|
|
307
|
-
// Returns true if position was successfully updated, false if we need to retry
|
|
308
|
-
const updatePosition = (): boolean => {
|
|
309
|
-
const triggerEl = props.triggerRef();
|
|
310
|
-
if (!triggerEl || !tooltipRef) return false;
|
|
311
|
-
|
|
312
|
-
const triggerRect = triggerEl.getBoundingClientRect();
|
|
313
|
-
|
|
314
|
-
// Check if the trigger has valid dimensions (not display:contents or not rendered yet)
|
|
315
|
-
if (triggerRect.width === 0 || triggerRect.height === 0) {
|
|
316
|
-
return false; // Need to retry
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Use offsetWidth/offsetHeight which are more reliable than getBoundingClientRect
|
|
320
|
-
// when the element might be positioned off-screen initially
|
|
321
|
-
const tooltipWidth = tooltipRef.offsetWidth;
|
|
322
|
-
const tooltipHeight = tooltipRef.offsetHeight;
|
|
323
|
-
const offset = 8; // Gap between trigger and tooltip
|
|
324
|
-
|
|
325
|
-
let top = 0;
|
|
326
|
-
let left = 0;
|
|
327
|
-
|
|
328
|
-
// Using viewport coordinates for position: fixed
|
|
329
|
-
switch (props.placement) {
|
|
330
|
-
case 'top':
|
|
331
|
-
top = triggerRect.top - tooltipHeight - offset;
|
|
332
|
-
left = triggerRect.left + (triggerRect.width - tooltipWidth) / 2;
|
|
333
|
-
break;
|
|
334
|
-
case 'bottom':
|
|
335
|
-
top = triggerRect.bottom + offset;
|
|
336
|
-
left = triggerRect.left + (triggerRect.width - tooltipWidth) / 2;
|
|
337
|
-
break;
|
|
338
|
-
case 'left':
|
|
339
|
-
top = triggerRect.top + (triggerRect.height - tooltipHeight) / 2;
|
|
340
|
-
left = triggerRect.left - tooltipWidth - offset;
|
|
341
|
-
break;
|
|
342
|
-
case 'right':
|
|
343
|
-
top = triggerRect.top + (triggerRect.height - tooltipHeight) / 2;
|
|
344
|
-
left = triggerRect.right + offset;
|
|
345
|
-
break;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
setPositionStyles({
|
|
349
|
-
top: `${top}px`,
|
|
350
|
-
left: `${left}px`,
|
|
351
|
-
visibility: 'visible',
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
return true;
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
// Set up positioning effect - runs when trigger ref is available
|
|
358
|
-
createEffect(() => {
|
|
359
|
-
const trigger = props.triggerRef();
|
|
360
|
-
if (!trigger) return;
|
|
361
|
-
|
|
362
|
-
// Initial position calculation - use requestAnimationFrame to ensure
|
|
363
|
-
// the element is rendered and has proper dimensions
|
|
364
|
-
// We may need multiple frames if the trigger ref hasn't resolved yet
|
|
365
|
-
let retryCount = 0;
|
|
366
|
-
const maxRetries = 5;
|
|
367
|
-
|
|
368
|
-
const tryUpdatePosition = () => {
|
|
369
|
-
const success = updatePosition();
|
|
370
|
-
if (!success && retryCount < maxRetries) {
|
|
371
|
-
retryCount++;
|
|
372
|
-
// In JSDOM, requestAnimationFrame may not trigger layout properly
|
|
373
|
-
// Use setTimeout for more reliable deferral across environments
|
|
374
|
-
setTimeout(tryUpdatePosition, 16); // ~60fps
|
|
375
|
-
}
|
|
376
|
-
// If all retries fail, tooltip stays at 0,0 (test environments)
|
|
377
|
-
// The tooltip is visible by default, so it remains accessible
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
// Initial attempt - use rAF for real browsers, then fall back to timeout retries
|
|
381
|
-
requestAnimationFrame(tryUpdatePosition);
|
|
382
|
-
|
|
383
|
-
// Update on scroll/resize
|
|
384
|
-
window.addEventListener('scroll', updatePosition, true);
|
|
385
|
-
window.addEventListener('resize', updatePosition);
|
|
386
|
-
|
|
387
|
-
onCleanup(() => {
|
|
388
|
-
window.removeEventListener('scroll', updatePosition, true);
|
|
389
|
-
window.removeEventListener('resize', updatePosition);
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
// Filter to only valid DOM props (aria-*, data-*, events)
|
|
394
|
-
const domProps = filterDOMProps(props);
|
|
395
|
-
|
|
396
|
-
// Extract ref from ariaTooltipProps to avoid type conflicts (SolidJS ref types are element-specific)
|
|
397
|
-
const { ref: _ariaRef, ...cleanAriaProps } = ariaTooltipProps as Record<string, unknown>;
|
|
398
|
-
|
|
399
|
-
return (
|
|
400
|
-
<OverlayContainer>
|
|
401
|
-
<div
|
|
402
|
-
{...domProps}
|
|
403
|
-
{...props.contextTooltipProps}
|
|
404
|
-
{...cleanAriaProps}
|
|
405
|
-
role="tooltip"
|
|
406
|
-
ref={tooltipRef}
|
|
407
|
-
class={renderProps.class()}
|
|
408
|
-
style={{
|
|
409
|
-
position: 'fixed',
|
|
410
|
-
'z-index': 100000,
|
|
411
|
-
...positionStyles(),
|
|
412
|
-
...renderProps.style(),
|
|
413
|
-
}}
|
|
414
|
-
data-placement={props.placement}
|
|
415
|
-
>
|
|
416
|
-
{renderProps.renderChildren()}
|
|
417
|
-
</div>
|
|
418
|
-
</OverlayContainer>
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Re-export types
|
|
423
|
-
export type { TooltipTriggerState };
|