@proyecto-viviana/ui 0.1.7 → 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.
- package/README.md +192 -0
- package/dist/autocomplete/index.d.ts +89 -0
- package/dist/autocomplete/index.d.ts.map +1 -0
- package/dist/breadcrumbs/index.d.ts +38 -0
- package/dist/breadcrumbs/index.d.ts.map +1 -0
- package/dist/button/Button.d.ts.map +1 -1
- package/dist/calendar/DateField.d.ts +47 -0
- package/dist/calendar/DateField.d.ts.map +1 -0
- package/dist/calendar/DatePicker.d.ts +48 -0
- package/dist/calendar/DatePicker.d.ts.map +1 -0
- package/dist/calendar/RangeCalendar.d.ts +42 -0
- package/dist/calendar/RangeCalendar.d.ts.map +1 -0
- package/dist/calendar/TimeField.d.ts +44 -0
- package/dist/calendar/TimeField.d.ts.map +1 -0
- package/dist/calendar/index.d.ts +50 -0
- package/dist/calendar/index.d.ts.map +1 -0
- package/dist/checkbox/index.d.ts.map +1 -1
- package/dist/color/index.d.ts +228 -0
- package/dist/color/index.d.ts.map +1 -0
- package/dist/combobox/index.d.ts +81 -0
- package/dist/combobox/index.d.ts.map +1 -0
- package/dist/components.css +116 -14
- package/dist/custom/chip/index.d.ts +7 -2
- package/dist/custom/chip/index.d.ts.map +1 -1
- package/dist/custom/event-card/index.d.ts +5 -1
- package/dist/custom/event-card/index.d.ts.map +1 -1
- package/dist/custom/header/index.d.ts +16 -0
- package/dist/custom/header/index.d.ts.map +1 -0
- package/dist/custom/logo/index.d.ts +2 -0
- package/dist/custom/logo/index.d.ts.map +1 -1
- package/dist/custom/page-layout/index.d.ts +2 -0
- package/dist/custom/page-layout/index.d.ts.map +1 -1
- package/dist/custom/profile-card/index.d.ts +5 -1
- package/dist/custom/profile-card/index.d.ts.map +1 -1
- package/dist/custom/timeline-item/index.d.ts +12 -2
- package/dist/custom/timeline-item/index.d.ts.map +1 -1
- package/dist/dialog/Dialog.d.ts +67 -0
- package/dist/dialog/Dialog.d.ts.map +1 -0
- package/dist/dialog/index.d.ts +2 -17
- package/dist/dialog/index.d.ts.map +1 -1
- package/dist/disclosure/index.d.ts +84 -0
- package/dist/disclosure/index.d.ts.map +1 -0
- package/dist/gridlist/index.d.ts +92 -0
- package/dist/gridlist/index.d.ts.map +1 -0
- package/dist/index.d.ts +58 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6984 -783
- package/dist/index.js.map +1 -1
- package/dist/index.ssr.js +5905 -571
- package/dist/index.ssr.js.map +1 -1
- package/dist/landmark/index.d.ts +83 -0
- package/dist/landmark/index.d.ts.map +1 -0
- package/dist/link/index.d.ts.map +1 -1
- package/dist/listbox/index.d.ts +47 -0
- package/dist/listbox/index.d.ts.map +1 -0
- package/dist/menu/index.d.ts +74 -0
- package/dist/menu/index.d.ts.map +1 -0
- package/dist/meter/index.d.ts +49 -0
- package/dist/meter/index.d.ts.map +1 -0
- package/dist/numberfield/index.d.ts +50 -0
- package/dist/numberfield/index.d.ts.map +1 -0
- package/dist/popover/index.d.ts +85 -0
- package/dist/popover/index.d.ts.map +1 -0
- package/dist/radio/index.d.ts +7 -4
- package/dist/radio/index.d.ts.map +1 -1
- package/dist/searchfield/index.d.ts +44 -0
- package/dist/searchfield/index.d.ts.map +1 -0
- package/dist/select/index.d.ts +72 -0
- package/dist/select/index.d.ts.map +1 -0
- package/dist/slider/index.d.ts +53 -0
- package/dist/slider/index.d.ts.map +1 -0
- package/dist/switch/ToggleSwitch.d.ts.map +1 -1
- package/dist/table/index.d.ts +140 -0
- package/dist/table/index.d.ts.map +1 -0
- package/dist/tabs/index.d.ts +56 -0
- package/dist/tabs/index.d.ts.map +1 -0
- package/dist/tag-group/index.d.ts +80 -0
- package/dist/tag-group/index.d.ts.map +1 -0
- package/dist/toast/index.d.ts +101 -0
- package/dist/toast/index.d.ts.map +1 -0
- package/dist/toolbar/index.d.ts +42 -0
- package/dist/toolbar/index.d.ts.map +1 -0
- package/dist/tooltip/index.d.ts +66 -5
- package/dist/tooltip/index.d.ts.map +1 -1
- package/dist/tree/index.d.ts +99 -0
- package/dist/tree/index.d.ts.map +1 -0
- package/package.json +66 -58
- package/src/autocomplete/index.tsx +313 -0
- package/src/breadcrumbs/index.tsx +207 -0
- package/src/button/Button.tsx +74 -75
- package/src/calendar/DateField.tsx +200 -0
- package/src/calendar/DatePicker.tsx +298 -0
- package/src/calendar/RangeCalendar.tsx +236 -0
- package/src/calendar/TimeField.tsx +196 -0
- package/src/calendar/index.tsx +223 -0
- package/src/checkbox/index.tsx +3 -4
- package/src/color/index.tsx +687 -0
- package/src/combobox/index.tsx +383 -0
- package/src/components.css +116 -14
- package/src/custom/chip/index.tsx +17 -3
- package/src/custom/event-card/index.tsx +8 -2
- package/src/custom/header/index.tsx +33 -0
- package/src/custom/logo/index.tsx +7 -3
- package/src/custom/page-layout/index.tsx +12 -3
- package/src/custom/profile-card/index.tsx +8 -2
- package/src/custom/timeline-item/index.tsx +28 -4
- package/src/dialog/Dialog.tsx +260 -0
- package/src/dialog/index.tsx +3 -69
- package/src/disclosure/index.tsx +307 -0
- package/src/gridlist/index.tsx +403 -0
- package/src/index.ts +219 -4
- package/src/landmark/index.tsx +231 -0
- package/src/link/index.tsx +1 -2
- package/src/listbox/index.tsx +231 -0
- package/src/menu/index.tsx +297 -0
- package/src/meter/index.tsx +163 -0
- package/src/numberfield/index.tsx +482 -0
- package/src/popover/index.tsx +260 -0
- package/src/radio/index.tsx +36 -82
- package/src/searchfield/index.tsx +453 -0
- package/src/select/index.tsx +349 -0
- package/src/slider/index.tsx +382 -0
- package/src/switch/ToggleSwitch.tsx +1 -2
- package/src/table/index.tsx +531 -0
- package/src/tabs/index.tsx +273 -0
- package/src/tag-group/index.tsx +240 -0
- package/src/toast/index.tsx +324 -0
- package/src/toolbar/index.tsx +108 -0
- package/src/tooltip/index.tsx +171 -5
- package/src/tree/index.tsx +494 -0
|
@@ -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 };
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TimeField component for proyecto-viviana-ui
|
|
3
|
+
*
|
|
4
|
+
* Styled time field component with segment-based editing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { type JSX, splitProps } from 'solid-js';
|
|
8
|
+
import {
|
|
9
|
+
TimeField as HeadlessTimeField,
|
|
10
|
+
TimeInput,
|
|
11
|
+
TimeSegment,
|
|
12
|
+
type TimeFieldProps as HeadlessTimeFieldProps,
|
|
13
|
+
type TimeValue,
|
|
14
|
+
} from '@proyecto-viviana/solidaria-components';
|
|
15
|
+
|
|
16
|
+
// ============================================
|
|
17
|
+
// TYPES
|
|
18
|
+
// ============================================
|
|
19
|
+
|
|
20
|
+
export type TimeFieldSize = 'sm' | 'md' | 'lg';
|
|
21
|
+
|
|
22
|
+
export interface TimeFieldProps<T extends TimeValue = TimeValue>
|
|
23
|
+
extends Omit<HeadlessTimeFieldProps<T>, 'class' | 'style' | 'children'> {
|
|
24
|
+
/** The size of the field. @default 'md' */
|
|
25
|
+
size?: TimeFieldSize;
|
|
26
|
+
/** Additional CSS class name. */
|
|
27
|
+
class?: string;
|
|
28
|
+
/** Label for the field. */
|
|
29
|
+
label?: string;
|
|
30
|
+
/** Description text. */
|
|
31
|
+
description?: string;
|
|
32
|
+
/** Error message. */
|
|
33
|
+
errorMessage?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// STYLES
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
const sizeStyles = {
|
|
41
|
+
sm: {
|
|
42
|
+
container: 'text-sm',
|
|
43
|
+
input: 'px-2 py-1 gap-0.5',
|
|
44
|
+
segment: 'px-0.5',
|
|
45
|
+
label: 'text-xs',
|
|
46
|
+
},
|
|
47
|
+
md: {
|
|
48
|
+
container: 'text-base',
|
|
49
|
+
input: 'px-3 py-2 gap-1',
|
|
50
|
+
segment: 'px-1',
|
|
51
|
+
label: 'text-sm',
|
|
52
|
+
},
|
|
53
|
+
lg: {
|
|
54
|
+
container: 'text-lg',
|
|
55
|
+
input: 'px-4 py-3 gap-1.5',
|
|
56
|
+
segment: 'px-1.5',
|
|
57
|
+
label: 'text-base',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// ============================================
|
|
62
|
+
// TIME FIELD COMPONENT
|
|
63
|
+
// ============================================
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A time field allows users to enter and edit time values using a keyboard.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* // Basic usage
|
|
71
|
+
* <TimeField label="Start time" />
|
|
72
|
+
*
|
|
73
|
+
* // With 24-hour format
|
|
74
|
+
* <TimeField
|
|
75
|
+
* label="Meeting time"
|
|
76
|
+
* hourCycle={24}
|
|
77
|
+
* />
|
|
78
|
+
*
|
|
79
|
+
* // With seconds
|
|
80
|
+
* <TimeField
|
|
81
|
+
* label="Precise time"
|
|
82
|
+
* granularity="second"
|
|
83
|
+
* />
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export function TimeField<T extends TimeValue = TimeValue>(
|
|
87
|
+
props: TimeFieldProps<T>
|
|
88
|
+
): JSX.Element {
|
|
89
|
+
const [local, rest] = splitProps(props, [
|
|
90
|
+
'size',
|
|
91
|
+
'class',
|
|
92
|
+
'label',
|
|
93
|
+
'description',
|
|
94
|
+
'errorMessage',
|
|
95
|
+
'isInvalid',
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
const size = () => local.size ?? 'md';
|
|
99
|
+
const sizeConfig = () => sizeStyles[size()];
|
|
100
|
+
const isInvalid = () => local.isInvalid || !!local.errorMessage;
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<HeadlessTimeField
|
|
104
|
+
{...rest}
|
|
105
|
+
isInvalid={isInvalid()}
|
|
106
|
+
class={`
|
|
107
|
+
flex flex-col gap-1
|
|
108
|
+
${sizeConfig().container}
|
|
109
|
+
${local.class ?? ''}
|
|
110
|
+
`}
|
|
111
|
+
>
|
|
112
|
+
{/* Label */}
|
|
113
|
+
{local.label && (
|
|
114
|
+
<label class={`font-medium text-primary-200 ${sizeConfig().label}`}>
|
|
115
|
+
{local.label}
|
|
116
|
+
{rest.isRequired && <span class="text-red-500 ml-0.5">*</span>}
|
|
117
|
+
</label>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
{/* Input container */}
|
|
121
|
+
<TimeInput
|
|
122
|
+
class={({ isFocused, isDisabled }) => {
|
|
123
|
+
const base = `
|
|
124
|
+
inline-flex items-center
|
|
125
|
+
${sizeConfig().input}
|
|
126
|
+
bg-bg-400 rounded-md border
|
|
127
|
+
transition-colors duration-150
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
let borderClass = 'border-primary-600';
|
|
131
|
+
if (isInvalid()) {
|
|
132
|
+
borderClass = 'border-red-500';
|
|
133
|
+
} else if (isFocused) {
|
|
134
|
+
borderClass = 'border-accent';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const disabledClass = isDisabled
|
|
138
|
+
? 'opacity-50 cursor-not-allowed'
|
|
139
|
+
: '';
|
|
140
|
+
|
|
141
|
+
const focusClass = isFocused
|
|
142
|
+
? 'ring-2 ring-accent/30'
|
|
143
|
+
: '';
|
|
144
|
+
|
|
145
|
+
return `${base} ${borderClass} ${disabledClass} ${focusClass}`.trim();
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
{(segment) => (
|
|
149
|
+
<TimeSegment
|
|
150
|
+
segment={segment}
|
|
151
|
+
class={({ isFocused, isPlaceholder, isEditable }) => {
|
|
152
|
+
const base = `
|
|
153
|
+
${sizeConfig().segment}
|
|
154
|
+
rounded
|
|
155
|
+
outline-none
|
|
156
|
+
tabular-nums
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
let stateClass = '';
|
|
160
|
+
if (segment.type === 'literal') {
|
|
161
|
+
stateClass = 'text-primary-400';
|
|
162
|
+
} else if (isPlaceholder) {
|
|
163
|
+
stateClass = 'text-primary-500 italic';
|
|
164
|
+
} else {
|
|
165
|
+
stateClass = 'text-primary-100';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const focusClass = isFocused && isEditable
|
|
169
|
+
? 'bg-accent text-white'
|
|
170
|
+
: '';
|
|
171
|
+
|
|
172
|
+
return `${base} ${stateClass} ${focusClass}`.trim();
|
|
173
|
+
}}
|
|
174
|
+
/>
|
|
175
|
+
)}
|
|
176
|
+
</TimeInput>
|
|
177
|
+
|
|
178
|
+
{/* Description */}
|
|
179
|
+
{local.description && !isInvalid() && (
|
|
180
|
+
<p class={`text-primary-400 ${sizeConfig().label}`}>
|
|
181
|
+
{local.description}
|
|
182
|
+
</p>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
{/* Error message */}
|
|
186
|
+
{isInvalid() && local.errorMessage && (
|
|
187
|
+
<p class={`text-red-500 ${sizeConfig().label}`}>
|
|
188
|
+
{local.errorMessage}
|
|
189
|
+
</p>
|
|
190
|
+
)}
|
|
191
|
+
</HeadlessTimeField>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Re-export types
|
|
196
|
+
export type { TimeValue };
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar component for proyecto-viviana-ui
|
|
3
|
+
*
|
|
4
|
+
* Styled calendar component built on top of solidaria-components.
|
|
5
|
+
* A calendar displays a grid of days and allows users to select dates.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type JSX, splitProps } from 'solid-js';
|
|
9
|
+
import {
|
|
10
|
+
Calendar as HeadlessCalendar,
|
|
11
|
+
CalendarHeading,
|
|
12
|
+
CalendarButton,
|
|
13
|
+
CalendarGrid,
|
|
14
|
+
CalendarCell,
|
|
15
|
+
type CalendarDate,
|
|
16
|
+
type DateValue,
|
|
17
|
+
} from '@proyecto-viviana/solidaria-components';
|
|
18
|
+
import type { CalendarStateProps } from '@proyecto-viviana/solid-stately';
|
|
19
|
+
|
|
20
|
+
// ============================================
|
|
21
|
+
// TYPES
|
|
22
|
+
// ============================================
|
|
23
|
+
|
|
24
|
+
export type CalendarSize = 'sm' | 'md' | 'lg';
|
|
25
|
+
|
|
26
|
+
export interface CalendarProps<T extends DateValue = DateValue>
|
|
27
|
+
extends Omit<CalendarStateProps<T>, 'locale'> {
|
|
28
|
+
/** The size of the calendar. @default 'md' */
|
|
29
|
+
size?: CalendarSize;
|
|
30
|
+
/** Additional CSS class name. */
|
|
31
|
+
class?: string;
|
|
32
|
+
/** Whether to show week numbers. */
|
|
33
|
+
showWeekNumbers?: boolean;
|
|
34
|
+
/** The locale to use for formatting. */
|
|
35
|
+
locale?: string;
|
|
36
|
+
/** Custom aria label. */
|
|
37
|
+
'aria-label'?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ============================================
|
|
41
|
+
// STYLES
|
|
42
|
+
// ============================================
|
|
43
|
+
|
|
44
|
+
const sizeStyles = {
|
|
45
|
+
sm: {
|
|
46
|
+
container: 'w-64',
|
|
47
|
+
header: 'text-sm',
|
|
48
|
+
cell: 'w-8 h-8 text-xs',
|
|
49
|
+
button: 'w-6 h-6',
|
|
50
|
+
},
|
|
51
|
+
md: {
|
|
52
|
+
container: 'w-80',
|
|
53
|
+
header: 'text-base',
|
|
54
|
+
cell: 'w-10 h-10 text-sm',
|
|
55
|
+
button: 'w-8 h-8',
|
|
56
|
+
},
|
|
57
|
+
lg: {
|
|
58
|
+
container: 'w-96',
|
|
59
|
+
header: 'text-lg',
|
|
60
|
+
cell: 'w-12 h-12 text-base',
|
|
61
|
+
button: 'w-10 h-10',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// ============================================
|
|
66
|
+
// CALENDAR COMPONENT
|
|
67
|
+
// ============================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A calendar displays a grid of days and allows users to select a date.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```tsx
|
|
74
|
+
* // Basic usage
|
|
75
|
+
* <Calendar
|
|
76
|
+
* aria-label="Event date"
|
|
77
|
+
* onChange={(date) => console.log(date)}
|
|
78
|
+
* />
|
|
79
|
+
*
|
|
80
|
+
* // Controlled
|
|
81
|
+
* const [date, setDate] = createSignal<CalendarDate | null>(null);
|
|
82
|
+
* <Calendar
|
|
83
|
+
* value={date()}
|
|
84
|
+
* onChange={setDate}
|
|
85
|
+
* />
|
|
86
|
+
*
|
|
87
|
+
* // With min/max dates
|
|
88
|
+
* <Calendar
|
|
89
|
+
* minValue={today(getLocalTimeZone())}
|
|
90
|
+
* maxValue={today(getLocalTimeZone()).add({ months: 3 })}
|
|
91
|
+
* />
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export function Calendar<T extends DateValue = CalendarDate>(
|
|
95
|
+
props: CalendarProps<T>
|
|
96
|
+
): JSX.Element {
|
|
97
|
+
const [local, rest] = splitProps(props, [
|
|
98
|
+
'size',
|
|
99
|
+
'class',
|
|
100
|
+
'showWeekNumbers',
|
|
101
|
+
'aria-label',
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
const size = () => local.size ?? 'md';
|
|
105
|
+
const sizeConfig = () => sizeStyles[size()];
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<HeadlessCalendar
|
|
109
|
+
{...rest}
|
|
110
|
+
aria-label={local['aria-label']}
|
|
111
|
+
class={`
|
|
112
|
+
${sizeConfig().container}
|
|
113
|
+
bg-bg-500 rounded-lg border border-primary-700 p-4
|
|
114
|
+
${local.class ?? ''}
|
|
115
|
+
`}
|
|
116
|
+
>
|
|
117
|
+
{/* Header with navigation */}
|
|
118
|
+
<header class="flex items-center justify-between mb-4">
|
|
119
|
+
<CalendarButton
|
|
120
|
+
slot="previous"
|
|
121
|
+
class={`
|
|
122
|
+
${sizeConfig().button}
|
|
123
|
+
flex items-center justify-center
|
|
124
|
+
rounded-md text-primary-200
|
|
125
|
+
hover:bg-bg-400 transition-colors
|
|
126
|
+
disabled:opacity-50 disabled:cursor-not-allowed
|
|
127
|
+
focus:outline-none focus:ring-2 focus:ring-accent/50
|
|
128
|
+
`}
|
|
129
|
+
>
|
|
130
|
+
<svg
|
|
131
|
+
viewBox="0 0 24 24"
|
|
132
|
+
fill="none"
|
|
133
|
+
stroke="currentColor"
|
|
134
|
+
stroke-width="2"
|
|
135
|
+
stroke-linecap="round"
|
|
136
|
+
stroke-linejoin="round"
|
|
137
|
+
class="w-4 h-4"
|
|
138
|
+
>
|
|
139
|
+
<polyline points="15 18 9 12 15 6" />
|
|
140
|
+
</svg>
|
|
141
|
+
</CalendarButton>
|
|
142
|
+
|
|
143
|
+
<CalendarHeading
|
|
144
|
+
class={`
|
|
145
|
+
font-semibold text-primary-100
|
|
146
|
+
${sizeConfig().header}
|
|
147
|
+
`}
|
|
148
|
+
/>
|
|
149
|
+
|
|
150
|
+
<CalendarButton
|
|
151
|
+
slot="next"
|
|
152
|
+
class={`
|
|
153
|
+
${sizeConfig().button}
|
|
154
|
+
flex items-center justify-center
|
|
155
|
+
rounded-md text-primary-200
|
|
156
|
+
hover:bg-bg-400 transition-colors
|
|
157
|
+
disabled:opacity-50 disabled:cursor-not-allowed
|
|
158
|
+
focus:outline-none focus:ring-2 focus:ring-accent/50
|
|
159
|
+
`}
|
|
160
|
+
>
|
|
161
|
+
<svg
|
|
162
|
+
viewBox="0 0 24 24"
|
|
163
|
+
fill="none"
|
|
164
|
+
stroke="currentColor"
|
|
165
|
+
stroke-width="2"
|
|
166
|
+
stroke-linecap="round"
|
|
167
|
+
stroke-linejoin="round"
|
|
168
|
+
class="w-4 h-4"
|
|
169
|
+
>
|
|
170
|
+
<polyline points="9 18 15 12 9 6" />
|
|
171
|
+
</svg>
|
|
172
|
+
</CalendarButton>
|
|
173
|
+
</header>
|
|
174
|
+
|
|
175
|
+
{/* Calendar grid */}
|
|
176
|
+
<CalendarGrid
|
|
177
|
+
class="w-full border-collapse"
|
|
178
|
+
>
|
|
179
|
+
{(date) => (
|
|
180
|
+
<CalendarCell
|
|
181
|
+
date={date}
|
|
182
|
+
class={({ isSelected, isFocused, isDisabled, isOutsideMonth, isToday, isPressed }) => {
|
|
183
|
+
const base = `
|
|
184
|
+
${sizeConfig().cell}
|
|
185
|
+
flex items-center justify-center
|
|
186
|
+
rounded-md cursor-pointer
|
|
187
|
+
transition-colors duration-150
|
|
188
|
+
focus:outline-none
|
|
189
|
+
`;
|
|
190
|
+
|
|
191
|
+
let stateClass = '';
|
|
192
|
+
|
|
193
|
+
if (isDisabled) {
|
|
194
|
+
stateClass = 'text-primary-600 cursor-not-allowed';
|
|
195
|
+
} else if (isSelected) {
|
|
196
|
+
stateClass = 'bg-accent text-white font-medium';
|
|
197
|
+
} else if (isOutsideMonth) {
|
|
198
|
+
stateClass = 'text-primary-600';
|
|
199
|
+
} else if (isToday) {
|
|
200
|
+
stateClass = 'ring-1 ring-accent text-primary-100';
|
|
201
|
+
} else {
|
|
202
|
+
stateClass = 'text-primary-200 hover:bg-bg-400';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const focusClass = isFocused && !isSelected
|
|
206
|
+
? 'ring-2 ring-accent/50'
|
|
207
|
+
: '';
|
|
208
|
+
|
|
209
|
+
const pressedClass = isPressed && !isDisabled
|
|
210
|
+
? 'scale-95'
|
|
211
|
+
: '';
|
|
212
|
+
|
|
213
|
+
return `${base} ${stateClass} ${focusClass} ${pressedClass}`.trim();
|
|
214
|
+
}}
|
|
215
|
+
/>
|
|
216
|
+
)}
|
|
217
|
+
</CalendarGrid>
|
|
218
|
+
</HeadlessCalendar>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Re-export types
|
|
223
|
+
export type { CalendarDate, DateValue };
|
package/src/checkbox/index.tsx
CHANGED
|
@@ -194,8 +194,8 @@ export function Checkbox(props: CheckboxProps): JSX.Element {
|
|
|
194
194
|
<CheckIcon class={iconClasses()} />
|
|
195
195
|
</Show>
|
|
196
196
|
</span>
|
|
197
|
-
<Show when={
|
|
198
|
-
<span class={labelClasses()}>{
|
|
197
|
+
<Show when={props.children}>
|
|
198
|
+
<span class={labelClasses()}>{props.children}</span>
|
|
199
199
|
</Show>
|
|
200
200
|
</>
|
|
201
201
|
)
|
|
@@ -216,7 +216,6 @@ export function Checkbox(props: CheckboxProps): JSX.Element {
|
|
|
216
216
|
export function CheckboxGroup(props: CheckboxGroupProps): JSX.Element {
|
|
217
217
|
const [local, headlessProps] = splitProps(props, [
|
|
218
218
|
'class',
|
|
219
|
-
'children',
|
|
220
219
|
'label',
|
|
221
220
|
'description',
|
|
222
221
|
'errorMessage',
|
|
@@ -237,7 +236,7 @@ export function CheckboxGroup(props: CheckboxGroupProps): JSX.Element {
|
|
|
237
236
|
<span class="text-sm font-medium text-primary-200">{local.label}</span>
|
|
238
237
|
</Show>
|
|
239
238
|
<div class="flex flex-col gap-2">
|
|
240
|
-
{
|
|
239
|
+
{props.children}
|
|
241
240
|
</div>
|
|
242
241
|
<Show when={local.description && !renderProps.isInvalid}>
|
|
243
242
|
<span class="text-sm text-primary-400">{local.description}</span>
|