@proyecto-viviana/ui 0.3.2 → 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 (76) 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
  75. package/dist/index.jsx +0 -6658
  76. package/dist/index.jsx.map +0 -7
@@ -0,0 +1,298 @@
1
+ /**
2
+ * DatePicker component for proyecto-viviana-ui
3
+ *
4
+ * Styled date picker component that combines a date field with a calendar popup.
5
+ */
6
+
7
+ import { type JSX, splitProps, Show } from 'solid-js';
8
+ import {
9
+ DatePicker as HeadlessDatePicker,
10
+ DatePickerButton,
11
+ DateInput,
12
+ DateSegment,
13
+ useDatePickerContext,
14
+ type DatePickerProps as HeadlessDatePickerProps,
15
+ type CalendarDate,
16
+ type DateValue,
17
+ } from '@proyecto-viviana/solidaria-components';
18
+ import { Calendar } from './index';
19
+
20
+ // Calendar icon component - use function to ensure consistent hydration
21
+ function CalendarIcon(): JSX.Element {
22
+ return (
23
+ <svg
24
+ viewBox="0 0 24 24"
25
+ fill="none"
26
+ stroke="currentColor"
27
+ stroke-width="2"
28
+ stroke-linecap="round"
29
+ stroke-linejoin="round"
30
+ class="w-5 h-5"
31
+ aria-hidden="true"
32
+ >
33
+ <rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
34
+ <line x1="16" y1="2" x2="16" y2="6" />
35
+ <line x1="8" y1="2" x2="8" y2="6" />
36
+ <line x1="3" y1="10" x2="21" y2="10" />
37
+ </svg>
38
+ );
39
+ }
40
+
41
+ // ============================================
42
+ // TYPES
43
+ // ============================================
44
+
45
+ export type DatePickerSize = 'sm' | 'md' | 'lg';
46
+
47
+ export interface DatePickerProps<T extends DateValue = DateValue>
48
+ extends Omit<HeadlessDatePickerProps<T>, 'class' | 'style' | 'children'> {
49
+ /** The size of the picker. @default 'md' */
50
+ size?: DatePickerSize;
51
+ /** Additional CSS class name. */
52
+ class?: string;
53
+ /** Label for the field. */
54
+ label?: string;
55
+ /** Description text. */
56
+ description?: string;
57
+ /** Error message. */
58
+ errorMessage?: string;
59
+ /** Placeholder text. */
60
+ placeholder?: string;
61
+ }
62
+
63
+ // ============================================
64
+ // STYLES
65
+ // ============================================
66
+
67
+ const sizeStyles = {
68
+ sm: {
69
+ container: 'text-sm',
70
+ input: 'px-2 py-1 gap-0.5',
71
+ segment: 'px-0.5',
72
+ label: 'text-xs',
73
+ button: 'w-7 h-7',
74
+ },
75
+ md: {
76
+ container: 'text-base',
77
+ input: 'px-3 py-2 gap-1',
78
+ segment: 'px-1',
79
+ label: 'text-sm',
80
+ button: 'w-9 h-9',
81
+ },
82
+ lg: {
83
+ container: 'text-lg',
84
+ input: 'px-4 py-3 gap-1.5',
85
+ segment: 'px-1.5',
86
+ label: 'text-base',
87
+ button: 'w-11 h-11',
88
+ },
89
+ };
90
+
91
+ // ============================================
92
+ // DATE PICKER COMPONENT
93
+ // ============================================
94
+
95
+ /**
96
+ * A date picker combines a date field and a calendar popup.
97
+ *
98
+ * @example
99
+ * ```tsx
100
+ * // Basic usage
101
+ * <DatePicker label="Event date" />
102
+ *
103
+ * // Controlled
104
+ * const [date, setDate] = createSignal<CalendarDate | null>(null);
105
+ * <DatePicker
106
+ * label="Appointment"
107
+ * value={date()}
108
+ * onChange={setDate}
109
+ * />
110
+ *
111
+ * // With constraints
112
+ * <DatePicker
113
+ * label="Future booking"
114
+ * minValue={today(getLocalTimeZone())}
115
+ * />
116
+ * ```
117
+ */
118
+ export function DatePicker<T extends DateValue = CalendarDate>(
119
+ props: DatePickerProps<T>
120
+ ): JSX.Element {
121
+ const [local, rest] = splitProps(props, [
122
+ 'size',
123
+ 'class',
124
+ 'label',
125
+ 'description',
126
+ 'errorMessage',
127
+ 'isInvalid',
128
+ 'placeholder',
129
+ ]);
130
+
131
+ const size = () => local.size ?? 'md';
132
+ const sizeConfig = () => sizeStyles[size()];
133
+ const isInvalid = () => local.isInvalid || !!local.errorMessage;
134
+
135
+ return (
136
+ <HeadlessDatePicker
137
+ {...rest}
138
+ isInvalid={isInvalid()}
139
+ class={`
140
+ flex flex-col gap-1 relative
141
+ ${sizeConfig().container}
142
+ ${local.class ?? ''}
143
+ `}
144
+ >
145
+ {/* Label */}
146
+ {local.label && (
147
+ <label class={`font-medium text-primary-200 ${sizeConfig().label}`}>
148
+ {local.label}
149
+ {rest.isRequired && <span class="text-red-500 ml-0.5">*</span>}
150
+ </label>
151
+ )}
152
+
153
+ {/* Input group */}
154
+ <div class="relative flex items-center">
155
+ {/* Date input */}
156
+ <DateInput
157
+ class={({ isFocused, isDisabled }) => {
158
+ const base = `
159
+ inline-flex items-center flex-1
160
+ ${sizeConfig().input}
161
+ bg-bg-400 rounded-l-md border-y border-l
162
+ transition-colors duration-150
163
+ `;
164
+
165
+ let borderClass = 'border-primary-600';
166
+ if (isInvalid()) {
167
+ borderClass = 'border-red-500';
168
+ } else if (isFocused) {
169
+ borderClass = 'border-accent';
170
+ }
171
+
172
+ const disabledClass = isDisabled
173
+ ? 'opacity-50 cursor-not-allowed'
174
+ : '';
175
+
176
+ const focusClass = isFocused
177
+ ? 'ring-2 ring-accent/30'
178
+ : '';
179
+
180
+ return `${base} ${borderClass} ${disabledClass} ${focusClass}`.trim();
181
+ }}
182
+ >
183
+ {(segment) => (
184
+ <DateSegment
185
+ segment={segment}
186
+ class={({ isFocused, isPlaceholder, isEditable }) => {
187
+ const base = `
188
+ ${sizeConfig().segment}
189
+ rounded
190
+ outline-none
191
+ tabular-nums
192
+ `;
193
+
194
+ let stateClass = '';
195
+ if (segment.type === 'literal') {
196
+ stateClass = 'text-primary-400';
197
+ } else if (isPlaceholder) {
198
+ stateClass = 'text-primary-500 italic';
199
+ } else {
200
+ stateClass = 'text-primary-100';
201
+ }
202
+
203
+ const focusClass = isFocused && isEditable
204
+ ? 'bg-accent text-white'
205
+ : '';
206
+
207
+ return `${base} ${stateClass} ${focusClass}`.trim();
208
+ }}
209
+ />
210
+ )}
211
+ </DateInput>
212
+
213
+ {/* Calendar button */}
214
+ <DatePickerButton
215
+ class={({ isDisabled, isOpen }) => {
216
+ const base = `
217
+ ${sizeConfig().button}
218
+ flex items-center justify-center
219
+ bg-bg-400 border-y border-r rounded-r-md
220
+ text-primary-200
221
+ transition-colors duration-150
222
+ focus:outline-none focus:ring-2 focus:ring-accent/50
223
+ `;
224
+
225
+ let borderClass = 'border-primary-600';
226
+ if (isInvalid()) {
227
+ borderClass = 'border-red-500';
228
+ } else if (isOpen) {
229
+ borderClass = 'border-accent bg-bg-300';
230
+ }
231
+
232
+ const disabledClass = isDisabled
233
+ ? 'opacity-50 cursor-not-allowed'
234
+ : 'hover:bg-bg-300 cursor-pointer';
235
+
236
+ return `${base} ${borderClass} ${disabledClass}`.trim();
237
+ }}
238
+ >
239
+ <CalendarIcon />
240
+ </DatePickerButton>
241
+
242
+ {/* Calendar popup */}
243
+ <DatePickerPopup size={size()} />
244
+ </div>
245
+
246
+ {/* Description */}
247
+ {local.description && !isInvalid() && (
248
+ <p class={`text-primary-400 ${sizeConfig().label}`}>
249
+ {local.description}
250
+ </p>
251
+ )}
252
+
253
+ {/* Error message */}
254
+ {isInvalid() && local.errorMessage && (
255
+ <p class={`text-red-500 ${sizeConfig().label}`}>
256
+ {local.errorMessage}
257
+ </p>
258
+ )}
259
+ </HeadlessDatePicker>
260
+ );
261
+ }
262
+
263
+ // ============================================
264
+ // POPUP COMPONENT (uses context)
265
+ // ============================================
266
+
267
+ function DatePickerPopup(props: { size: DatePickerSize }): JSX.Element {
268
+ const context = useDatePickerContext();
269
+
270
+ return (
271
+ <Show when={context.overlayState.isOpen}>
272
+ <div
273
+ class={`
274
+ absolute top-full left-0 z-50 mt-1
275
+ shadow-lg rounded-lg
276
+ `}
277
+ >
278
+ <Calendar
279
+ value={context.calendarState.value()}
280
+ onChange={(date) => {
281
+ context.fieldState.setValue(date as any);
282
+ context.overlayState.close();
283
+ }}
284
+ minValue={context.calendarState.visibleRange().start}
285
+ size={props.size}
286
+ />
287
+ </div>
288
+ {/* Backdrop */}
289
+ <div
290
+ class="fixed inset-0 z-40"
291
+ onClick={() => context.overlayState.close()}
292
+ />
293
+ </Show>
294
+ );
295
+ }
296
+
297
+ // Re-export types
298
+ export type { CalendarDate, DateValue };
@@ -0,0 +1,236 @@
1
+ /**
2
+ * RangeCalendar component for proyecto-viviana-ui
3
+ *
4
+ * Styled range calendar component built on top of solidaria-components.
5
+ * A range calendar displays a grid of days and allows users to select a date range.
6
+ */
7
+
8
+ import { type JSX, splitProps } from 'solid-js';
9
+ import {
10
+ RangeCalendar as HeadlessRangeCalendar,
11
+ RangeCalendarHeading,
12
+ RangeCalendarButton,
13
+ RangeCalendarGrid,
14
+ RangeCalendarCell,
15
+ type CalendarDate,
16
+ type DateValue,
17
+ type RangeValue,
18
+ } from '@proyecto-viviana/solidaria-components';
19
+ import type { RangeCalendarStateProps } from '@proyecto-viviana/solid-stately';
20
+
21
+ // ============================================
22
+ // TYPES
23
+ // ============================================
24
+
25
+ export type RangeCalendarSize = 'sm' | 'md' | 'lg';
26
+
27
+ export interface RangeCalendarProps<T extends DateValue = DateValue>
28
+ extends Omit<RangeCalendarStateProps<T>, 'locale'> {
29
+ /** The size of the calendar. @default 'md' */
30
+ size?: RangeCalendarSize;
31
+ /** Additional CSS class name. */
32
+ class?: string;
33
+ /** The locale to use for formatting. */
34
+ locale?: string;
35
+ /** Custom aria label. */
36
+ 'aria-label'?: string;
37
+ }
38
+
39
+ // ============================================
40
+ // STYLES
41
+ // ============================================
42
+
43
+ const sizeStyles = {
44
+ sm: {
45
+ container: 'w-64',
46
+ header: 'text-sm',
47
+ cell: 'w-8 h-8 text-xs',
48
+ button: 'w-6 h-6',
49
+ },
50
+ md: {
51
+ container: 'w-80',
52
+ header: 'text-base',
53
+ cell: 'w-10 h-10 text-sm',
54
+ button: 'w-8 h-8',
55
+ },
56
+ lg: {
57
+ container: 'w-96',
58
+ header: 'text-lg',
59
+ cell: 'w-12 h-12 text-base',
60
+ button: 'w-10 h-10',
61
+ },
62
+ };
63
+
64
+ // ============================================
65
+ // RANGE CALENDAR COMPONENT
66
+ // ============================================
67
+
68
+ /**
69
+ * A range calendar displays a grid of days and allows users to select a date range.
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * // Basic usage
74
+ * <RangeCalendar
75
+ * aria-label="Trip dates"
76
+ * onChange={(range) => console.log(range)}
77
+ * />
78
+ *
79
+ * // Controlled
80
+ * const [range, setRange] = createSignal<RangeValue<CalendarDate> | null>(null);
81
+ * <RangeCalendar
82
+ * value={range()}
83
+ * onChange={setRange}
84
+ * />
85
+ * ```
86
+ */
87
+ export function RangeCalendar<T extends DateValue = CalendarDate>(
88
+ props: RangeCalendarProps<T>
89
+ ): JSX.Element {
90
+ const [local, rest] = splitProps(props, [
91
+ 'size',
92
+ 'class',
93
+ 'aria-label',
94
+ ]);
95
+
96
+ const size = () => local.size ?? 'md';
97
+ const sizeConfig = () => sizeStyles[size()];
98
+
99
+ return (
100
+ <HeadlessRangeCalendar
101
+ {...rest}
102
+ aria-label={local['aria-label']}
103
+ class={`
104
+ ${sizeConfig().container}
105
+ bg-bg-500 rounded-lg border border-primary-700 p-4
106
+ ${local.class ?? ''}
107
+ `}
108
+ >
109
+ {/* Header with navigation */}
110
+ <header class="flex items-center justify-between mb-4">
111
+ <RangeCalendarButton
112
+ slot="previous"
113
+ class={`
114
+ ${sizeConfig().button}
115
+ flex items-center justify-center
116
+ rounded-md text-primary-200
117
+ hover:bg-bg-400 transition-colors
118
+ disabled:opacity-50 disabled:cursor-not-allowed
119
+ focus:outline-none focus:ring-2 focus:ring-accent/50
120
+ `}
121
+ >
122
+ <svg
123
+ viewBox="0 0 24 24"
124
+ fill="none"
125
+ stroke="currentColor"
126
+ stroke-width="2"
127
+ stroke-linecap="round"
128
+ stroke-linejoin="round"
129
+ class="w-4 h-4"
130
+ >
131
+ <polyline points="15 18 9 12 15 6" />
132
+ </svg>
133
+ </RangeCalendarButton>
134
+
135
+ <RangeCalendarHeading
136
+ class={`
137
+ font-semibold text-primary-100
138
+ ${sizeConfig().header}
139
+ `}
140
+ />
141
+
142
+ <RangeCalendarButton
143
+ slot="next"
144
+ class={`
145
+ ${sizeConfig().button}
146
+ flex items-center justify-center
147
+ rounded-md text-primary-200
148
+ hover:bg-bg-400 transition-colors
149
+ disabled:opacity-50 disabled:cursor-not-allowed
150
+ focus:outline-none focus:ring-2 focus:ring-accent/50
151
+ `}
152
+ >
153
+ <svg
154
+ viewBox="0 0 24 24"
155
+ fill="none"
156
+ stroke="currentColor"
157
+ stroke-width="2"
158
+ stroke-linecap="round"
159
+ stroke-linejoin="round"
160
+ class="w-4 h-4"
161
+ >
162
+ <polyline points="9 18 15 12 9 6" />
163
+ </svg>
164
+ </RangeCalendarButton>
165
+ </header>
166
+
167
+ {/* Calendar grid */}
168
+ <RangeCalendarGrid
169
+ class="w-full border-collapse"
170
+ >
171
+ {(date) => (
172
+ <RangeCalendarCell
173
+ date={date}
174
+ class={({
175
+ isSelected,
176
+ isSelectionStart,
177
+ isSelectionEnd,
178
+ isFocused,
179
+ isDisabled,
180
+ isOutsideMonth,
181
+ isToday,
182
+ isPressed,
183
+ }) => {
184
+ const base = `
185
+ ${sizeConfig().cell}
186
+ flex items-center justify-center
187
+ cursor-pointer
188
+ transition-colors duration-150
189
+ focus:outline-none
190
+ `;
191
+
192
+ let stateClass = '';
193
+ let roundedClass = 'rounded-md';
194
+
195
+ if (isDisabled) {
196
+ stateClass = 'text-primary-600 cursor-not-allowed';
197
+ } else if (isSelectionStart && isSelectionEnd) {
198
+ // Single day selection
199
+ stateClass = 'bg-accent text-white font-medium';
200
+ roundedClass = 'rounded-md';
201
+ } else if (isSelectionStart) {
202
+ stateClass = 'bg-accent text-white font-medium';
203
+ roundedClass = 'rounded-l-md rounded-r-none';
204
+ } else if (isSelectionEnd) {
205
+ stateClass = 'bg-accent text-white font-medium';
206
+ roundedClass = 'rounded-r-md rounded-l-none';
207
+ } else if (isSelected) {
208
+ stateClass = 'bg-accent/20 text-primary-100';
209
+ roundedClass = 'rounded-none';
210
+ } else if (isOutsideMonth) {
211
+ stateClass = 'text-primary-600';
212
+ } else if (isToday) {
213
+ stateClass = 'ring-1 ring-accent text-primary-100';
214
+ } else {
215
+ stateClass = 'text-primary-200 hover:bg-bg-400';
216
+ }
217
+
218
+ const focusClass = isFocused && !isSelected
219
+ ? 'ring-2 ring-accent/50'
220
+ : '';
221
+
222
+ const pressedClass = isPressed && !isDisabled
223
+ ? 'scale-95'
224
+ : '';
225
+
226
+ return `${base} ${stateClass} ${roundedClass} ${focusClass} ${pressedClass}`.trim();
227
+ }}
228
+ />
229
+ )}
230
+ </RangeCalendarGrid>
231
+ </HeadlessRangeCalendar>
232
+ );
233
+ }
234
+
235
+ // Re-export types
236
+ export type { RangeValue };