@axzydev/axzy_ui_system 1.2.0 → 1.2.2
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/index.css +82 -197
- package/dist/index.css.map +1 -1
- package/package.json +2 -2
- package/src/App.tsx +354 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/components/alert/alert.props.ts +13 -0
- package/src/components/alert/alert.stories.tsx +41 -0
- package/src/components/alert/alert.tsx +53 -0
- package/src/components/avatar/avatar.props.ts +14 -0
- package/src/components/avatar/avatar.stories.tsx +46 -0
- package/src/components/avatar/avatar.tsx +53 -0
- package/src/components/badget/badget.props.ts +12 -0
- package/src/components/badget/badget.stories.tsx +76 -0
- package/src/components/badget/badget.tsx +61 -0
- package/src/components/breadcrumbs/breadcrumbs.props.ts +13 -0
- package/src/components/breadcrumbs/breadcrumbs.stories.tsx +21 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +34 -0
- package/src/components/button/button.props.ts +18 -0
- package/src/components/button/button.stories.tsx +174 -0
- package/src/components/button/button.tsx +117 -0
- package/src/components/calendar/calendar.props.ts +33 -0
- package/src/components/calendar/calendar.stories.tsx +91 -0
- package/src/components/calendar/calendar.tsx +608 -0
- package/src/components/calendar/index.ts +3 -0
- package/src/components/card/card.props.ts +13 -0
- package/src/components/card/card.stories.tsx +58 -0
- package/src/components/card/card.tsx +79 -0
- package/src/components/checkbox/checkbox.props.ts +11 -0
- package/src/components/checkbox/checkbox.stories.tsx +54 -0
- package/src/components/checkbox/checkbox.tsx +52 -0
- package/src/components/confirm-dialog/confirm-dialog.props.ts +14 -0
- package/src/components/confirm-dialog/confirm-dialog.stories.tsx +33 -0
- package/src/components/confirm-dialog/confirm-dialog.tsx +45 -0
- package/src/components/data-table/ITDataTable.stories.tsx +213 -0
- package/src/components/data-table/dataTable.props.ts +69 -0
- package/src/components/data-table/dataTable.tsx +313 -0
- package/src/components/date-picker/date-picker.props.ts +30 -0
- package/src/components/date-picker/date-picker.stories.tsx +90 -0
- package/src/components/date-picker/datePicker.tsx +307 -0
- package/src/components/dialog/dialog.props.ts +9 -0
- package/src/components/dialog/dialog.stories.tsx +80 -0
- package/src/components/dialog/dialog.tsx +88 -0
- package/src/components/divider/divider.props.ts +8 -0
- package/src/components/divider/divider.stories.tsx +34 -0
- package/src/components/divider/divider.tsx +21 -0
- package/src/components/drawer/drawer.props.ts +14 -0
- package/src/components/drawer/drawer.stories.tsx +41 -0
- package/src/components/drawer/drawer.tsx +53 -0
- package/src/components/dropfile/dropfile.stories.tsx +75 -0
- package/src/components/dropfile/dropfile.tsx +407 -0
- package/src/components/empty-state/empty-state.props.ts +9 -0
- package/src/components/empty-state/empty-state.stories.tsx +20 -0
- package/src/components/empty-state/empty-state.tsx +21 -0
- package/src/components/flex/flex.props.ts +22 -0
- package/src/components/flex/flex.stories.tsx +71 -0
- package/src/components/flex/flex.tsx +79 -0
- package/src/components/form-builder/fieldRenderer.tsx +218 -0
- package/src/components/form-builder/formBuilder.context.tsx +70 -0
- package/src/components/form-builder/formBuilder.props.ts +43 -0
- package/src/components/form-builder/formBuilder.stories.tsx +317 -0
- package/src/components/form-builder/formBuilder.tsx +186 -0
- package/src/components/form-builder/useFormBuilder.ts +80 -0
- package/src/components/form-header/form-header.props.ts +5 -0
- package/src/components/form-header/form-header.tsx +38 -0
- package/src/components/grid/grid.props.ts +17 -0
- package/src/components/grid/grid.stories.tsx +72 -0
- package/src/components/grid/grid.tsx +69 -0
- package/src/components/image/image.props.ts +7 -0
- package/src/components/image/image.tsx +38 -0
- package/src/components/input/input.props.ts +49 -0
- package/src/components/input/input.stories.tsx +115 -0
- package/src/components/input/input.tsx +615 -0
- package/src/components/layout/layout.props.ts +10 -0
- package/src/components/layout/layout.stories.tsx +114 -0
- package/src/components/layout/layout.tsx +80 -0
- package/src/components/loader/loader.props.ts +8 -0
- package/src/components/loader/loader.stories.tsx +105 -0
- package/src/components/loader/loader.tsx +108 -0
- package/src/components/navbar/navbar.props.ts +37 -0
- package/src/components/navbar/navbar.tsx +328 -0
- package/src/components/page/page.props.ts +19 -0
- package/src/components/page/page.stories.tsx +98 -0
- package/src/components/page/page.tsx +90 -0
- package/src/components/page-header/page-header.props.ts +11 -0
- package/src/components/page-header/page-header.stories.tsx +61 -0
- package/src/components/page-header/page-header.tsx +62 -0
- package/src/components/pagination/pagination.props.ts +53 -0
- package/src/components/pagination/pagination.stories.tsx +111 -0
- package/src/components/pagination/pagination.tsx +241 -0
- package/src/components/popover/popover.props.ts +12 -0
- package/src/components/popover/popover.stories.tsx +25 -0
- package/src/components/popover/popover.tsx +45 -0
- package/src/components/progress/progress.props.ts +12 -0
- package/src/components/progress/progress.stories.tsx +40 -0
- package/src/components/progress/progress.tsx +52 -0
- package/src/components/radio/radio.props.ts +16 -0
- package/src/components/radio/radio.stories.tsx +50 -0
- package/src/components/radio/radio.tsx +58 -0
- package/src/components/search-select/index.ts +2 -0
- package/src/components/search-select/search-select.props.ts +46 -0
- package/src/components/search-select/search-select.stories.tsx +129 -0
- package/src/components/search-select/search-select.tsx +229 -0
- package/src/components/searchTable/components/EditableCell.tsx +149 -0
- package/src/components/searchTable/components/PaginationControls.tsx +86 -0
- package/src/components/searchTable/components/PaginationInfo.tsx +20 -0
- package/src/components/searchTable/components/SearchAndSortBar.tsx +53 -0
- package/src/components/searchTable/components/SearchInput.tsx +33 -0
- package/src/components/searchTable/components/SortButton.tsx +50 -0
- package/src/components/searchTable/components/TableEmptyState.tsx +22 -0
- package/src/components/searchTable/components/TableHeader.tsx +35 -0
- package/src/components/searchTable/components/TableHeaderCell.tsx +43 -0
- package/src/components/searchTable/components/TableRow.tsx +144 -0
- package/src/components/searchTable/searchTable.props.ts +56 -0
- package/src/components/searchTable/searchTable.tsx +187 -0
- package/src/components/segmented-control/segmented-control.props.ts +18 -0
- package/src/components/segmented-control/segmented-control.stories.tsx +63 -0
- package/src/components/segmented-control/segmented-control.tsx +52 -0
- package/src/components/select/select.props.ts +25 -0
- package/src/components/select/select.stories.tsx +86 -0
- package/src/components/select/select.tsx +150 -0
- package/src/components/sidebar/sidebar.props.ts +28 -0
- package/src/components/sidebar/sidebar.stories.tsx +117 -0
- package/src/components/sidebar/sidebar.tsx +313 -0
- package/src/components/skeleton/skeleton.props.ts +12 -0
- package/src/components/skeleton/skeleton.stories.tsx +30 -0
- package/src/components/skeleton/skeleton.tsx +45 -0
- package/src/components/slide/slide.props.ts +45 -0
- package/src/components/slide/slide.stories.tsx +121 -0
- package/src/components/slide/slide.tsx +109 -0
- package/src/components/slider/slider.props.ts +10 -0
- package/src/components/slider/slider.stories.tsx +30 -0
- package/src/components/slider/slider.tsx +49 -0
- package/src/components/stack/stack.props.ts +19 -0
- package/src/components/stack/stack.stories.tsx +79 -0
- package/src/components/stack/stack.tsx +79 -0
- package/src/components/stat-card/stat-card.props.ts +13 -0
- package/src/components/stat-card/stat-card.stories.tsx +41 -0
- package/src/components/stat-card/stat-card.tsx +44 -0
- package/src/components/stepper/stepper.css +26 -0
- package/src/components/stepper/stepper.props.ts +29 -0
- package/src/components/stepper/stepper.stories.tsx +155 -0
- package/src/components/stepper/stepper.tsx +227 -0
- package/src/components/table/table.props.ts +43 -0
- package/src/components/table/table.stories.tsx +189 -0
- package/src/components/table/table.tsx +376 -0
- package/src/components/tabs/tabs.props.ts +18 -0
- package/src/components/tabs/tabs.stories.tsx +32 -0
- package/src/components/tabs/tabs.tsx +74 -0
- package/src/components/text/text.props.ts +9 -0
- package/src/components/text/text.tsx +20 -0
- package/src/components/textarea/textarea.props.ts +15 -0
- package/src/components/textarea/textarea.stories.tsx +27 -0
- package/src/components/textarea/textarea.tsx +55 -0
- package/src/components/theme-provider/themeProvider.props.ts +28 -0
- package/src/components/theme-provider/themeProvider.tsx +1854 -0
- package/src/components/time-picker/timePicker.props.ts +16 -0
- package/src/components/time-picker/timePicker.stories.tsx +131 -0
- package/src/components/time-picker/timePicker.tsx +317 -0
- package/src/components/toast/toast.css +32 -0
- package/src/components/toast/toast.props.ts +13 -0
- package/src/components/toast/toast.stories.tsx +138 -0
- package/src/components/toast/toast.tsx +87 -0
- package/src/components/tooltip/tooltip.props.ts +11 -0
- package/src/components/tooltip/tooltip.stories.tsx +20 -0
- package/src/components/tooltip/tooltip.tsx +55 -0
- package/src/components/topbar/topbar.props.ts +21 -0
- package/src/components/topbar/topbar.stories.tsx +80 -0
- package/src/components/topbar/topbar.tsx +205 -0
- package/src/components/triple-filter/tripleFilter.props.ts +15 -0
- package/src/components/triple-filter/tripleFilter.stories.tsx +32 -0
- package/src/components/triple-filter/tripleFilter.tsx +50 -0
- package/src/hooks/useClickOutside.ts +21 -0
- package/src/hooks/useDebouncedSearch.ts +55 -0
- package/src/hooks/useEditableRow.ts +157 -0
- package/src/hooks/useTableState.ts +122 -0
- package/src/index.css +168 -0
- package/src/index.ts +165 -0
- package/src/main.tsx +9 -0
- package/src/showcases/DataShowcases.tsx +260 -0
- package/src/showcases/FeedbackShowcases.tsx +268 -0
- package/src/showcases/FormShowcases.tsx +1159 -0
- package/src/showcases/HomeShowcase.tsx +324 -0
- package/src/showcases/LayoutPrimitivesShowcases.tsx +569 -0
- package/src/showcases/NavigationShowcases.tsx +193 -0
- package/src/showcases/PageShowcases.tsx +207 -0
- package/src/showcases/ShowcaseLayout.tsx +139 -0
- package/src/showcases/StructureShowcases.tsx +152 -0
- package/src/types/badget.types.ts +37 -0
- package/src/types/button.types.ts +16 -0
- package/src/types/colors.types.ts +3 -0
- package/src/types/field.types.ts +103 -0
- package/src/types/formik.types.ts +15 -0
- package/src/types/input.types.ts +14 -0
- package/src/types/loader.types.ts +9 -0
- package/src/types/sizes.types.ts +1 -0
- package/src/types/table.types.ts +15 -0
- package/src/types/toast.types.ts +8 -0
- package/src/types/yup.types.ts +11 -0
- package/src/utils/color.utils.ts +99 -0
- package/src/utils/styles.ts +120 -0
- package/src/utils/table.utils.ts +10 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
import React, { useMemo, useState, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
format,
|
|
4
|
+
addDays,
|
|
5
|
+
startOfWeek,
|
|
6
|
+
eachDayOfInterval,
|
|
7
|
+
endOfWeek,
|
|
8
|
+
isSameDay,
|
|
9
|
+
isToday,
|
|
10
|
+
startOfDay,
|
|
11
|
+
parseISO,
|
|
12
|
+
differenceInMinutes,
|
|
13
|
+
addMinutes,
|
|
14
|
+
startOfMonth,
|
|
15
|
+
endOfMonth,
|
|
16
|
+
addMonths,
|
|
17
|
+
isSameMonth,
|
|
18
|
+
isBefore,
|
|
19
|
+
isAfter
|
|
20
|
+
} from 'date-fns';
|
|
21
|
+
import { es } from 'date-fns/locale';
|
|
22
|
+
import { clsx, type ClassValue } from 'clsx';
|
|
23
|
+
import { twMerge } from 'tailwind-merge';
|
|
24
|
+
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
|
|
25
|
+
import { ITCalendarProps, CalendarEvent } from './calendar.props';
|
|
26
|
+
import ITText from "@/components/text/text";
|
|
27
|
+
|
|
28
|
+
function cn(...inputs: ClassValue[]) {
|
|
29
|
+
return twMerge(clsx(inputs));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ----------------------------------------------------------------------
|
|
33
|
+
// Constants
|
|
34
|
+
// ----------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
const START_HOUR = 6; // 6 AM
|
|
37
|
+
const END_HOUR = 22; // 10 PM
|
|
38
|
+
const HOURS_COUNT = END_HOUR - START_HOUR;
|
|
39
|
+
|
|
40
|
+
// Generate time slots
|
|
41
|
+
const TIME_SLOTS = Array.from({ length: HOURS_COUNT + 1 }, (_, i) => START_HOUR + i);
|
|
42
|
+
|
|
43
|
+
// ----------------------------------------------------------------------
|
|
44
|
+
// Component
|
|
45
|
+
// ----------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
export const ITCalendar: React.FC<ITCalendarProps> = ({
|
|
48
|
+
events = [],
|
|
49
|
+
mode: modeProp,
|
|
50
|
+
onEventClick,
|
|
51
|
+
onSlotClick,
|
|
52
|
+
onSlotHover,
|
|
53
|
+
onSelectRange,
|
|
54
|
+
value,
|
|
55
|
+
onChange,
|
|
56
|
+
selectionMode = 'single',
|
|
57
|
+
startDate,
|
|
58
|
+
endDate,
|
|
59
|
+
minDate,
|
|
60
|
+
maxDate,
|
|
61
|
+
className,
|
|
62
|
+
variant = 'primary',
|
|
63
|
+
}) => {
|
|
64
|
+
// Determine mode: if onChange provided, assume picker (month) unless specialized
|
|
65
|
+
const mode = modeProp || (onChange ? 'month' : 'week');
|
|
66
|
+
|
|
67
|
+
const [currentDate, setCurrentDate] = useState(value || new Date());
|
|
68
|
+
const [view, setView] = useState<'calendar' | 'years'>('calendar');
|
|
69
|
+
|
|
70
|
+
// Selection/Accent colors based on variant
|
|
71
|
+
const getVariantStyles = () => {
|
|
72
|
+
const v = variant || 'primary';
|
|
73
|
+
return {
|
|
74
|
+
'--calendar-selected-bg': `var(--color-${v})`,
|
|
75
|
+
'--calendar-range-bg': `var(--color-${v}-50)`,
|
|
76
|
+
'--calendar-today-bg': `var(--color-${v}-100)`,
|
|
77
|
+
'--calendar-today-text': `var(--color-${v})`,
|
|
78
|
+
} as React.CSSProperties;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Sync internal state if value changes (for picker)
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (value) setCurrentDate(value);
|
|
84
|
+
}, [value]);
|
|
85
|
+
|
|
86
|
+
// Navigation handlers
|
|
87
|
+
const handleNext = () => {
|
|
88
|
+
if (view === 'years') {
|
|
89
|
+
setCurrentDate((d) => {
|
|
90
|
+
const newDate = new Date(d);
|
|
91
|
+
newDate.setFullYear(d.getFullYear() + 12);
|
|
92
|
+
return newDate;
|
|
93
|
+
});
|
|
94
|
+
} else if (mode === 'month') {
|
|
95
|
+
setCurrentDate((d) => addMonths(d, 1));
|
|
96
|
+
} else if (mode === 'day') {
|
|
97
|
+
setCurrentDate((d) => addDays(d, 1));
|
|
98
|
+
} else {
|
|
99
|
+
setCurrentDate((d) => addDays(d, 7));
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const handlePrev = () => {
|
|
104
|
+
if (view === 'years') {
|
|
105
|
+
setCurrentDate((d) => {
|
|
106
|
+
const newDate = new Date(d);
|
|
107
|
+
newDate.setFullYear(d.getFullYear() - 12);
|
|
108
|
+
return newDate;
|
|
109
|
+
});
|
|
110
|
+
} else if (mode === 'month') {
|
|
111
|
+
setCurrentDate((d) => addMonths(d, -1));
|
|
112
|
+
} else if (mode === 'day') {
|
|
113
|
+
setCurrentDate((d) => addDays(d, -1));
|
|
114
|
+
} else {
|
|
115
|
+
setCurrentDate((d) => addDays(d, -7));
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const handleToday = () => {
|
|
120
|
+
setCurrentDate(new Date());
|
|
121
|
+
setView('calendar');
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// ----------------------------------------------------------------------
|
|
125
|
+
// Week / Day View Logic
|
|
126
|
+
// ----------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
const viewDays = useMemo(() => {
|
|
129
|
+
if (mode === 'day') {
|
|
130
|
+
return [currentDate];
|
|
131
|
+
}
|
|
132
|
+
const start = startOfWeek(currentDate, { weekStartsOn: 1 });
|
|
133
|
+
const end = endOfWeek(currentDate, { weekStartsOn: 1 });
|
|
134
|
+
return eachDayOfInterval({ start, end });
|
|
135
|
+
}, [currentDate, mode]);
|
|
136
|
+
|
|
137
|
+
const getEventStyle = (event: CalendarEvent) => {
|
|
138
|
+
const start = typeof event.start === 'string' ? parseISO(event.start) : event.start;
|
|
139
|
+
const end = typeof event.end === 'string' ? parseISO(event.end) : event.end;
|
|
140
|
+
|
|
141
|
+
const startMinutes = start.getHours() * 60 + start.getMinutes();
|
|
142
|
+
const dayStartMinutes = START_HOUR * 60;
|
|
143
|
+
|
|
144
|
+
const duration = differenceInMinutes(end, start);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
top: `${((startMinutes - dayStartMinutes) / 60) * 80}px`,
|
|
148
|
+
height: `${(duration / 60) * 80}px`,
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const weekEvents = useMemo(() => {
|
|
153
|
+
return events.filter(event => {
|
|
154
|
+
const eventStart = typeof event.start === 'string' ? parseISO(event.start) : event.start;
|
|
155
|
+
return viewDays.some(day => isSameDay(day, eventStart));
|
|
156
|
+
});
|
|
157
|
+
}, [events, viewDays]);
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
// ----------------------------------------------------------------------
|
|
161
|
+
// Month View Logic (Picker)
|
|
162
|
+
// ----------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
const monthDays = useMemo(() => {
|
|
165
|
+
const start = startOfWeek(startOfMonth(currentDate), { weekStartsOn: 1 });
|
|
166
|
+
const end = endOfWeek(endOfMonth(currentDate), { weekStartsOn: 1 });
|
|
167
|
+
return eachDayOfInterval({ start, end });
|
|
168
|
+
}, [currentDate]);
|
|
169
|
+
|
|
170
|
+
const isDateDisabled = (date: Date) => {
|
|
171
|
+
if (minDate && isBefore(date, startOfDay(minDate))) return true;
|
|
172
|
+
if (maxDate && isAfter(date, startOfDay(maxDate))) return true;
|
|
173
|
+
return false;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const [dragStart, setDragStart] = useState<Date | null>(null);
|
|
177
|
+
const [dragCurrent, setDragCurrent] = useState<Date | null>(null);
|
|
178
|
+
const isDraggingRef = React.useRef(false);
|
|
179
|
+
|
|
180
|
+
const handleMouseDown = (date: Date, e: React.MouseEvent) => {
|
|
181
|
+
// Only enable drag if onSelectRange is provided
|
|
182
|
+
if (!onSelectRange) return;
|
|
183
|
+
// We do NOT stopPropagation here completely because we might want other things?
|
|
184
|
+
// Actually for drag we probably want to claim it.
|
|
185
|
+
e.stopPropagation();
|
|
186
|
+
e.preventDefault(); // Prevent text selection
|
|
187
|
+
|
|
188
|
+
isDraggingRef.current = false;
|
|
189
|
+
setDragStart(date);
|
|
190
|
+
setDragCurrent(date);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const handleMouseEnter = (date: Date) => {
|
|
194
|
+
if (onSlotHover) {
|
|
195
|
+
onSlotHover(date);
|
|
196
|
+
}
|
|
197
|
+
if (dragStart) {
|
|
198
|
+
isDraggingRef.current = true;
|
|
199
|
+
setDragCurrent(date);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// This handles the end of the drag (assigned to container)
|
|
204
|
+
const handleMouseUp = () => {
|
|
205
|
+
if (dragStart && dragCurrent && onSelectRange && isDraggingRef.current) {
|
|
206
|
+
// Normalize start/end
|
|
207
|
+
let start = dragStart;
|
|
208
|
+
let end = dragCurrent;
|
|
209
|
+
if (isBefore(end, start)) {
|
|
210
|
+
[start, end] = [end, start];
|
|
211
|
+
}
|
|
212
|
+
// End date should include the selected slot duration (add 30 mins)
|
|
213
|
+
const finalEnd = addMinutes(end, 30);
|
|
214
|
+
|
|
215
|
+
if (!isSameDay(start, finalEnd) && differenceInMinutes(finalEnd, start) > 0) {
|
|
216
|
+
// Ensure we don't accidentally select across days if logic isn't robust
|
|
217
|
+
// But currently `dragCurrent` comes from same view.
|
|
218
|
+
// Just call it.
|
|
219
|
+
onSelectRange(start, finalEnd);
|
|
220
|
+
} else {
|
|
221
|
+
onSelectRange(start, finalEnd);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// We don't reset isDraggingRef.current here immediately if we want to block the subsequent click?
|
|
226
|
+
// But clearing dragStart will be checked by onClick?
|
|
227
|
+
// No, onClick checks !dragStart... but dragStart becomes null here.
|
|
228
|
+
// So onClick needs to check isDraggingRef.
|
|
229
|
+
|
|
230
|
+
setDragStart(null);
|
|
231
|
+
setDragCurrent(null);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// ----------------------------------------------------------------------
|
|
235
|
+
// Year View Data
|
|
236
|
+
// ----------------------------------------------------------------------
|
|
237
|
+
const startYear = currentDate.getFullYear() - 6;
|
|
238
|
+
const years = Array.from({ length: 12 }, (_, i) => startYear + i);
|
|
239
|
+
|
|
240
|
+
// ----------------------------------------------------------------------
|
|
241
|
+
// Render
|
|
242
|
+
// ----------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<div
|
|
246
|
+
className={cn("flex flex-col h-full rounded-lg shadow-sm overflow-hidden select-none", className)}
|
|
247
|
+
style={{
|
|
248
|
+
backgroundColor: 'var(--calendar-bg, #ffffff)',
|
|
249
|
+
border: '1px solid var(--calendar-border, #e2e8f0)',
|
|
250
|
+
...getVariantStyles(),
|
|
251
|
+
}}
|
|
252
|
+
onMouseUp={handleMouseUp}
|
|
253
|
+
onMouseLeave={() => {
|
|
254
|
+
setDragStart(null);
|
|
255
|
+
setDragCurrent(null);
|
|
256
|
+
}}
|
|
257
|
+
>
|
|
258
|
+
|
|
259
|
+
{/* Header */}
|
|
260
|
+
<div
|
|
261
|
+
className="flex items-center justify-between px-2 py-2 border-b"
|
|
262
|
+
style={{
|
|
263
|
+
backgroundColor: 'var(--calendar-bg, #ffffff)',
|
|
264
|
+
borderBottomColor: 'var(--calendar-border, #e2e8f0)'
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
<ITText
|
|
268
|
+
as="h2"
|
|
269
|
+
className="text-sm font-bold capitalize cursor-pointer transition-colors select-none px-2 py-1 rounded"
|
|
270
|
+
style={{
|
|
271
|
+
color: 'var(--calendar-header-text, #1e293b)',
|
|
272
|
+
}}
|
|
273
|
+
onClick={() => setView(view === 'calendar' ? 'years' : 'calendar')}
|
|
274
|
+
onMouseEnter={(e) => {
|
|
275
|
+
e.currentTarget.style.backgroundColor = 'var(--calendar-header-hover, #f1f5f9)';
|
|
276
|
+
}}
|
|
277
|
+
onMouseLeave={(e) => {
|
|
278
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
279
|
+
}}
|
|
280
|
+
>
|
|
281
|
+
{view === 'years'
|
|
282
|
+
? `${years[0]} - ${years[years.length - 1]}`
|
|
283
|
+
: format(currentDate, 'MMMM yyyy', { locale: es })
|
|
284
|
+
}
|
|
285
|
+
</ITText>
|
|
286
|
+
<div className="flex items-center gap-1">
|
|
287
|
+
<button
|
|
288
|
+
onClick={handlePrev}
|
|
289
|
+
type="button"
|
|
290
|
+
className="p-1.5 rounded-md transition-colors"
|
|
291
|
+
style={{ color: 'var(--calendar-days-text, #334155)' }}
|
|
292
|
+
onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = 'var(--calendar-header-hover, #f1f5f9)'; }}
|
|
293
|
+
onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = 'transparent'; }}
|
|
294
|
+
>
|
|
295
|
+
<FaChevronLeft size={14} />
|
|
296
|
+
</button>
|
|
297
|
+
<button
|
|
298
|
+
onClick={handleToday}
|
|
299
|
+
type="button"
|
|
300
|
+
className="text-sm font-medium px-3 py-1.5 rounded-md transition-colors"
|
|
301
|
+
style={{ color: 'var(--calendar-days-text, #334155)' }}
|
|
302
|
+
onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = 'var(--calendar-header-hover, #f1f5f9)'; }}
|
|
303
|
+
onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = 'transparent'; }}
|
|
304
|
+
>
|
|
305
|
+
<ITText as="span">Hoy</ITText>
|
|
306
|
+
</button>
|
|
307
|
+
<button
|
|
308
|
+
onClick={handleNext}
|
|
309
|
+
type="button"
|
|
310
|
+
className="p-1.5 rounded-md transition-colors"
|
|
311
|
+
style={{ color: 'var(--calendar-days-text, #334155)' }}
|
|
312
|
+
onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = 'var(--calendar-header-hover, #f1f5f9)'; }}
|
|
313
|
+
onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = 'transparent'; }}
|
|
314
|
+
>
|
|
315
|
+
<FaChevronRight size={14} />
|
|
316
|
+
</button>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
{/* Content */}
|
|
321
|
+
<div
|
|
322
|
+
className="flex-1 overflow-auto relative"
|
|
323
|
+
style={{ backgroundColor: 'var(--calendar-bg, #ffffff)' }}
|
|
324
|
+
>
|
|
325
|
+
|
|
326
|
+
{view === 'years' ? (
|
|
327
|
+
<div className="p-4 grid grid-cols-4 gap-2">
|
|
328
|
+
{years.map(year => (
|
|
329
|
+
<button
|
|
330
|
+
key={year}
|
|
331
|
+
type="button"
|
|
332
|
+
className={cn(
|
|
333
|
+
"h-10 rounded-md text-sm font-medium transition-colors border border-transparent",
|
|
334
|
+
year === currentDate.getFullYear()
|
|
335
|
+
? "bg-[var(--calendar-selected-bg)] text-[var(--calendar-selected-text)]"
|
|
336
|
+
: "hover:bg-[var(--calendar-today-bg)] hover:text-[var(--calendar-today-text)]"
|
|
337
|
+
)}
|
|
338
|
+
style={{
|
|
339
|
+
color: year === currentDate.getFullYear()
|
|
340
|
+
? 'var(--calendar-selected-text, #ffffff)'
|
|
341
|
+
: 'var(--calendar-days-text, #334155)'
|
|
342
|
+
}}
|
|
343
|
+
onClick={() => {
|
|
344
|
+
setCurrentDate((d) => {
|
|
345
|
+
const newDate = new Date(d);
|
|
346
|
+
newDate.setFullYear(year);
|
|
347
|
+
return newDate;
|
|
348
|
+
});
|
|
349
|
+
setView('calendar');
|
|
350
|
+
}}
|
|
351
|
+
>
|
|
352
|
+
<ITText as="span">{year}</ITText>
|
|
353
|
+
</button>
|
|
354
|
+
))}
|
|
355
|
+
</div>
|
|
356
|
+
) : mode === 'month' ? (
|
|
357
|
+
<div className="p-4">
|
|
358
|
+
{/* Weekday Headers */}
|
|
359
|
+
<div className="grid grid-cols-7 mb-2">
|
|
360
|
+
{['Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom'].map(day => (
|
|
361
|
+
<ITText key={day} as="div" className="text-center text-xs font-semibold text-gray-400 uppercase py-1">
|
|
362
|
+
{day}
|
|
363
|
+
</ITText>
|
|
364
|
+
))}
|
|
365
|
+
</div>
|
|
366
|
+
{/* Days Grid */}
|
|
367
|
+
<div className="grid grid-cols-7 gap-1">
|
|
368
|
+
{monthDays.map((day) => {
|
|
369
|
+
const isDisabled = isDateDisabled(day);
|
|
370
|
+
const isCurrentMonth = isSameMonth(day, currentDate);
|
|
371
|
+
|
|
372
|
+
// Selection logic
|
|
373
|
+
const isSelected = selectionMode === 'single' && value && isSameDay(day, value);
|
|
374
|
+
const isRangeStart = selectionMode === 'range' && startDate && isSameDay(day, startDate);
|
|
375
|
+
const isRangeEnd = selectionMode === 'range' && endDate && isSameDay(day, endDate);
|
|
376
|
+
const isInRange = selectionMode === 'range' && startDate && endDate && isAfter(day, startDate) && isBefore(day, endDate);
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<button
|
|
380
|
+
key={day.toISOString()}
|
|
381
|
+
type="button"
|
|
382
|
+
disabled={isDisabled}
|
|
383
|
+
onClick={() => onChange && onChange(day)}
|
|
384
|
+
className={cn(
|
|
385
|
+
"h-10 w-full flex items-center justify-center rounded-md text-sm transition-colors relative",
|
|
386
|
+
!isCurrentMonth && "opacity-40",
|
|
387
|
+
isDisabled && "opacity-20 cursor-not-allowed",
|
|
388
|
+
)}
|
|
389
|
+
style={{
|
|
390
|
+
backgroundColor: isSelected || isRangeStart || isRangeEnd
|
|
391
|
+
? 'var(--calendar-selected-bg, #2563eb)'
|
|
392
|
+
: isInRange
|
|
393
|
+
? 'var(--calendar-range-bg, #eff6ff)'
|
|
394
|
+
: isToday(day)
|
|
395
|
+
? 'var(--calendar-today-bg, #eff6ff)'
|
|
396
|
+
: 'transparent',
|
|
397
|
+
color: isSelected || isRangeStart || isRangeEnd
|
|
398
|
+
? 'var(--calendar-selected-text, #ffffff)'
|
|
399
|
+
: isToday(day)
|
|
400
|
+
? 'var(--calendar-today-text, #2563eb)'
|
|
401
|
+
: 'var(--calendar-days-text, #334155)',
|
|
402
|
+
fontWeight: isSelected || isRangeStart || isRangeEnd || isToday(day) ? '700' : '400',
|
|
403
|
+
}}
|
|
404
|
+
>
|
|
405
|
+
<ITText as="span">{format(day, 'd')}</ITText>
|
|
406
|
+
|
|
407
|
+
{/* Connection for range selection to make it look continuous */}
|
|
408
|
+
{selectionMode === 'range' && isRangeStart && endDate && (
|
|
409
|
+
<div className="absolute right-0 top-0 bottom-0 w-2 bg-[var(--calendar-range-bg)] -z-10" />
|
|
410
|
+
)}
|
|
411
|
+
{selectionMode === 'range' && isRangeEnd && startDate && (
|
|
412
|
+
<div className="absolute left-0 top-0 bottom-0 w-2 bg-[var(--calendar-range-bg)] -z-10" />
|
|
413
|
+
)}
|
|
414
|
+
</button>
|
|
415
|
+
);
|
|
416
|
+
})}
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
) : (
|
|
420
|
+
/* Week/Day View (Scheduler) */
|
|
421
|
+
<div className={cn("flex h-full", mode === 'week' ? "min-w-[800px]" : "w-full")}>
|
|
422
|
+
{/* Time Sidebar */}
|
|
423
|
+
<div
|
|
424
|
+
className="flex-none w-16 pt-10 select-none"
|
|
425
|
+
style={{
|
|
426
|
+
backgroundColor: 'var(--calendar-header-hover, #f1f5f9)',
|
|
427
|
+
borderRight: '1px solid var(--calendar-border, #e2e8f0)',
|
|
428
|
+
}}
|
|
429
|
+
>
|
|
430
|
+
{TIME_SLOTS.map((hour) => (
|
|
431
|
+
hour < END_HOUR && (
|
|
432
|
+
<div key={hour} className="h-20 relative text-right pr-2">
|
|
433
|
+
<ITText as="span" className="text-xs text-slate-400 dark:text-slate-500 -mt-2 inline-block transform -translate-y-1/2">
|
|
434
|
+
{format(new Date().setHours(hour, 0), 'HH:mm')}
|
|
435
|
+
</ITText>
|
|
436
|
+
</div>
|
|
437
|
+
)
|
|
438
|
+
))}
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
{/* Days Columns */}
|
|
442
|
+
<div className="flex flex-1">
|
|
443
|
+
{viewDays.map((day) => (
|
|
444
|
+
<div
|
|
445
|
+
key={day.toISOString()}
|
|
446
|
+
className="flex-1 min-w-[120px] relative"
|
|
447
|
+
style={{ borderRight: '1px solid var(--calendar-border, #e2e8f0)' }}
|
|
448
|
+
>
|
|
449
|
+
|
|
450
|
+
{/* Day Header */}
|
|
451
|
+
<div
|
|
452
|
+
className="h-10 flex flex-col items-center justify-center sticky top-0 z-10"
|
|
453
|
+
style={{
|
|
454
|
+
backgroundColor: isToday(day) ? 'var(--calendar-today-bg, #eff6ff)' : 'var(--calendar-bg, #ffffff)',
|
|
455
|
+
borderBottom: '1px solid var(--calendar-border, #e2e8f0)',
|
|
456
|
+
}}
|
|
457
|
+
>
|
|
458
|
+
<ITText
|
|
459
|
+
as="span"
|
|
460
|
+
className="text-xs font-semibold uppercase"
|
|
461
|
+
style={{
|
|
462
|
+
color: isToday(day) ? 'var(--calendar-today-text, #2563eb)' : 'var(--calendar-days-text, #334155)',
|
|
463
|
+
opacity: isToday(day) ? 1 : 0.6,
|
|
464
|
+
}}
|
|
465
|
+
>
|
|
466
|
+
{format(day, 'EEE', { locale: es })}
|
|
467
|
+
</ITText>
|
|
468
|
+
<ITText
|
|
469
|
+
as="span"
|
|
470
|
+
className="text-sm font-bold w-6 h-6 flex items-center justify-center rounded-full mt-0.5"
|
|
471
|
+
style={{
|
|
472
|
+
color: isToday(day) ? 'var(--calendar-selected-text, #ffffff)' : 'var(--calendar-days-text, #334155)',
|
|
473
|
+
backgroundColor: isToday(day) ? 'var(--calendar-selected-bg, #2563eb)' : 'transparent',
|
|
474
|
+
}}
|
|
475
|
+
>
|
|
476
|
+
{format(day, 'd')}
|
|
477
|
+
</ITText>
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
{/* Slots Grid */}
|
|
481
|
+
<div className="relative">
|
|
482
|
+
{TIME_SLOTS.map((hour) => (
|
|
483
|
+
hour < END_HOUR && (
|
|
484
|
+
<div
|
|
485
|
+
key={hour}
|
|
486
|
+
className="h-20 relative group"
|
|
487
|
+
style={{ borderBottom: '1px dashed var(--calendar-border, #e2e8f0)' }}
|
|
488
|
+
>
|
|
489
|
+
{/* Slot 00 */}
|
|
490
|
+
<div
|
|
491
|
+
className="absolute inset-x-0 top-0 h-10 border-b border-transparent hover:border-[var(--calendar-today-bg)] hover:bg-[var(--calendar-today-bg)] transition-colors cursor-pointer z-0"
|
|
492
|
+
onMouseDown={(e) => {
|
|
493
|
+
const d = new Date(day); d.setHours(hour, 0, 0, 0);
|
|
494
|
+
handleMouseDown(d, e);
|
|
495
|
+
}}
|
|
496
|
+
onMouseEnter={() => {
|
|
497
|
+
const d = new Date(day); d.setHours(hour, 0, 0, 0);
|
|
498
|
+
handleMouseEnter(d);
|
|
499
|
+
}}
|
|
500
|
+
onClick={() => {
|
|
501
|
+
if (!isDraggingRef.current) {
|
|
502
|
+
const d = new Date(day); d.setHours(hour, 0, 0, 0);
|
|
503
|
+
onSlotClick && onSlotClick(d);
|
|
504
|
+
}
|
|
505
|
+
}}
|
|
506
|
+
/>
|
|
507
|
+
{/* Slot 30 */}
|
|
508
|
+
<div
|
|
509
|
+
className="absolute inset-x-0 bottom-0 h-10 hover:border-[var(--calendar-today-bg)] hover:bg-[var(--calendar-today-bg)] transition-colors cursor-pointer z-0"
|
|
510
|
+
onMouseDown={(e) => {
|
|
511
|
+
const d = new Date(day); d.setHours(hour, 30, 0, 0);
|
|
512
|
+
handleMouseDown(d, e);
|
|
513
|
+
}}
|
|
514
|
+
onMouseEnter={() => {
|
|
515
|
+
const d = new Date(day); d.setHours(hour, 30, 0, 0);
|
|
516
|
+
handleMouseEnter(d);
|
|
517
|
+
}}
|
|
518
|
+
onClick={() => {
|
|
519
|
+
if (!isDraggingRef.current) {
|
|
520
|
+
const d = new Date(day); d.setHours(hour, 30, 0, 0);
|
|
521
|
+
onSlotClick && onSlotClick(d);
|
|
522
|
+
}
|
|
523
|
+
}}
|
|
524
|
+
/>
|
|
525
|
+
</div>
|
|
526
|
+
)
|
|
527
|
+
))}
|
|
528
|
+
|
|
529
|
+
{/* Selection Overlay */}
|
|
530
|
+
{dragStart && dragCurrent && isSameDay(dragStart, day) && (
|
|
531
|
+
(() => {
|
|
532
|
+
let start = dragStart;
|
|
533
|
+
let end = dragCurrent;
|
|
534
|
+
if (isBefore(end, start)) [start, end] = [end, start];
|
|
535
|
+
const finalEnd = addMinutes(end, 30); // Visual end is end of slot
|
|
536
|
+
|
|
537
|
+
const startMinutes = start.getHours() * 60 + start.getMinutes();
|
|
538
|
+
const dayStartMinutes = START_HOUR * 60;
|
|
539
|
+
const duration = differenceInMinutes(finalEnd, start);
|
|
540
|
+
const top = ((startMinutes - dayStartMinutes) / 60) * 80;
|
|
541
|
+
const height = (duration / 60) * 80;
|
|
542
|
+
|
|
543
|
+
return (
|
|
544
|
+
<div
|
|
545
|
+
className="absolute left-1 right-1 bg-[var(--calendar-selected-bg)]/30 border border-[var(--calendar-selected-bg)] rounded z-10 pointer-events-none"
|
|
546
|
+
style={{ top: `${top}px`, height: `${height}px` }}
|
|
547
|
+
/>
|
|
548
|
+
);
|
|
549
|
+
})()
|
|
550
|
+
)}
|
|
551
|
+
|
|
552
|
+
{/* Events */}
|
|
553
|
+
{weekEvents
|
|
554
|
+
.filter((event) => isSameDay(typeof event.start === 'string' ? parseISO(event.start) : event.start, day))
|
|
555
|
+
.map((event) => {
|
|
556
|
+
const style = getEventStyle(event);
|
|
557
|
+
return (
|
|
558
|
+
<div
|
|
559
|
+
key={event.id}
|
|
560
|
+
className={cn(
|
|
561
|
+
"absolute left-1 right-1 rounded px-2 py-1 text-xs cursor-pointer hover:brightness-95 transition-all shadow-sm overflow-hidden z-20 border-l-4",
|
|
562
|
+
!event.color && "bg-[var(--calendar-today-bg)] text-[var(--calendar-today-text)] border-[var(--calendar-selected-bg)]"
|
|
563
|
+
)}
|
|
564
|
+
style={{
|
|
565
|
+
top: style.top,
|
|
566
|
+
height: style.height,
|
|
567
|
+
backgroundColor: event.color ? `${event.color}20` : undefined,
|
|
568
|
+
borderColor: event.color,
|
|
569
|
+
color: event.color ? event.color : undefined
|
|
570
|
+
}}
|
|
571
|
+
onClick={(e) => {
|
|
572
|
+
e.stopPropagation();
|
|
573
|
+
onEventClick && onEventClick(event);
|
|
574
|
+
}}
|
|
575
|
+
>
|
|
576
|
+
<ITText as="div" className="font-semibold truncate">{event.title}</ITText>
|
|
577
|
+
<ITText as="div" className="opacity-80 truncate">
|
|
578
|
+
{format(typeof event.start === 'string' ? parseISO(event.start) : event.start, 'HH:mm')} -
|
|
579
|
+
{format(typeof event.end === 'string' ? parseISO(event.end) : event.end, 'HH:mm')}
|
|
580
|
+
</ITText>
|
|
581
|
+
</div>
|
|
582
|
+
);
|
|
583
|
+
})}
|
|
584
|
+
</div>
|
|
585
|
+
|
|
586
|
+
{/* Current Time Line */}
|
|
587
|
+
{isToday(day) && (
|
|
588
|
+
<div
|
|
589
|
+
className="absolute left-0 right-0 border-t-2 border-danger-500 z-30 pointer-events-none"
|
|
590
|
+
style={{
|
|
591
|
+
top: `${((new Date().getHours() * 60 + new Date().getMinutes() - (START_HOUR * 60)) / 60) * 80}px`
|
|
592
|
+
}}
|
|
593
|
+
>
|
|
594
|
+
<div className="absolute -left-1.5 -top-1.5 w-3 h-3 bg-danger-500 rounded-full" />
|
|
595
|
+
</div>
|
|
596
|
+
)}
|
|
597
|
+
|
|
598
|
+
</div>
|
|
599
|
+
))}
|
|
600
|
+
</div>
|
|
601
|
+
</div>
|
|
602
|
+
)}
|
|
603
|
+
</div>
|
|
604
|
+
</div>
|
|
605
|
+
);
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
export default ITCalendar;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface ITCardProps {
|
|
2
|
+
onClick?: () => void;
|
|
3
|
+
title?: string;
|
|
4
|
+
image?: string;
|
|
5
|
+
alt?: string;
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
actions?: React.ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
imageClassName?: string;
|
|
10
|
+
titleClassName?: string;
|
|
11
|
+
contentClassName?: string;
|
|
12
|
+
actionClassName?: string;
|
|
13
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import ITCard from './card';
|
|
3
|
+
import ITButton from '../button/button';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/Layout & Navigation/ITCard',
|
|
7
|
+
component: ITCard,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: 'centered',
|
|
10
|
+
},
|
|
11
|
+
tags: ['autodocs'],
|
|
12
|
+
argTypes: {
|
|
13
|
+
onClick: { action: 'clicked' },
|
|
14
|
+
},
|
|
15
|
+
} satisfies Meta<typeof ITCard>;
|
|
16
|
+
|
|
17
|
+
export default meta;
|
|
18
|
+
type Story = StoryObj<typeof meta>;
|
|
19
|
+
|
|
20
|
+
export const Default: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
title: 'Card Title',
|
|
23
|
+
children: 'This is the body of the card. It has padding defined by the theme.',
|
|
24
|
+
onClick: undefined,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const WithImage: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
title: 'Card with Image',
|
|
31
|
+
image: 'https://images.unsplash.com/photo-1579546929518-9e396f3cc809?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
|
32
|
+
children: 'A nice gradient image above.',
|
|
33
|
+
onClick: undefined,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const WithActions: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
title: 'Card with Actions',
|
|
40
|
+
children: 'This card has a button action at the bottom.',
|
|
41
|
+
actions: (
|
|
42
|
+
<div className="flex justify-end gap-2">
|
|
43
|
+
<ITButton variant="text" color="secondary" label="Cancel" />
|
|
44
|
+
<ITButton variant="filled" color="primary" label="Save" />
|
|
45
|
+
</div>
|
|
46
|
+
),
|
|
47
|
+
onClick: undefined,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Clickable: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
title: 'Clickable Card',
|
|
54
|
+
children: 'Hover over me! I should have a stronger shadow and a pointer cursor.',
|
|
55
|
+
onClick: () => alert('Card clicked!'),
|
|
56
|
+
image: 'https://images.unsplash.com/photo-1557683316-973673baf926?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
|
57
|
+
},
|
|
58
|
+
};
|