@proyecto-viviana/ui 0.3.1 → 0.3.3

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 (74) hide show
  1. package/dist/components.css +1077 -1077
  2. package/dist/index.js +236 -249
  3. package/dist/index.js.map +3 -3
  4. package/dist/index.ssr.js +78 -81
  5. package/dist/index.ssr.js.map +3 -3
  6. package/dist/radio/index.d.ts +12 -27
  7. package/dist/radio/index.d.ts.map +1 -1
  8. package/dist/test-utils/index.d.ts +2 -2
  9. package/dist/test-utils/index.d.ts.map +1 -1
  10. package/package.json +13 -12
  11. package/src/alert/index.tsx +48 -0
  12. package/src/assets/favicon.png +0 -0
  13. package/src/assets/fire.gif +0 -0
  14. package/src/autocomplete/index.tsx +313 -0
  15. package/src/avatar/index.tsx +75 -0
  16. package/src/badge/index.tsx +43 -0
  17. package/src/breadcrumbs/index.tsx +207 -0
  18. package/src/button/Button.tsx +74 -0
  19. package/src/button/index.ts +2 -0
  20. package/src/button/types.ts +24 -0
  21. package/src/calendar/DateField.tsx +200 -0
  22. package/src/calendar/DatePicker.tsx +298 -0
  23. package/src/calendar/RangeCalendar.tsx +236 -0
  24. package/src/calendar/TimeField.tsx +196 -0
  25. package/src/calendar/index.tsx +223 -0
  26. package/src/checkbox/index.tsx +257 -0
  27. package/src/color/index.tsx +687 -0
  28. package/src/combobox/index.tsx +383 -0
  29. package/src/components.css +1077 -0
  30. package/src/custom/calendar-card/index.tsx +66 -0
  31. package/src/custom/chip/index.tsx +46 -0
  32. package/src/custom/conversation/index.tsx +105 -0
  33. package/src/custom/event-card/index.tsx +132 -0
  34. package/src/custom/header/index.tsx +33 -0
  35. package/src/custom/lateral-nav/index.tsx +88 -0
  36. package/src/custom/logo/index.tsx +58 -0
  37. package/src/custom/nav-header/index.tsx +42 -0
  38. package/src/custom/page-layout/index.tsx +29 -0
  39. package/src/custom/profile-card/index.tsx +64 -0
  40. package/src/custom/project-card/index.tsx +59 -0
  41. package/src/custom/timeline-item/index.tsx +105 -0
  42. package/src/dialog/Dialog.tsx +260 -0
  43. package/src/dialog/index.tsx +3 -0
  44. package/src/disclosure/index.tsx +307 -0
  45. package/src/gridlist/index.tsx +403 -0
  46. package/src/icon/icons/GitHubIcon.tsx +20 -0
  47. package/src/icon/index.tsx +48 -0
  48. package/src/index.ts +322 -0
  49. package/src/landmark/index.tsx +231 -0
  50. package/src/link/index.tsx +130 -0
  51. package/src/listbox/index.tsx +231 -0
  52. package/src/menu/index.tsx +297 -0
  53. package/src/meter/index.tsx +163 -0
  54. package/src/numberfield/index.tsx +482 -0
  55. package/src/popover/index.tsx +260 -0
  56. package/src/progress-bar/index.tsx +169 -0
  57. package/src/radio/index.tsx +173 -0
  58. package/src/searchfield/index.tsx +453 -0
  59. package/src/select/index.tsx +349 -0
  60. package/src/separator/index.tsx +141 -0
  61. package/src/slider/index.tsx +382 -0
  62. package/src/styles.css +450 -0
  63. package/src/switch/ToggleSwitch.tsx +112 -0
  64. package/src/switch/index.tsx +90 -0
  65. package/src/table/index.tsx +531 -0
  66. package/src/tabs/index.tsx +273 -0
  67. package/src/tag-group/index.tsx +240 -0
  68. package/src/test-utils/index.ts +40 -0
  69. package/src/textfield/index.tsx +211 -0
  70. package/src/theme.css +101 -0
  71. package/src/toast/index.tsx +324 -0
  72. package/src/toolbar/index.tsx +108 -0
  73. package/src/tooltip/index.tsx +197 -0
  74. package/src/tree/index.tsx +494 -0
