@proyecto-viviana/ui 0.1.5 → 0.2.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 (130) hide show
  1. package/README.md +192 -0
  2. package/dist/autocomplete/index.d.ts +89 -0
  3. package/dist/autocomplete/index.d.ts.map +1 -0
  4. package/dist/breadcrumbs/index.d.ts +38 -0
  5. package/dist/breadcrumbs/index.d.ts.map +1 -0
  6. package/dist/button/Button.d.ts.map +1 -1
  7. package/dist/calendar/DateField.d.ts +47 -0
  8. package/dist/calendar/DateField.d.ts.map +1 -0
  9. package/dist/calendar/DatePicker.d.ts +48 -0
  10. package/dist/calendar/DatePicker.d.ts.map +1 -0
  11. package/dist/calendar/RangeCalendar.d.ts +42 -0
  12. package/dist/calendar/RangeCalendar.d.ts.map +1 -0
  13. package/dist/calendar/TimeField.d.ts +44 -0
  14. package/dist/calendar/TimeField.d.ts.map +1 -0
  15. package/dist/calendar/index.d.ts +50 -0
  16. package/dist/calendar/index.d.ts.map +1 -0
  17. package/dist/checkbox/index.d.ts.map +1 -1
  18. package/dist/color/index.d.ts +228 -0
  19. package/dist/color/index.d.ts.map +1 -0
  20. package/dist/combobox/index.d.ts +81 -0
  21. package/dist/combobox/index.d.ts.map +1 -0
  22. package/dist/components.css +116 -14
  23. package/dist/custom/chip/index.d.ts +7 -2
  24. package/dist/custom/chip/index.d.ts.map +1 -1
  25. package/dist/custom/event-card/index.d.ts +5 -1
  26. package/dist/custom/event-card/index.d.ts.map +1 -1
  27. package/dist/custom/header/index.d.ts +16 -0
  28. package/dist/custom/header/index.d.ts.map +1 -0
  29. package/dist/custom/logo/index.d.ts +2 -0
  30. package/dist/custom/logo/index.d.ts.map +1 -1
  31. package/dist/custom/page-layout/index.d.ts +2 -0
  32. package/dist/custom/page-layout/index.d.ts.map +1 -1
  33. package/dist/custom/profile-card/index.d.ts +5 -1
  34. package/dist/custom/profile-card/index.d.ts.map +1 -1
  35. package/dist/custom/timeline-item/index.d.ts +12 -2
  36. package/dist/custom/timeline-item/index.d.ts.map +1 -1
  37. package/dist/dialog/Dialog.d.ts +67 -0
  38. package/dist/dialog/Dialog.d.ts.map +1 -0
  39. package/dist/dialog/index.d.ts +2 -17
  40. package/dist/dialog/index.d.ts.map +1 -1
  41. package/dist/disclosure/index.d.ts +84 -0
  42. package/dist/disclosure/index.d.ts.map +1 -0
  43. package/dist/gridlist/index.d.ts +92 -0
  44. package/dist/gridlist/index.d.ts.map +1 -0
  45. package/dist/index.d.ts +58 -4
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +6984 -783
  48. package/dist/index.js.map +1 -1
  49. package/dist/index.ssr.js +5905 -571
  50. package/dist/index.ssr.js.map +1 -1
  51. package/dist/landmark/index.d.ts +83 -0
  52. package/dist/landmark/index.d.ts.map +1 -0
  53. package/dist/link/index.d.ts.map +1 -1
  54. package/dist/listbox/index.d.ts +47 -0
  55. package/dist/listbox/index.d.ts.map +1 -0
  56. package/dist/menu/index.d.ts +74 -0
  57. package/dist/menu/index.d.ts.map +1 -0
  58. package/dist/meter/index.d.ts +49 -0
  59. package/dist/meter/index.d.ts.map +1 -0
  60. package/dist/numberfield/index.d.ts +50 -0
  61. package/dist/numberfield/index.d.ts.map +1 -0
  62. package/dist/popover/index.d.ts +85 -0
  63. package/dist/popover/index.d.ts.map +1 -0
  64. package/dist/radio/index.d.ts +7 -4
  65. package/dist/radio/index.d.ts.map +1 -1
  66. package/dist/searchfield/index.d.ts +44 -0
  67. package/dist/searchfield/index.d.ts.map +1 -0
  68. package/dist/select/index.d.ts +72 -0
  69. package/dist/select/index.d.ts.map +1 -0
  70. package/dist/slider/index.d.ts +53 -0
  71. package/dist/slider/index.d.ts.map +1 -0
  72. package/dist/switch/ToggleSwitch.d.ts.map +1 -1
  73. package/dist/table/index.d.ts +140 -0
  74. package/dist/table/index.d.ts.map +1 -0
  75. package/dist/tabs/index.d.ts +56 -0
  76. package/dist/tabs/index.d.ts.map +1 -0
  77. package/dist/tag-group/index.d.ts +80 -0
  78. package/dist/tag-group/index.d.ts.map +1 -0
  79. package/dist/toast/index.d.ts +101 -0
  80. package/dist/toast/index.d.ts.map +1 -0
  81. package/dist/toolbar/index.d.ts +42 -0
  82. package/dist/toolbar/index.d.ts.map +1 -0
  83. package/dist/tooltip/index.d.ts +66 -5
  84. package/dist/tooltip/index.d.ts.map +1 -1
  85. package/dist/tree/index.d.ts +99 -0
  86. package/dist/tree/index.d.ts.map +1 -0
  87. package/package.json +66 -57
  88. package/src/autocomplete/index.tsx +313 -0
  89. package/src/breadcrumbs/index.tsx +207 -0
  90. package/src/button/Button.tsx +74 -75
  91. package/src/calendar/DateField.tsx +200 -0
  92. package/src/calendar/DatePicker.tsx +298 -0
  93. package/src/calendar/RangeCalendar.tsx +236 -0
  94. package/src/calendar/TimeField.tsx +196 -0
  95. package/src/calendar/index.tsx +223 -0
  96. package/src/checkbox/index.tsx +3 -4
  97. package/src/color/index.tsx +687 -0
  98. package/src/combobox/index.tsx +383 -0
  99. package/src/components.css +116 -14
  100. package/src/custom/chip/index.tsx +17 -3
  101. package/src/custom/event-card/index.tsx +8 -2
  102. package/src/custom/header/index.tsx +33 -0
  103. package/src/custom/logo/index.tsx +7 -3
  104. package/src/custom/page-layout/index.tsx +12 -3
  105. package/src/custom/profile-card/index.tsx +8 -2
  106. package/src/custom/timeline-item/index.tsx +28 -4
  107. package/src/dialog/Dialog.tsx +260 -0
  108. package/src/dialog/index.tsx +3 -69
  109. package/src/disclosure/index.tsx +307 -0
  110. package/src/gridlist/index.tsx +403 -0
  111. package/src/index.ts +219 -4
  112. package/src/landmark/index.tsx +231 -0
  113. package/src/link/index.tsx +1 -2
  114. package/src/listbox/index.tsx +231 -0
  115. package/src/menu/index.tsx +297 -0
  116. package/src/meter/index.tsx +163 -0
  117. package/src/numberfield/index.tsx +482 -0
  118. package/src/popover/index.tsx +260 -0
  119. package/src/radio/index.tsx +36 -82
  120. package/src/searchfield/index.tsx +453 -0
  121. package/src/select/index.tsx +349 -0
  122. package/src/slider/index.tsx +382 -0
  123. package/src/switch/ToggleSwitch.tsx +1 -2
  124. package/src/table/index.tsx +531 -0
  125. package/src/tabs/index.tsx +273 -0
  126. package/src/tag-group/index.tsx +240 -0
  127. package/src/toast/index.tsx +324 -0
  128. package/src/toolbar/index.tsx +108 -0
  129. package/src/tooltip/index.tsx +171 -5
  130. package/src/tree/index.tsx +494 -0
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Toast components for proyecto-viviana-ui
3
+ *
4
+ * Toast notifications with auto-dismiss, animations, and variants.
5
+ * Built on top of solidaria-components for accessibility.
6
+ */
7
+
8
+ import { type JSX, splitProps, For, Show } from 'solid-js';
9
+ import {
10
+ Toast as HeadlessToast,
11
+ ToastRegion as HeadlessToastRegion,
12
+ ToastProvider as HeadlessToastProvider,
13
+ ToastContext,
14
+ ToastCloseButton as HeadlessToastCloseButton,
15
+ globalToastQueue,
16
+ addToast as headlessAddToast,
17
+ useToastContext,
18
+ type ToastContent,
19
+ type ToastProps as HeadlessToastProps,
20
+ type ToastRegionProps as HeadlessToastRegionProps,
21
+ type ToastProviderProps as HeadlessToastProviderProps,
22
+ type ToastRenderProps,
23
+ type ToastRegionRenderProps,
24
+ } from '@proyecto-viviana/solidaria-components';
25
+ import { type QueuedToast, type ToastOptions } from '@proyecto-viviana/solid-stately';
26
+
27
+ // ============================================
28
+ // TYPES
29
+ // ============================================
30
+
31
+ export type ToastPlacement = 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end';
32
+ export type ToastVariant = 'info' | 'success' | 'warning' | 'error' | 'neutral';
33
+
34
+ export interface ToastProviderProps extends HeadlessToastProviderProps {}
35
+
36
+ export interface ToastRegionProps extends Omit<HeadlessToastRegionProps, 'class' | 'style' | 'children'> {
37
+ /** The placement of the toast region. */
38
+ placement?: ToastPlacement;
39
+ /** Additional CSS class name. */
40
+ class?: string;
41
+ }
42
+
43
+ export interface ToastProps extends Omit<HeadlessToastProps, 'class' | 'style'> {
44
+ /** Additional CSS class name. */
45
+ class?: string;
46
+ }
47
+
48
+ // ============================================
49
+ // STYLES
50
+ // ============================================
51
+
52
+ const regionStyles = [
53
+ 'flex flex-col gap-3',
54
+ 'p-4',
55
+ ].join(' ');
56
+
57
+ const toastBaseStyles = [
58
+ 'flex items-start gap-3',
59
+ 'px-4 py-3',
60
+ 'rounded-lg shadow-lg',
61
+ 'min-w-[300px] max-w-[400px]',
62
+ 'border',
63
+ // Animations
64
+ 'data-[animation=entering]:animate-in data-[animation=entering]:fade-in-0 data-[animation=entering]:slide-in-from-right-5',
65
+ 'data-[animation=exiting]:animate-out data-[animation=exiting]:fade-out-0 data-[animation=exiting]:slide-out-to-right-5',
66
+ ].join(' ');
67
+
68
+ const variantStyles: Record<ToastVariant, string> = {
69
+ info: 'bg-blue-50 border-blue-200 text-blue-800 dark:bg-blue-950 dark:border-blue-800 dark:text-blue-200',
70
+ success: 'bg-green-50 border-green-200 text-green-800 dark:bg-green-950 dark:border-green-800 dark:text-green-200',
71
+ warning: 'bg-yellow-50 border-yellow-200 text-yellow-800 dark:bg-yellow-950 dark:border-yellow-800 dark:text-yellow-200',
72
+ error: 'bg-red-50 border-red-200 text-red-800 dark:bg-red-950 dark:border-red-800 dark:text-red-200',
73
+ neutral: 'bg-neutral-50 border-neutral-200 text-neutral-800 dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-200',
74
+ };
75
+
76
+ const iconStyles: Record<ToastVariant, string> = {
77
+ info: 'text-blue-500 dark:text-blue-400',
78
+ success: 'text-green-500 dark:text-green-400',
79
+ warning: 'text-yellow-500 dark:text-yellow-400',
80
+ error: 'text-red-500 dark:text-red-400',
81
+ neutral: 'text-neutral-500 dark:text-neutral-400',
82
+ };
83
+
84
+ const closeButtonStyles = [
85
+ 'ml-auto -mr-1 -mt-1',
86
+ 'p-1 rounded-md',
87
+ 'text-current opacity-60 hover:opacity-100',
88
+ 'transition-opacity',
89
+ 'focus:outline-none focus:ring-2 focus:ring-offset-2',
90
+ ].join(' ');
91
+
92
+ // ============================================
93
+ // ICONS
94
+ // ============================================
95
+
96
+ const InfoIcon = () => (
97
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
98
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
99
+ </svg>
100
+ );
101
+
102
+ const SuccessIcon = () => (
103
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
104
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
105
+ </svg>
106
+ );
107
+
108
+ const WarningIcon = () => (
109
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
110
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
111
+ </svg>
112
+ );
113
+
114
+ const ErrorIcon = () => (
115
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
116
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
117
+ </svg>
118
+ );
119
+
120
+ const CloseIcon = () => (
121
+ <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
122
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
123
+ </svg>
124
+ );
125
+
126
+ const getVariantIcon = (variant: ToastVariant) => {
127
+ switch (variant) {
128
+ case 'success': return <SuccessIcon />;
129
+ case 'warning': return <WarningIcon />;
130
+ case 'error': return <ErrorIcon />;
131
+ case 'info':
132
+ case 'neutral':
133
+ default: return <InfoIcon />;
134
+ }
135
+ };
136
+
137
+ // ============================================
138
+ // COMPONENTS
139
+ // ============================================
140
+
141
+ /**
142
+ * ToastProvider creates a toast queue context for descendant components.
143
+ * Wrap your app or a section that needs toast notifications.
144
+ *
145
+ * @example
146
+ * ```tsx
147
+ * <ToastProvider>
148
+ * <App />
149
+ * <ToastRegion placement="bottom-end" />
150
+ * </ToastProvider>
151
+ * ```
152
+ */
153
+ export function ToastProvider(props: ToastProviderProps): JSX.Element {
154
+ return <HeadlessToastProvider {...props} />;
155
+ }
156
+
157
+ /**
158
+ * ToastRegion displays all visible toasts in a fixed position.
159
+ *
160
+ * @example
161
+ * ```tsx
162
+ * <ToastRegion placement="bottom-end" />
163
+ * ```
164
+ */
165
+ export function ToastRegion(props: ToastRegionProps): JSX.Element {
166
+ const [local, rest] = splitProps(props, ['placement', 'class']);
167
+
168
+ return (
169
+ <HeadlessToastRegion
170
+ {...rest}
171
+ placement={local.placement ?? 'bottom-end'}
172
+ class={(_renderProps: ToastRegionRenderProps) => {
173
+ return [regionStyles, local.class ?? ''].filter(Boolean).join(' ');
174
+ }}
175
+ >
176
+ {(regionProps: ToastRegionRenderProps) => (
177
+ <For each={regionProps.visibleToasts}>
178
+ {(toast) => <Toast toast={toast} />}
179
+ </For>
180
+ )}
181
+ </HeadlessToastRegion>
182
+ );
183
+ }
184
+
185
+ /**
186
+ * Toast displays an individual notification with icon, content, and close button.
187
+ *
188
+ * Usually you don't need to use this directly - ToastRegion renders toasts automatically.
189
+ */
190
+ export function Toast(props: ToastProps): JSX.Element {
191
+ const [local, rest] = splitProps(props, ['toast', 'class']);
192
+
193
+ const content = () => local.toast.content;
194
+ const variant = (): ToastVariant => content().type ?? 'neutral';
195
+
196
+ return (
197
+ <HeadlessToast
198
+ {...rest}
199
+ toast={local.toast}
200
+ class={(_renderProps: ToastRenderProps) => {
201
+ return [
202
+ toastBaseStyles,
203
+ variantStyles[variant()],
204
+ local.class ?? '',
205
+ ].filter(Boolean).join(' ');
206
+ }}
207
+ >
208
+ {/* Icon */}
209
+ <div class={`flex-shrink-0 ${iconStyles[variant()]}`}>
210
+ {getVariantIcon(variant())}
211
+ </div>
212
+
213
+ {/* Content */}
214
+ <div class="flex-1 min-w-0">
215
+ <Show when={content().title}>
216
+ <p class="font-semibold text-sm">{content().title}</p>
217
+ </Show>
218
+ <Show when={content().description}>
219
+ <p class="text-sm opacity-90 mt-1">{content().description}</p>
220
+ </Show>
221
+ <Show when={content().action}>
222
+ <button
223
+ type="button"
224
+ class="mt-2 text-sm font-medium underline hover:no-underline"
225
+ onClick={content().action?.onAction}
226
+ >
227
+ {content().action?.label}
228
+ </button>
229
+ </Show>
230
+ </div>
231
+
232
+ {/* Close Button */}
233
+ <HeadlessToastCloseButton
234
+ toast={local.toast}
235
+ class={closeButtonStyles}
236
+ aria-label="Dismiss"
237
+ >
238
+ <CloseIcon />
239
+ </HeadlessToastCloseButton>
240
+ </HeadlessToast>
241
+ );
242
+ }
243
+
244
+ // ============================================
245
+ // GLOBAL TOAST API
246
+ // ============================================
247
+
248
+ /**
249
+ * Add a toast to the global queue.
250
+ * Use this to show toasts from anywhere in your app.
251
+ *
252
+ * @example
253
+ * ```tsx
254
+ * // Show a success toast
255
+ * addToast({
256
+ * title: 'Success!',
257
+ * description: 'Your changes have been saved.',
258
+ * type: 'success',
259
+ * });
260
+ *
261
+ * // Show an error toast with auto-dismiss
262
+ * addToast({
263
+ * title: 'Error',
264
+ * description: 'Something went wrong.',
265
+ * type: 'error',
266
+ * }, { timeout: 5000 });
267
+ *
268
+ * // Show a toast with action
269
+ * addToast({
270
+ * title: 'File deleted',
271
+ * type: 'info',
272
+ * action: {
273
+ * label: 'Undo',
274
+ * onAction: () => restoreFile(),
275
+ * },
276
+ * }, { timeout: 10000 });
277
+ * ```
278
+ */
279
+ export function addToast(
280
+ content: ToastContent,
281
+ options?: ToastOptions
282
+ ): string {
283
+ return headlessAddToast(content, options);
284
+ }
285
+
286
+ /**
287
+ * Convenience function to show a success toast.
288
+ */
289
+ export function toastSuccess(message: string, options?: Omit<ToastOptions, 'priority'>): string {
290
+ return addToast({ title: message, type: 'success' }, { timeout: 5000, ...options });
291
+ }
292
+
293
+ /**
294
+ * Convenience function to show an error toast.
295
+ */
296
+ export function toastError(message: string, options?: Omit<ToastOptions, 'priority'>): string {
297
+ return addToast({ title: message, type: 'error' }, { timeout: 8000, ...options });
298
+ }
299
+
300
+ /**
301
+ * Convenience function to show a warning toast.
302
+ */
303
+ export function toastWarning(message: string, options?: Omit<ToastOptions, 'priority'>): string {
304
+ return addToast({ title: message, type: 'warning' }, { timeout: 6000, ...options });
305
+ }
306
+
307
+ /**
308
+ * Convenience function to show an info toast.
309
+ */
310
+ export function toastInfo(message: string, options?: Omit<ToastOptions, 'priority'>): string {
311
+ return addToast({ title: message, type: 'info' }, { timeout: 5000, ...options });
312
+ }
313
+
314
+ // Re-exports
315
+ export {
316
+ ToastContext,
317
+ globalToastQueue,
318
+ useToastContext,
319
+ type ToastContent,
320
+ type ToastRenderProps,
321
+ type ToastRegionRenderProps,
322
+ type QueuedToast,
323
+ type ToastOptions,
324
+ };
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Toolbar component for proyecto-viviana-ui
3
+ *
4
+ * Styled toolbar component built on top of solidaria-components Toolbar.
5
+ */
6
+
7
+ import { type JSX, splitProps, createMemo } from 'solid-js'
8
+ import {
9
+ Toolbar as HeadlessToolbar,
10
+ type ToolbarProps as HeadlessToolbarProps,
11
+ type ToolbarRenderProps,
12
+ } from '@proyecto-viviana/solidaria-components'
13
+
14
+ // ============================================
15
+ // TYPES
16
+ // ============================================
17
+
18
+ export type ToolbarSize = 'sm' | 'md' | 'lg'
19
+ export type ToolbarVariant = 'default' | 'bordered' | 'ghost'
20
+
21
+ export interface ToolbarProps extends Omit<HeadlessToolbarProps, 'class' | 'style'> {
22
+ /** The visual variant of the toolbar. @default 'default' */
23
+ variant?: ToolbarVariant
24
+ /** The size of the toolbar. @default 'md' */
25
+ size?: ToolbarSize
26
+ /** Additional CSS class name. */
27
+ class?: string
28
+ /** Inline styles. */
29
+ style?: JSX.CSSProperties
30
+ }
31
+
32
+ // ============================================
33
+ // STYLES
34
+ // ============================================
35
+
36
+ const baseStyles = 'vui-toolbar inline-flex items-center'
37
+
38
+ const variantStyles: Record<ToolbarVariant, string> = {
39
+ default: 'bg-bg-50 rounded-md',
40
+ bordered: 'border border-bg-200 rounded-md',
41
+ ghost: '',
42
+ }
43
+
44
+ const sizeStyles: Record<ToolbarSize, string> = {
45
+ sm: 'gap-1 p-1',
46
+ md: 'gap-2 p-2',
47
+ lg: 'gap-3 p-3',
48
+ }
49
+
50
+ const orientationStyles = {
51
+ horizontal: 'flex-row',
52
+ vertical: 'flex-col',
53
+ }
54
+
55
+ // ============================================
56
+ // TOOLBAR COMPONENT
57
+ // ============================================
58
+
59
+ /**
60
+ * A styled toolbar for grouping interactive controls with keyboard navigation.
61
+ *
62
+ * @example
63
+ * ```tsx
64
+ * <Toolbar aria-label="Text formatting">
65
+ * <Button>Bold</Button>
66
+ * <Button>Italic</Button>
67
+ * <Separator orientation="vertical" />
68
+ * <Button>Align Left</Button>
69
+ * <Button>Align Center</Button>
70
+ * </Toolbar>
71
+ *
72
+ * // Vertical toolbar
73
+ * <Toolbar orientation="vertical" variant="bordered">
74
+ * <Button>Cut</Button>
75
+ * <Button>Copy</Button>
76
+ * <Button>Paste</Button>
77
+ * </Toolbar>
78
+ * ```
79
+ */
80
+ export function Toolbar(props: ToolbarProps): JSX.Element {
81
+ const [local, headlessProps] = splitProps(props, [
82
+ 'variant',
83
+ 'size',
84
+ 'class',
85
+ 'style',
86
+ ])
87
+
88
+ const variant = () => local.variant ?? 'default'
89
+ const size = () => local.size ?? 'md'
90
+
91
+ const getClassName = (renderProps: ToolbarRenderProps): string => {
92
+ return [
93
+ baseStyles,
94
+ variantStyles[variant()],
95
+ sizeStyles[size()],
96
+ orientationStyles[renderProps.orientation],
97
+ local.class ?? '',
98
+ ].filter(Boolean).join(' ')
99
+ }
100
+
101
+ return (
102
+ <HeadlessToolbar
103
+ {...headlessProps}
104
+ class={getClassName}
105
+ style={local.style}
106
+ />
107
+ )
108
+ }
@@ -1,6 +1,160 @@
1
- import type { JSX } from 'solid-js'
1
+ /**
2
+ * Tooltip component for proyecto-viviana-ui
3
+ *
4
+ * A tooltip displays a description of an element on hover or focus.
5
+ * Built on top of solidaria-components for accessibility.
6
+ */
7
+
8
+ import { type JSX, Show, splitProps } from 'solid-js'
9
+ import {
10
+ Tooltip as HeadlessTooltip,
11
+ TooltipTrigger as HeadlessTooltipTrigger,
12
+ type TooltipProps as HeadlessTooltipProps,
13
+ type TooltipTriggerComponentProps as HeadlessTooltipTriggerProps,
14
+ type TooltipRenderProps,
15
+ } from '@proyecto-viviana/solidaria-components'
16
+
17
+ // ============================================
18
+ // TYPES
19
+ // ============================================
20
+
21
+ export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right'
22
+ export type TooltipVariant = 'default' | 'neutral' | 'info'
2
23
 
