@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.
- package/dist/components.css +1077 -1077
- package/dist/index.js +236 -249
- package/dist/index.js.map +3 -3
- package/dist/index.ssr.js +78 -81
- package/dist/index.ssr.js.map +3 -3
- package/dist/radio/index.d.ts +12 -27
- package/dist/radio/index.d.ts.map +1 -1
- package/dist/test-utils/index.d.ts +2 -2
- package/dist/test-utils/index.d.ts.map +1 -1
- package/package.json +13 -12
- package/src/alert/index.tsx +48 -0
- package/src/assets/favicon.png +0 -0
- package/src/assets/fire.gif +0 -0
- package/src/autocomplete/index.tsx +313 -0
- package/src/avatar/index.tsx +75 -0
- package/src/badge/index.tsx +43 -0
- package/src/breadcrumbs/index.tsx +207 -0
- package/src/button/Button.tsx +74 -0
- package/src/button/index.ts +2 -0
- package/src/button/types.ts +24 -0
- 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 +257 -0
- package/src/color/index.tsx +687 -0
- package/src/combobox/index.tsx +383 -0
- package/src/components.css +1077 -0
- package/src/custom/calendar-card/index.tsx +66 -0
- package/src/custom/chip/index.tsx +46 -0
- package/src/custom/conversation/index.tsx +105 -0
- package/src/custom/event-card/index.tsx +132 -0
- package/src/custom/header/index.tsx +33 -0
- package/src/custom/lateral-nav/index.tsx +88 -0
- package/src/custom/logo/index.tsx +58 -0
- package/src/custom/nav-header/index.tsx +42 -0
- package/src/custom/page-layout/index.tsx +29 -0
- package/src/custom/profile-card/index.tsx +64 -0
- package/src/custom/project-card/index.tsx +59 -0
- package/src/custom/timeline-item/index.tsx +105 -0
- package/src/dialog/Dialog.tsx +260 -0
- package/src/dialog/index.tsx +3 -0
- package/src/disclosure/index.tsx +307 -0
- package/src/gridlist/index.tsx +403 -0
- package/src/icon/icons/GitHubIcon.tsx +20 -0
- package/src/icon/index.tsx +48 -0
- package/src/index.ts +322 -0
- package/src/landmark/index.tsx +231 -0
- package/src/link/index.tsx +130 -0
- 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/progress-bar/index.tsx +169 -0
- package/src/radio/index.tsx +173 -0
- package/src/searchfield/index.tsx +453 -0
- package/src/select/index.tsx +349 -0
- package/src/separator/index.tsx +141 -0
- package/src/slider/index.tsx +382 -0
- package/src/styles.css +450 -0
- package/src/switch/ToggleSwitch.tsx +112 -0
- package/src/switch/index.tsx +90 -0
- package/src/table/index.tsx +531 -0
- package/src/tabs/index.tsx +273 -0
- package/src/tag-group/index.tsx +240 -0
- package/src/test-utils/index.ts +40 -0
- package/src/textfield/index.tsx +211 -0
- package/src/theme.css +101 -0
- package/src/toast/index.tsx +324 -0
- package/src/toolbar/index.tsx +108 -0
- package/src/tooltip/index.tsx +197 -0
- package/src/tree/index.tsx +494 -0
|
@@ -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 };
|