@@ -0,0 +1,383 @@
1
+ /**
2
+ * ComboBox component for proyecto-viviana-ui
3
+ *
4
+ * Styled combobox component built on top of solidaria-components.
5
+ * Inspired by Spectrum 2's ComboBox component patterns.
6
+ */
7
+
8
+ import { type JSX, splitProps, createContext, useContext, Show } from 'solid-js'
9
+ import {
10
+ ComboBox as HeadlessComboBox,
11
+ ComboBoxInput as HeadlessComboBoxInput,
12
+ ComboBoxButton as HeadlessComboBoxButton,
13
+ ComboBoxListBox as HeadlessComboBoxListBox,
14
+ ComboBoxOption as HeadlessComboBoxOption,
15
+ defaultContainsFilter,
16
+ type ComboBoxProps as HeadlessComboBoxProps,
17
+ type ComboBoxInputProps as HeadlessComboBoxInputProps,
18
+ type ComboBoxButtonProps as HeadlessComboBoxButtonProps,
19
+ type ComboBoxListBoxProps as HeadlessComboBoxListBoxProps,
20
+ type ComboBoxOptionProps as HeadlessComboBoxOptionProps,
21
+ type ComboBoxRenderProps,
22
+ type ComboBoxInputRenderProps,
23
+ type ComboBoxButtonRenderProps,
24
+ type ComboBoxListBoxRenderProps,
25
+ type ComboBoxOptionRenderProps,
26
+ } from '@proyecto-viviana/solidaria-components'
27
+ import type { Key, FilterFn, MenuTriggerAction } from '@proyecto-viviana/solid-stately'
28
+
29
+ // ============================================
30
+ // SIZE CONTEXT
31
+ // ============================================
32
+
33
+ export type ComboBoxSize = 'sm' | 'md' | 'lg'
34
+
35
+ const ComboBoxSizeContext = createContext<ComboBoxSize>('md')
36
+
37
+ // ============================================
38
+ // TYPES
39
+ // ============================================
40
+
41
+ export interface ComboBoxProps<T> extends Omit<HeadlessComboBoxProps<T>, 'class' | 'style'> {
42
+ /** The size of the combobox. */
43
+ size?: ComboBoxSize
44
+ /** Additional CSS class name. */
45
+ class?: string
46
+ /** Label for the combobox. */
47
+ label?: string
48
+ /** Description for the combobox. */
49
+ description?: string
50
+ /** Error message when invalid. */
51
+ errorMessage?: string
52
+ /** Whether the combobox is invalid. */
53
+ isInvalid?: boolean
54
+ }
55
+
56
+ export interface ComboBoxInputProps extends Omit<HeadlessComboBoxInputProps, 'class' | 'style'> {
57
+ /** Additional CSS class name. */
58
+ class?: string
59
+ }
60
+
61
+ export interface ComboBoxButtonProps extends Omit<HeadlessComboBoxButtonProps, 'class' | 'style'> {
62
+ /** Additional CSS class name. */
63
+ class?: string
64
+ }
65
+
66
+ export interface ComboBoxListBoxProps<T> extends Omit<HeadlessComboBoxListBoxProps<T>, 'class' | 'style'> {
67
+ /** Additional CSS class name. */
68
+ class?: string
69
+ }
70
+
71
+ export interface ComboBoxOptionProps<T> extends Omit<HeadlessComboBoxOptionProps<T>, 'class' | 'style'> {
72
+ /** Additional CSS class name. */
73
+ class?: string
74
+ }
75
+
76
+ // ============================================
77
+ // STYLES
78
+ // ============================================
79
+
80
+ const sizeStyles = {
81
+ sm: {
82
+ wrapper: 'h-8',
83
+ input: 'h-8 text-sm pl-3 pr-8',
84
+ button: 'h-8 w-8',
85
+ label: 'text-sm',
86
+ option: 'text-sm py-1.5 px-3',
87
+ icon: 'h-4 w-4',
88
+ },
89
+ md: {
90
+ wrapper: 'h-10',
91
+ input: 'h-10 text-base pl-4 pr-10',
92
+ button: 'h-10 w-10',
93
+ label: 'text-base',
94
+ option: 'text-base py-2 px-4',
95
+ icon: 'h-5 w-5',
96
+ },
97
+ lg: {
98
+ wrapper: 'h-12',
99
+ input: 'h-12 text-lg pl-5 pr-12',
100
+ button: 'h-12 w-12',
101
+ label: 'text-lg',
102
+ option: 'text-lg py-2.5 px-5',
103
+ icon: 'h-6 w-6',
104
+ },
105
+ }
106
+
107
+ // ============================================
108
+ // COMBOBOX COMPONENT
109
+ // ============================================
110
+
111
+ /**
112
+ * A combobox combines a text input with a listbox, allowing users to filter a list of options.
113
+ *
114
+ * Built on solidaria-components ComboBox for full accessibility support.
115
+ */
116
+ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
117
+ const [local, headlessProps] = splitProps(props, [
118
+ 'size',
119
+ 'class',
120
+ 'label',
121
+ 'description',
122
+ 'errorMessage',
123
+ 'isInvalid',
124
+ ])
125
+
126
+ const size = local.size ?? 'md'
127
+ const customClass = local.class ?? ''
128
+
129
+ const getClassName = (renderProps: ComboBoxRenderProps): string => {
130
+ const base = 'relative inline-flex flex-col gap-1.5'
131
+ const disabledClass = renderProps.isDisabled ? 'opacity-50' : ''
132
+ return [base, disabledClass, customClass].filter(Boolean).join(' ')
133
+ }
134
+
135
+ return (
136
+ <ComboBoxSizeContext.Provider value={size}>
137
+ <HeadlessComboBox
138
+ {...headlessProps}
139
+ class={getClassName}
140
+ >
141
+ <Show when={local.label}>
142
+ <label class={`text-primary-200 font-medium ${sizeStyles[size].label}`}>
143
+ {local.label}
144
+ </label>
145
+ </Show>
146
+ {props.children}
147
+ <Show when={local.description && !local.isInvalid}>
148
+ <span class="text-primary-400 text-sm">{local.description}</span>
149
+ </Show>
150
+ <Show when={local.errorMessage && local.isInvalid}>
151
+ <span class="text-danger-400 text-sm">{local.errorMessage}</span>
152
+ </Show>
153
+ </HeadlessComboBox>
154
+ </ComboBoxSizeContext.Provider>
155
+ )
156
+ }
157
+
158
+ // ============================================
159
+ // COMBOBOX INPUT GROUP COMPONENT
160
+ // ============================================
161
+
162
+ /**
163
+ * A wrapper for the input and button that provides proper styling.
164
+ */
165
+ export function ComboBoxInputGroup(props: { children: JSX.Element; class?: string }): JSX.Element {
166
+ const size = useContext(ComboBoxSizeContext)
167
+ const styles = () => sizeStyles[size]
168
+
169
+ return (
170
+ <div class={`relative flex items-center ${styles().wrapper} ${props.class ?? ''}`}>
171
+ {props.children}
172
+ </div>
173
+ )
174
+ }
175
+
176
+ // ============================================
177
+ // COMBOBOX INPUT COMPONENT
178
+ // ============================================
179
+
180
+ /**
181
+ * The text input for a combobox.
182
+ */
183
+ export function ComboBoxInput(props: ComboBoxInputProps): JSX.Element {
184
+ const [local, headlessProps] = splitProps(props, ['class'])
185
+ const size = useContext(ComboBoxSizeContext)
186
+ const styles = () => sizeStyles[size]
187
+ const customClass = local.class ?? ''
188
+
189
+ const getClassName = (renderProps: ComboBoxInputRenderProps): string => {
190
+ const base = 'w-full rounded-lg border-2 transition-all duration-200 outline-none'
191
+ const sizeClass = styles().input
192
+
193
+ let colorClass: string
194
+ if (renderProps.isDisabled) {
195
+ colorClass = 'border-bg-300 bg-bg-200 text-primary-500 cursor-not-allowed'
196
+ } else if (renderProps.isOpen) {
197
+ colorClass = 'border-accent bg-bg-300 text-primary-100'
198
+ } else if (renderProps.isHovered) {
199
+ colorClass = 'border-accent-300 bg-bg-300 text-primary-100'
200
+ } else {
201
+ colorClass = 'border-primary-600 bg-bg-400 text-primary-200'
202
+ }
203
+
204
+ const focusClass = renderProps.isFocusVisible
205
+ ? 'ring-2 ring-accent-300 ring-offset-2 ring-offset-bg-400'
206
+ : ''
207
+
208
+ return [base, sizeClass, colorClass, focusClass, customClass].filter(Boolean).join(' ')
209
+ }
210
+
211
+ return (
212
+ <HeadlessComboBoxInput
213
+ {...headlessProps}
214
+ class={getClassName}
215
+ />
216
+ )
217
+ }
218
+
219
+ // ============================================
220
+ // COMBOBOX BUTTON COMPONENT
221
+ // ============================================
222
+
223
+ /**
224
+ * The trigger button for a combobox.
225
+ * SSR-compatible - renders children or chevron icon directly without render props.
226
+ */
227
+ export function ComboBoxButton(props: ComboBoxButtonProps): JSX.Element {
228
+ const [local, headlessProps] = splitProps(props, ['class'])
229
+ const size = useContext(ComboBoxSizeContext)
230
+ const sizeStyle = sizeStyles[size]
231
+ const customClass = local.class ?? ''
232
+
233
+ const getClassName = (renderProps: ComboBoxButtonRenderProps): string => {
234
+ const base = 'absolute right-0 top-0 flex items-center justify-center transition-all duration-200 rounded-r-lg'
235
+ const sizeClass = sizeStyle.button
236
+
237
+ let colorClass: string
238
+ if (renderProps.isDisabled) {
239
+ colorClass = 'text-primary-500 cursor-not-allowed'
240
+ } else if (renderProps.isOpen) {
241
+ colorClass = 'text-accent'
242
+ } else if (renderProps.isHovered) {
243
+ colorClass = 'text-accent-300 cursor-pointer'
244
+ } else {
245
+ colorClass = 'text-primary-400 cursor-pointer hover:text-primary-200'
246
+ }
247
+
248
+ return [base, sizeClass, colorClass, customClass].filter(Boolean).join(' ')
249
+ }
250
+
251
+ return (
252
+ <HeadlessComboBoxButton
253
+ {...headlessProps}
254
+ class={getClassName}
255
+ >
256
+ {props.children || <ChevronIcon class={`${sizeStyle.icon} transition-transform duration-200 data-open:rotate-180`} />}
257
+ </HeadlessComboBoxButton>
258
+ )
259
+ }
260
+
261
+ // ============================================
262
+ // COMBOBOX LISTBOX COMPONENT
263
+ // ============================================
264
+
265
+ /**
266
+ * The listbox popup for a combobox.
267
+ */
268
+ export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element {
269
+ const [local, headlessProps] = splitProps(props, ['class'])
270
+ const customClass = local.class ?? ''
271
+
272
+ const getClassName = (_renderProps: ComboBoxListBoxRenderProps): string => {
273
+ const base = 'absolute z-50 mt-1 w-full rounded-lg border-2 border-primary-600 bg-bg-400 py-1 shadow-lg max-h-60 overflow-auto'
274
+ return [base, customClass].filter(Boolean).join(' ')
275
+ }
276
+
277
+ return (
278
+ <HeadlessComboBoxListBox
279
+ {...headlessProps}
280
+ class={getClassName}
281
+ children={props.children}
282
+ />
283
+ )
284
+ }
285
+
286
+ // ============================================
287
+ // COMBOBOX OPTION COMPONENT
288
+ // ============================================
289
+
290
+ /**
291
+ * An option in a combobox listbox.
292
+ * SSR-compatible - renders check icon and content directly without render props.
293
+ */
294
+ export function ComboBoxOption<T>(props: ComboBoxOptionProps<T>): JSX.Element {
295
+ const [local, headlessProps] = splitProps(props, ['class'])
296
+ const size = useContext(ComboBoxSizeContext)
297
+ const sizeStyle = sizeStyles[size]
298
+ const customClass = local.class ?? ''
299
+
300
+ const getClassName = (renderProps: ComboBoxOptionRenderProps): string => {
301
+ const base = 'flex items-center gap-2 cursor-pointer transition-colors duration-150'
302
+ const sizeClass = sizeStyle.option
303
+
304
+ let colorClass: string
305
+ if (renderProps.isDisabled) {
306
+ colorClass = 'text-primary-500 cursor-not-allowed'
307
+ } else if (renderProps.isSelected) {
308
+ colorClass = 'bg-accent/20 text-accent'
309
+ } else if (renderProps.isFocused || renderProps.isHovered) {
310
+ colorClass = 'bg-bg-300 text-primary-100'
311
+ } else {
312
+ colorClass = 'text-primary-200'
313
+ }
314
+
315
+ const focusClass = renderProps.isFocusVisible
316
+ ? 'ring-2 ring-inset ring-accent-300'
317
+ : ''
318
+
319
+ return [base, sizeClass, colorClass, focusClass, customClass].filter(Boolean).join(' ')
320
+ }
321
+
322
+ // Compute padding for non-selected items to align with check icon
323
+ const iconPadding: Record<ComboBoxSize, string> = {
324
+ sm: 'pl-6', // h-4 icon + gap
325
+ md: 'pl-7', // h-5 icon + gap
326
+ lg: 'pl-8', // h-6 icon + gap
327
+ }
328
+
329
+ return (
330
+ <HeadlessComboBoxOption
331
+ {...headlessProps}
332
+ class={getClassName}
333
+ >
334
+ <CheckIcon class={`${sizeStyle.icon} text-accent shrink-0 hidden data-selected:block`} />
335
+ <span class={`flex-1 data-selected:pl-0 ${iconPadding[size]}`}>
336
+ {props.children as JSX.Element}
337
+ </span>
338
+ </HeadlessComboBoxOption>
339
+ )
340
+ }
341
+
342
+ // ============================================
343
+ // ICONS
344
+ // ============================================
345
+
346
+ function ChevronIcon(props: { class?: string }): JSX.Element {
347
+ return (
348
+ <svg
349
+ class={props.class}
350
+ fill="none"
351
+ viewBox="0 0 24 24"
352
+ stroke="currentColor"
353
+ stroke-width="2"
354
+ >
355
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
356
+ </svg>
357
+ )
358
+ }
359
+
360
+ function CheckIcon(props: { class?: string }): JSX.Element {
361
+ return (
362
+ <svg
363
+ class={props.class}
364
+ fill="none"
365
+ viewBox="0 0 24 24"
366
+ stroke="currentColor"
367
+ stroke-width="2"
368
+ >
369
+ <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
370
+ </svg>
371
+ )
372
+ }
373
+
374
+ // Attach sub-components for convenience
375
+ ComboBox.InputGroup = ComboBoxInputGroup
376
+ ComboBox.Input = ComboBoxInput
377
+ ComboBox.Button = ComboBoxButton
378
+ ComboBox.ListBox = ComboBoxListBox
379
+ ComboBox.Option = ComboBoxOption
380
+
381
+ // Re-export types and utilities for convenience
382
+ export type { Key, FilterFn, MenuTriggerAction }
383
+ export { defaultContainsFilter }