3
- export interface TooltipProps {
24
+ export interface TooltipTriggerProps extends HeadlessTooltipTriggerProps {
25
+ /** The children of the tooltip trigger (trigger element and tooltip). */
26
+ children: JSX.Element
27
+ }
28
+
29
+ export interface TooltipProps extends Omit<HeadlessTooltipProps, 'class' | 'style' | 'children'> {
30
+ /** The content of the tooltip. */
31
+ children: JSX.Element
32
+ /** The position of the tooltip relative to the trigger. */
33
+ placement?: TooltipPlacement
34
+ /** Visual variant of the tooltip. */
35
+ variant?: TooltipVariant
36
+ /** Additional CSS class name. */
37
+ class?: string
38
+ /** Whether to show an arrow pointing to the trigger. */
39
+ showArrow?: boolean
40
+ }
41
+
42
+ // ============================================
43
+ // STYLES
44
+ // ============================================
45
+
46
+ // Note: Position is now calculated by the headless layer (solidaria-components)
47
+ // so we don't need CSS positioning classes here
48
+ const baseStyles = [
49
+ 'px-3 py-2 rounded-lg',
50
+ 'text-sm font-medium',
51
+ 'shadow-lg',
52
+ 'pointer-events-auto',
53
+ 'animate-in fade-in-0 zoom-in-95',
54
+ 'data-[exiting]:animate-out data-[exiting]:fade-out-0 data-[exiting]:zoom-out-95',
55
+ ].join(' ')
56
+
57
+ const variantStyles: Record<TooltipVariant, string> = {
58
+ default: 'bg-neutral-900 text-white dark:bg-neutral-100 dark:text-neutral-900',
59
+ neutral: 'bg-neutral-800 text-neutral-100 dark:bg-neutral-200 dark:text-neutral-900',
60
+ info: 'bg-blue-600 text-white dark:bg-blue-500',
61
+ }
62
+
63
+ const arrowStyles: Record<TooltipPlacement, string> = {
64
+ top: 'bottom-0 left-1/2 -translate-x-1/2 translate-y-full border-l-transparent border-r-transparent border-b-transparent',
65
+ bottom: 'top-0 left-1/2 -translate-x-1/2 -translate-y-full border-l-transparent border-r-transparent border-t-transparent',
66
+ left: 'right-0 top-1/2 -translate-y-1/2 translate-x-full border-t-transparent border-b-transparent border-r-transparent',
67
+ right: 'left-0 top-1/2 -translate-y-1/2 -translate-x-full border-t-transparent border-b-transparent border-l-transparent',
68
+ }
69
+
70
+ const getArrowBorderColor = (variant: TooltipVariant): string => {
71
+ const colors: Record<TooltipVariant, string> = {
72
+ default: 'border-neutral-900 dark:border-neutral-100',
73
+ neutral: 'border-neutral-800 dark:border-neutral-200',
74
+ info: 'border-blue-600 dark:border-blue-500',
75
+ }
76
+ return colors[variant]
77
+ }
78
+
79
+ // ============================================
80
+ // COMPONENTS
81
+ // ============================================
82
+
83
+ /**
84
+ * TooltipTrigger wraps around a trigger element and a Tooltip.
85
+ * It handles opening and closing the Tooltip when the user hovers
86
+ * over or focuses the trigger.
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * <TooltipTrigger>
91
+ * <Button>Hover me</Button>
92
+ * <Tooltip>This is helpful information</Tooltip>
93
+ * </TooltipTrigger>
94
+ * ```
95
+ */
96
+ export function TooltipTrigger(props: TooltipTriggerProps): JSX.Element {
97
+ return <HeadlessTooltipTrigger {...props} />
98
+ }
99
+
100
+ /**
101
+ * Styled tooltip component that displays a description on hover or focus.
102
+ *
103
+ * @example
104
+ * ```tsx
105
+ * <TooltipTrigger>
106
+ * <Button>Save</Button>
107
+ * <Tooltip placement="top">Save your changes</Tooltip>
108
+ * </TooltipTrigger>
109
+ * ```
110
+ */
111
+ export function Tooltip(props: TooltipProps): JSX.Element {
112
+ const [local, rest] = splitProps(props, [
113
+ 'placement',
114
+ 'variant',
115
+ 'class',
116
+ 'showArrow',
117
+ ])
118
+
119
+ const placement = () => local.placement ?? 'top'
120
+ const variant = () => local.variant ?? 'default'
121
+
122
+ return (
123
+ <HeadlessTooltip
124
+ {...rest}
125
+ placement={placement()}
126
+ class={(_renderProps: TooltipRenderProps) => {
127
+ const classes = [
128
+ baseStyles,
129
+ variantStyles[variant()],
130
+ local.class ?? '',
131
+ ].filter(Boolean).join(' ')
132
+ return classes
133
+ }}
134
+ >
135
+ {(renderProps: TooltipRenderProps) => (
136
+ <>
137
+ {props.children}
138
+ <Show when={local.showArrow}>
139
+ <div
140
+ class={[
141
+ 'absolute w-0 h-0 border-4',
142
+ arrowStyles[renderProps.placement ?? placement()],
143
+ getArrowBorderColor(variant()),
144
+ ].join(' ')}
145
+ />
146
+ </Show>
147
+ </>
148
+ )}
149
+ </HeadlessTooltip>
150
+ )
151
+ }
152
+
153
+ // ============================================
154
+ // SIMPLE CSS-ONLY TOOLTIP (Legacy)
155
+ // ============================================
156
+
157
+ export interface SimpleTooltipProps {
4
158
  /** The content to show in the tooltip */
5
159
  label: string
6
160
  /** The trigger element */
@@ -12,10 +166,19 @@ export interface TooltipProps {
12
166
  }
13
167
 
14
168
  /**
15
- * Tooltip component that shows a label on hover.
16
- * Uses CSS-only hover effect for performance.
169
+ * Simple CSS-only tooltip component.
170
+ * Uses CSS hover effect for performance. No JS state management.
171
+ *
172
+ * @deprecated Use the accessible Tooltip + TooltipTrigger components instead.
173
+ *
174
+ * @example
175
+ * ```tsx
176
+ * <SimpleTooltip label="Save your changes">
177
+ * <button>Save</button>
178
+ * </SimpleTooltip>
179
+ * ```
17
180
  */
18
- export function Tooltip(props: TooltipProps): JSX.Element {
181
+ export function SimpleTooltip(props: SimpleTooltipProps): JSX.Element {
19
182
  const position = () => props.position ?? 'bottom'
20
183
 
21
184
  return (
@@ -29,3 +192,6 @@ export function Tooltip(props: TooltipProps): JSX.Element {
29
192
  </div>
30
193
  )
31
194
  }
195
+
196
+ // Re-export types
197
+ export type { TooltipRenderProps }