diff --git a/my-app/.gitignore b/my-app/.gitignore index f52343a..0a776fb 100644 --- a/my-app/.gitignore +++ b/my-app/.gitignore @@ -25,3 +25,7 @@ dist-ssr *storybook.log storybook-static + +# Font files +public/caecilia/ +public/the-sans/ diff --git a/my-app/public/su-logo-white.svg b/my-app/public/su-logo-white.svg new file mode 100644 index 0000000..2aa90b8 --- /dev/null +++ b/my-app/public/su-logo-white.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/my-app/public/the-sans/TheSansB-W2ExtraLight.otf b/my-app/public/the-sans/TheSansB-W2ExtraLight.otf deleted file mode 100755 index e7a7f59..0000000 Binary files a/my-app/public/the-sans/TheSansB-W2ExtraLight.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W2ExtraLightItalic.otf b/my-app/public/the-sans/TheSansB-W2ExtraLightItalic.otf deleted file mode 100755 index 8566c5c..0000000 Binary files a/my-app/public/the-sans/TheSansB-W2ExtraLightItalic.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W3Light.otf b/my-app/public/the-sans/TheSansB-W3Light.otf deleted file mode 100755 index a6cc72a..0000000 Binary files a/my-app/public/the-sans/TheSansB-W3Light.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W3LightItalic.otf b/my-app/public/the-sans/TheSansB-W3LightItalic.otf deleted file mode 100755 index 4442c95..0000000 Binary files a/my-app/public/the-sans/TheSansB-W3LightItalic.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W4SemiLight.otf b/my-app/public/the-sans/TheSansB-W4SemiLight.otf deleted file mode 100755 index 4aca117..0000000 Binary files a/my-app/public/the-sans/TheSansB-W4SemiLight.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W4SemiLightItalic.otf b/my-app/public/the-sans/TheSansB-W4SemiLightItalic.otf deleted file mode 100755 index 56edcb0..0000000 Binary files a/my-app/public/the-sans/TheSansB-W4SemiLightItalic.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W5Plain.otf b/my-app/public/the-sans/TheSansB-W5Plain.otf deleted file mode 100755 index 03864e3..0000000 Binary files a/my-app/public/the-sans/TheSansB-W5Plain.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W5PlainItalic.otf b/my-app/public/the-sans/TheSansB-W5PlainItalic.otf deleted file mode 100755 index 26db55e..0000000 Binary files a/my-app/public/the-sans/TheSansB-W5PlainItalic.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W6SemiBold.otf b/my-app/public/the-sans/TheSansB-W6SemiBold.otf deleted file mode 100755 index 9781830..0000000 Binary files a/my-app/public/the-sans/TheSansB-W6SemiBold.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W6SemiBoldItalic.otf b/my-app/public/the-sans/TheSansB-W6SemiBoldItalic.otf deleted file mode 100755 index 4a2e4d6..0000000 Binary files a/my-app/public/the-sans/TheSansB-W6SemiBoldItalic.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W7Bold.otf b/my-app/public/the-sans/TheSansB-W7Bold.otf deleted file mode 100755 index f3f9f31..0000000 Binary files a/my-app/public/the-sans/TheSansB-W7Bold.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W7BoldItalic.otf b/my-app/public/the-sans/TheSansB-W7BoldItalic.otf deleted file mode 100755 index 97819cc..0000000 Binary files a/my-app/public/the-sans/TheSansB-W7BoldItalic.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W8ExtraBold.otf b/my-app/public/the-sans/TheSansB-W8ExtraBold.otf deleted file mode 100755 index 26386b7..0000000 Binary files a/my-app/public/the-sans/TheSansB-W8ExtraBold.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W8ExtraBoldItalic.otf b/my-app/public/the-sans/TheSansB-W8ExtraBoldItalic.otf deleted file mode 100755 index d7f1297..0000000 Binary files a/my-app/public/the-sans/TheSansB-W8ExtraBoldItalic.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W9Black.otf b/my-app/public/the-sans/TheSansB-W9Black.otf deleted file mode 100755 index a009678..0000000 Binary files a/my-app/public/the-sans/TheSansB-W9Black.otf and /dev/null differ diff --git a/my-app/public/the-sans/TheSansB-W9BlackItalic.otf b/my-app/public/the-sans/TheSansB-W9BlackItalic.otf deleted file mode 100755 index 705c768..0000000 Binary files a/my-app/public/the-sans/TheSansB-W9BlackItalic.otf and /dev/null differ diff --git a/my-app/src/AppRoutes.jsx b/my-app/src/AppRoutes.jsx index 92e41cb..76e0a30 100644 --- a/my-app/src/AppRoutes.jsx +++ b/my-app/src/AppRoutes.jsx @@ -5,6 +5,7 @@ import { CalendarDate } from '@internationalized/date'; import Layout from './Layout'; import { RoomBooking } from './pages/RoomBooking'; import { NewBooking } from './pages/NewBooking'; +import { BookingDetails } from './pages/BookingDetails'; import { BookingSettings } from './pages/BookingSettings'; import { CourseSchedule } from './pages/CourseSchedule'; import { CourseScheduleView } from './pages/CourseScheduleView'; @@ -119,6 +120,7 @@ const AppRoutes = () => { }> setShowSuccessBanner(false)} onBookingUpdate={updateBooking} onBookingDelete={deleteBooking} showDeleteBanner={showDeleteBanner} lastDeletedBooking={lastDeletedBooking} onDismissDeleteBanner={() => setShowDeleteBanner(false)} />} /> } /> + } /> } /> } /> } /> diff --git a/my-app/src/components/booking/BookingModal.jsx b/my-app/src/components/booking/BookingModal.jsx index 30f87fd..f7ad497 100644 --- a/my-app/src/components/booking/BookingModal.jsx +++ b/my-app/src/components/booking/BookingModal.jsx @@ -4,6 +4,8 @@ import { convertDateObjectToString, getTimeFromIndex } from '../../helpers'; import Dropdown from '../ui/Dropdown'; import { useBookingContext } from '../../context/BookingContext'; import { useSettingsContext } from '../../context/SettingsContext'; +import { BookingTitleField } from '../forms/BookingTitleField'; +import { ParticipantsSelector } from '../forms/ParticipantsSelector'; import styles from './BookingModal.module.css'; export function BookingModal({ @@ -13,79 +15,104 @@ export function BookingModal({ setEndTimeIndex, className, onClose, + onNavigateToDetails, isOpen = true }) { const booking = useBookingContext(); const { getCurrentUser, getDefaultBookingTitle } = useSettingsContext(); - // Initialize with pre-selected booking length if available, or auto-select if only 30 min available - const initialLength = booking.selectedBookingLength > 0 ? booking.selectedBookingLength : - (hoursAvailable === 1 ? 1 : null); // Auto-select 30 min if that's all that's available - const [selectedLength, setSelectedLength] = useState(null); - const [calculatedEndTime, setCalculatedEndTime] = useState(startTimeIndex); + // Initialize with pre-selected end time if available, or auto-select if only 30 min available + const initialEndTimeIndex = booking.selectedBookingLength > 0 ? startTimeIndex + booking.selectedBookingLength : + (hoursAvailable === 1 ? startTimeIndex + 1 : null); // Auto-select 30 min if that's all that's available + const [selectedEndTimeIndex, setSelectedEndTimeIndex] = useState(null); const hasInitialized = useRef(false); + + // Store the original hours available to prevent it from changing when selections are made + const originalHoursAvailable = useRef(hoursAvailable); + if (originalHoursAvailable.current < hoursAvailable) { + originalHoursAvailable.current = hoursAvailable; + } // Effect to handle initial setup only once when modal opens useEffect(() => { - if (initialLength && !hasInitialized.current) { - setSelectedLength(initialLength); - const newEndTime = startTimeIndex + initialLength; - setCalculatedEndTime(newEndTime); - setEndTimeIndex(newEndTime); - booking.setSelectedEndIndex(newEndTime); + if (initialEndTimeIndex && !hasInitialized.current) { + setSelectedEndTimeIndex(initialEndTimeIndex); + setEndTimeIndex(initialEndTimeIndex); + booking.setSelectedEndIndex(initialEndTimeIndex); hasInitialized.current = true; } - }, [initialLength, startTimeIndex, setEndTimeIndex, booking]); + }, [initialEndTimeIndex, setEndTimeIndex, booking]); - const bookingLengths = [ - { value: 1, label: "30 min" }, - { value: 2, label: "1 h" }, - { value: 3, label: "1.5 h" }, - { value: 4, label: "2 h" }, - { value: 5, label: "2.5 h" }, - { value: 6, label: "3 h" }, - { value: 7, label: "3.5 h" }, - { value: 8, label: "4 h" }, - ]; - - function getLabelFromAvailableHours(availableHours) { - return bookingLengths.find(option => option.value === availableHours)?.label || "Välj längd"; + // Generate end time options based on available hours + const endTimeOptions = []; + const disabledOptions = {}; + + // Always show all possible options up to the original available hours, not limited by current selection + const maxOptions = Math.min(originalHoursAvailable.current, 8); + console.log('hoursAvailable:', hoursAvailable, 'originalHoursAvailable:', originalHoursAvailable.current, 'maxOptions:', maxOptions); + + for (let i = 1; i <= maxOptions; i++) { + const endTimeIndex = startTimeIndex + i; + const endTime = getTimeFromIndex(endTimeIndex); + const durationLabel = i === 1 ? "30 min" : + i === 2 ? "1 h" : + i === 3 ? "1.5 h" : + i === 4 ? "2 h" : + i === 5 ? "2.5 h" : + i === 6 ? "3 h" : + i === 7 ? "3.5 h" : + i === 8 ? "4 h" : `${i * 0.5} h`; + + endTimeOptions.push({ + value: endTimeIndex, + label: `${endTime} · ${durationLabel}` + }); + + disabledOptions[endTimeIndex] = false; // All available options are enabled } - const disabledOptions = { - 1: !(hoursAvailable > 0), - 2: !(hoursAvailable > 1), - 3: !(hoursAvailable > 2), - 4: !(hoursAvailable > 3), - 5: !(hoursAvailable > 4), - 6: !(hoursAvailable > 5), - 7: !(hoursAvailable > 6), - 8: !(hoursAvailable > 7), - }; - function handleChange(event) { - const lengthValue = event.target.value === "" ? null : parseInt(event.target.value); - console.log(event.target.value); - setSelectedLength(lengthValue); + const endTimeValue = event.target.value === "" ? null : parseInt(event.target.value); + console.log('Selected end time value:', endTimeValue, 'Previous:', selectedEndTimeIndex); + setSelectedEndTimeIndex(endTimeValue); - if (lengthValue !== null) { - const newEndTime = startTimeIndex + lengthValue; - setCalculatedEndTime(newEndTime); - setEndTimeIndex(newEndTime); - booking.setSelectedEndIndex(newEndTime); + if (endTimeValue !== null) { + setEndTimeIndex(endTimeValue); + booking.setSelectedEndIndex(endTimeValue); + // Update the selected booking length in context so it doesn't interfere + const newLength = endTimeValue - startTimeIndex; + booking.setSelectedBookingLength && booking.setSelectedBookingLength(newLength); } else { // Reset to default state when placeholder is selected - setCalculatedEndTime(startTimeIndex); setEndTimeIndex(startTimeIndex); booking.setSelectedEndIndex(null); + booking.setSelectedBookingLength && booking.setSelectedBookingLength(0); } } - // Check if user has selected a booking length (including pre-selected) - const hasSelectedLength = selectedLength !== null; + // Check if user has selected an end time (including pre-selected) + const hasSelectedEndTime = selectedEndTimeIndex !== null; - // Display time range - show calculated end time if length is selected - const displayEndTime = hasSelectedLength ? calculatedEndTime : startTimeIndex; + // Calculate duration in hours for display + const calculateDuration = (endIndex) => { + const durationSlots = endIndex - startTimeIndex; + return durationSlots * 0.5; // Each slot is 30 minutes + }; + + const handleNavigateToDetails = () => { + console.log('handleNavigateToDetails called', { hasSelectedEndTime, onNavigateToDetails }); + + onNavigateToDetails(); + /* + if (hasSelectedEndTime) { + // Close modal first, then navigate + onClose && onClose(); + setTimeout(() => { + onNavigateToDetails && onNavigateToDetails(); + }, 100); + } + */ + }; return ( @@ -94,68 +121,49 @@ export function BookingModal({ isDismissable onOpenChange={(open) => !open && onClose && onClose()} className={className} - style={{borderRadius: '0.4rem', overflow: 'hidden'}} + style={{borderRadius: '0.4rem', overflow: 'visible'}} > - +
- {booking.title == "" ? getDefaultBookingTitle() : booking.title} -

{convertDateObjectToString(booking.selectedDate)}

-
-
-
- - {getTimeFromIndex(startTimeIndex)} -
-
-
- - - {hasSelectedLength ? getTimeFromIndex(displayEndTime) : "Välj längd"} - +
+ Välj sluttid +

{convertDateObjectToString(booking.selectedDate)}

+
+
+
+ +
{getTimeFromIndex(startTimeIndex)}
+
+
+ + +
-
- - -
- -
- -

{booking.selectedRoom !== "allRooms" ? booking.selectedRoom : (booking.assignedRoom || 'Inget rum tilldelat')}

-
- -
- -

- {(() => { - const currentUser = getCurrentUser(); - const allParticipants = [currentUser, ...booking.participants.filter(p => p.id !== currentUser.id)]; - return allParticipants.map(p => p.name).join(", "); - })()} -

-
- + {hasSelectedEndTime ? 'Nästa' : 'Välj sluttid först'} +
diff --git a/my-app/src/components/booking/BookingModal.module.css b/my-app/src/components/booking/BookingModal.module.css index 6f6e4b1..b45b2d0 100644 --- a/my-app/src/components/booking/BookingModal.module.css +++ b/my-app/src/components/booking/BookingModal.module.css @@ -139,54 +139,148 @@ /* New time display styles */ .timeDisplay { margin: 1rem 0; - padding: 1rem; - background-color: var(--modal-display-bg); - border-radius: 8px; - border: 1px solid var(--modal-display-border); min-width: 196px; width: fit-content; } .timeRange { display: flex; - align-items: center; - justify-content: space-between; + flex-direction: column; gap: 1rem; } -.startTime, .endTime { +.startTimeSection { display: flex; flex-direction: column; - align-items: center; + width: fit-content; } -.startTime label, .endTime label { - font-size: 0.75rem; - color: var(--text-secondary); - font-weight: 500; - margin-bottom: 0.25rem; - text-transform: uppercase; - letter-spacing: 0.5px; +.endTimeSection { + display: flex; + flex-direction: column; + width: fit-content; } -.timeValue { - font-size: 1.5rem; - font-weight: 600; +.startTimeSection label, .endTimeSection label { + font-size: 0.8rem; + color: var(--text-tertiary); +} + +.startTimeValue { + margin: 0; + font-size: 1.8rem; + font-weight: 400; color: var(--text-primary); } -.timeValue.placeholder { - color: var(--text-muted); - font-style: italic; +.timeSeparator { + font-size: 2.5rem; + font-weight: 300; + color: var(--text-secondary); + margin: 0 0.75rem; + padding-top: 1.5rem; +} + +/* Custom End Time Dropdown */ +.customEndTimeDropdown { + position: relative; + min-width: fit-content; +} + +.endTimeButton { + background: var(--input-bg); + border: 1px solid var(--input-border); + border-radius: 0.375rem; + padding: 0.75rem 2.5rem 0.75rem 1rem; + cursor: pointer; + font-size: 1.8rem; + text-align: center; + min-width: 200px; + position: relative; + transition: all 0.2s ease; +} + +.endTimeButton:hover { + border-color: var(--color-primary); +} + +.endTimeButton:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 0 2px var(--color-primary-light); +} + +.endTimeButton::after { + content: '▼'; + position: absolute; + right: 1rem; + top: 50%; + transform: translateY(-50%); + font-size: 0.8rem; + color: var(--dropdown-chevron-color); +} + +.timeText { + font-weight: 700; + color: var(--text-primary); + font-feature-settings: 'tnum'; +} + +.durationText { + font-weight: 400; + color: var(--text-tertiary); + margin-left: 0.5rem; +} + +.placeholderText { + font-weight: 400; + color: var(--text-secondary); font-size: 1rem; } -.timeSeparator { - font-size: 1.5rem; +.endTimeOptionsDropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + background: var(--input-bg); + border: 1px solid var(--input-border); + border-radius: 0.375rem; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + z-index: 1000; + max-height: 200px; + overflow-y: auto; + margin-top: 4px; +} + +.endTimeOption { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + background: none; + border: none; + padding: 0.75rem 1rem; + cursor: pointer; + text-align: left; + transition: background-color 0.2s ease; +} + +.endTimeOption:hover { + background: var(--bg-secondary); +} + +.optionTime { + font-weight: 700; + color: var(--text-primary); + font-feature-settings: 'tnum'; + font-size: 1.1rem; +} + +.optionDuration { font-weight: 400; - color: var(--text-secondary); - margin: 0 0.5rem; - padding-top: 1.3rem; + color: var(--text-tertiary); + font-size: 0.9rem; } /* Disabled button styles */ @@ -210,4 +304,51 @@ .disabledButton:active { background-color: var(--button-disabled-bg) !important; transform: none !important; +} + +.bookingForms { + display: flex; + flex-direction: column; + gap: 1rem; + margin: 1rem 0; +} + +/* Consistent modal sizing */ +:global(.react-aria-ModalOverlay .react-aria-Modal.react-aria-Modal) { + height: 550px !important; + width: 400px !important; + max-width: 90vw !important; + min-height: 550px !important; + max-height: 550px !important; +} + +:global(.react-aria-ModalOverlay .react-aria-Modal.react-aria-Modal form) { + height: 100% !important; + width: 100% !important; + display: flex; + flex-direction: column; + margin: 0; + padding: 0; +} + +.modalContent { + height: 450px; + overflow-y: auto; + padding: 1.5rem; + flex-shrink: 0; +} + +.modalFooter { + height: fit-content; + width: 100%; + color: var(--color-primary); + display: flex; + align-items: center; + gap: 1rem; + margin: 0; + padding-top: 1rem; + border-top: 1px solid var(--border-light); + flex-shrink: 0; + position: relative; + z-index: 1; } \ No newline at end of file diff --git a/my-app/src/components/booking/InlineBookingForm.module.css b/my-app/src/components/booking/InlineBookingForm.module.css index 1f78cc8..15adace 100644 --- a/my-app/src/components/booking/InlineBookingForm.module.css +++ b/my-app/src/components/booking/InlineBookingForm.module.css @@ -1,10 +1,10 @@ .inlineForm { - background: white; - border: 1px solid #D1D5DB; - border-radius: 0.5rem; - padding: 1.5rem; - margin: 1rem 0; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + background: var(--modal-bg); + border: 1px solid var(--border-light); + border-radius: var(--border-radius-lg); + padding: var(--spacing-2xl); + margin: var(--spacing-lg) 0; + box-shadow: var(--shadow-lg); animation: slideDown 0.2s ease-out; width: 100%; flex-basis: 100%; @@ -22,7 +22,7 @@ height: 0; border-left: 8px solid transparent; border-right: 8px solid transparent; - border-bottom: 8px solid #D1D5DB; + border-bottom: 8px solid var(--border-light); } .arrowLeft::after { @@ -34,7 +34,7 @@ height: 0; border-left: 7px solid transparent; border-right: 7px solid transparent; - border-bottom: 7px solid white; + border-bottom: 7px solid var(--modal-bg); } /* Arrow pointing to right card */ @@ -47,7 +47,7 @@ height: 0; border-left: 8px solid transparent; border-right: 8px solid transparent; - border-bottom: 8px solid #D1D5DB; + border-bottom: 8px solid var(--border-light); } .arrowRight::after { @@ -59,18 +59,18 @@ height: 0; border-left: 7px solid transparent; border-right: 7px solid transparent; - border-bottom: 7px solid white; + border-bottom: 7px solid var(--modal-bg); } .formHeader { text-align: center; - margin-bottom: 1rem; - padding-bottom: 0.75rem; - border-bottom: 1px solid #E5E7EB; + margin-bottom: var(--spacing-lg); + padding-bottom: var(--spacing-md); + border-bottom: 1px solid var(--border-light); } .section { - margin-bottom: 1.5rem; + margin-bottom: var(--spacing-2xl); } .section:last-of-type { @@ -78,86 +78,87 @@ } .formHeader h3 { - margin: 0 0 0.5rem 0; - font-size: 1.25rem; - font-weight: 700; - color: #111827; + margin: 0 0 var(--spacing-sm) 0; + font-size: var(--font-size-3xl); + font-weight: var(--font-weight-bold); + color: var(--text-primary); } .dateText { margin: 0; - color: #6B7280; - font-size: 0.875rem; + color: var(--text-secondary); + font-size: var(--font-size-sm); } .timeDisplay { - margin-bottom: 1rem; + margin-bottom: var(--spacing-lg); } .timeRange { display: flex; align-items: center; justify-content: center; - gap: 1rem; - padding: 1rem; - background: #F9FAFB; - border-radius: 0.375rem; + gap: var(--spacing-lg); + padding: var(--spacing-lg); + background: var(--modal-display-bg); + border: 1px solid var(--modal-display-border); + border-radius: var(--border-radius-md); } .timeItem { display: flex; flex-direction: column; align-items: center; - gap: 0.25rem; + gap: var(--spacing-xs); } .timeItem label { - font-size: 0.75rem; - color: #6B7280; - font-weight: 500; + font-size: var(--font-size-xs); + color: var(--text-secondary); + font-weight: var(--font-weight-medium); text-transform: uppercase; letter-spacing: 0.05em; } .timeValue { - font-size: 1.25rem; - font-weight: 600; - color: #111827; + font-size: var(--font-size-3xl); + font-weight: var(--font-weight-semibold); + color: var(--text-primary); } .timeValue.placeholder { - color: #9CA3AF; + color: var(--text-tertiary); font-style: italic; } .timeSeparator { - font-size: 1.5rem; - color: #6B7280; - font-weight: 300; + font-size: var(--font-size-4xl); + color: var(--text-secondary); + font-weight: var(--font-weight-light); } .formField { - margin-bottom: 1rem; + margin-bottom: var(--spacing-lg); } .formField label { display: block; - font-size: 0.875rem; - font-weight: 500; - color: #374151; - margin-bottom: 0.5rem; + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--text-primary); + margin-bottom: var(--spacing-sm); } .sectionWithTitle { - padding-top: 1rem; + padding-top: var(--spacing-lg); display: flex; flex-direction: column; width: fit-content; } .sectionWithTitle label { - font-size: 0.8rem; - color: #717171; + font-size: var(--font-size-xs); + color: var(--text-secondary); } .sectionWithTitle p { @@ -166,77 +167,77 @@ .formActions { display: flex; - gap: 1rem; - margin-top: 2rem; - padding-top: 1.5rem; - border-top: 1px solid #E5E7EB; + gap: var(--spacing-lg); + margin-top: var(--spacing-3xl); + padding-top: var(--spacing-2xl); + border-top: 1px solid var(--border-light); } .cancelButton { flex: 1; - background-color: white; + background-color: var(--modal-cancel-bg); height: 2.75rem; - color: #374151; - font-weight: 600; - border: 2px solid #d1d5db; - border-radius: 0.375rem; - transition: all 0.2s ease; + color: var(--modal-cancel-text); + font-weight: var(--font-weight-semibold); + border: 2px solid var(--modal-cancel-border); + border-radius: var(--border-radius-md); + transition: var(--transition-medium); cursor: pointer; - font-size: 0.875rem; + font-size: var(--font-size-sm); } .cancelButton:hover { - background-color: #f9fafb; - border-color: #9ca3af; + background-color: var(--modal-cancel-hover-bg); + border-color: var(--modal-cancel-hover-border); } .cancelButton:active { - background-color: #e5e7eb; + background-color: var(--modal-cancel-active-bg); transform: translateY(1px); } .saveButton { flex: 2; - background-color: #059669; - color: white; + background-color: var(--modal-save-bg); + color: var(--modal-save-text); height: 2.75rem; - font-weight: 600; - font-size: 0.875rem; - border: 2px solid #047857; - border-radius: 0.375rem; - transition: all 0.2s ease; - box-shadow: 0 2px 4px rgba(5, 150, 105, 0.2); + font-weight: var(--font-weight-semibold); + font-size: var(--font-size-sm); + border: 2px solid var(--modal-save-border); + border-radius: var(--border-radius-md); + transition: var(--transition-medium); + box-shadow: var(--modal-save-shadow); cursor: pointer; } .saveButton:hover { - background-color: #047857; - box-shadow: 0 4px 8px rgba(5, 150, 105, 0.3); + background-color: var(--modal-save-hover-bg); + box-shadow: var(--modal-save-hover-shadow); } .saveButton:active { - background-color: #065f46; + background-color: var(--modal-save-active-bg); transform: translateY(1px); - box-shadow: 0 1px 2px rgba(5, 150, 105, 0.2); + box-shadow: var(--modal-save-active-shadow); } .disabledButton { - background-color: #f8f9fa !important; - color: #adb5bd !important; - border: 2px dashed #dee2e6 !important; + background-color: var(--button-disabled-bg) !important; + color: var(--button-disabled-text) !important; + border: 2px dashed var(--button-disabled-border) !important; opacity: 0.6 !important; box-shadow: none !important; cursor: default !important; } .disabledButton:hover { - background-color: #f8f9fa !important; + background-color: var(--button-disabled-bg) !important; transform: none !important; box-shadow: none !important; } .disabledButton:active { - background-color: #f8f9fa !important; + background-color: var(--button-disabled-bg) !important; transform: none !important; } diff --git a/my-app/src/components/booking/InlineModalBookingForm.jsx b/my-app/src/components/booking/InlineModalBookingForm.jsx new file mode 100644 index 0000000..d459a39 --- /dev/null +++ b/my-app/src/components/booking/InlineModalBookingForm.jsx @@ -0,0 +1,136 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Button } from 'react-aria-components'; +import { convertDateObjectToString, getTimeFromIndex } from '../../helpers'; +import Dropdown from '../ui/Dropdown'; +import { useBookingContext } from '../../context/BookingContext'; +import { useSettingsContext } from '../../context/SettingsContext'; +import styles from './InlineModalBookingForm.module.css'; + +export function InlineModalBookingForm({ + startTimeIndex, + hoursAvailable, + endTimeIndex, + setEndTimeIndex, + onClose, + onNavigateToDetails, + arrowPointsLeft = true +}) { + const booking = useBookingContext(); + const { getCurrentUser, getDefaultBookingTitle } = useSettingsContext(); + + // Initialize with pre-selected end time if available, or auto-select if only 30 min available + const initialEndTimeIndex = booking.selectedBookingLength > 0 ? startTimeIndex + booking.selectedBookingLength : + (hoursAvailable === 1 ? startTimeIndex + 1 : null); // Auto-select 30 min if that's all that's available + const [selectedEndTimeIndex, setSelectedEndTimeIndex] = useState(null); + const hasInitialized = useRef(false); + + // Store the original hours available to prevent it from changing when selections are made + const originalHoursAvailable = useRef(hoursAvailable); + if (originalHoursAvailable.current < hoursAvailable) { + originalHoursAvailable.current = hoursAvailable; + } + + // Effect to handle initial setup only once when form opens + useEffect(() => { + if (initialEndTimeIndex && !hasInitialized.current) { + setSelectedEndTimeIndex(initialEndTimeIndex); + setEndTimeIndex(initialEndTimeIndex); + booking.setSelectedEndIndex(initialEndTimeIndex); + hasInitialized.current = true; + } + }, [initialEndTimeIndex, setEndTimeIndex, booking]); + + // Generate end time options based on available hours + const endTimeOptions = []; + const disabledOptions = {}; + + // Always show all possible options up to the original available hours, not limited by current selection + const maxOptions = Math.min(originalHoursAvailable.current, 8); + + for (let i = 1; i <= maxOptions; i++) { + const endTimeIndex = startTimeIndex + i; + const endTime = getTimeFromIndex(endTimeIndex); + const durationLabel = i === 1 ? "30 min" : + i === 2 ? "1 h" : + i === 3 ? "1.5 h" : + i === 4 ? "2 h" : + i === 5 ? "2.5 h" : + i === 6 ? "3 h" : + i === 7 ? "3.5 h" : + i === 8 ? "4 h" : `${i * 0.5} h`; + + endTimeOptions.push({ + value: endTimeIndex, + label: `${endTime} · ${durationLabel}` + }); + + disabledOptions[endTimeIndex] = false; // All available options are enabled + } + + function handleChange(event) { + const endTimeValue = event.target.value === "" ? null : parseInt(event.target.value); + setSelectedEndTimeIndex(endTimeValue); + + if (endTimeValue !== null) { + setEndTimeIndex(endTimeValue); + booking.setSelectedEndIndex(endTimeValue); + // Update the selected booking length in context so it doesn't interfere + const newLength = endTimeValue - startTimeIndex; + booking.setSelectedBookingLength && booking.setSelectedBookingLength(newLength); + } else { + // Reset to default state when placeholder is selected + setEndTimeIndex(startTimeIndex); + booking.setSelectedEndIndex(null); + booking.setSelectedBookingLength && booking.setSelectedBookingLength(0); + } + } + + // Check if user has selected an end time (including pre-selected) + const hasSelectedEndTime = selectedEndTimeIndex !== null; + + const handleNavigateToDetails = () => { + if (hasSelectedEndTime) { + onNavigateToDetails && onNavigateToDetails(); + } + }; + + return ( +
+ {/* Header */} +
+ {/*

Välj sluttid

*/} + {/* Time Selection */} +
+
+ {/**/} + +
+
+
+ + {/* Actions */} +
+ + +
+
+ ); +} \ No newline at end of file diff --git a/my-app/src/components/booking/InlineModalBookingForm.module.css b/my-app/src/components/booking/InlineModalBookingForm.module.css new file mode 100644 index 0000000..17294bc --- /dev/null +++ b/my-app/src/components/booking/InlineModalBookingForm.module.css @@ -0,0 +1,192 @@ +.inlineForm { + background: var(--modal-bg); + border: 1px solid var(--border-light); + border-radius: var(--border-radius-lg); + padding: var(--spacing-2xl); + margin: var(--spacing-lg) 0; + box-shadow: var(--shadow-lg); + animation: slideDown 0.2s ease-out; + width: 100%; + flex-basis: 100%; + max-width: none; + position: relative; +} + +/* Arrow pointing to left card */ +.arrowLeft::before { + content: ''; + position: absolute; + top: -8px; + left: 75px; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid var(--border-light); +} + +.arrowLeft::after { + content: ''; + position: absolute; + top: -7px; + left: 76px; + width: 0; + height: 0; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid var(--modal-bg); +} + +/* Arrow pointing to right card */ +.arrowRight::before { + content: ''; + position: absolute; + top: -8px; + right: 75px; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid var(--border-light); +} + +.arrowRight::after { + content: ''; + position: absolute; + top: -7px; + right: 76px; + width: 0; + height: 0; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid var(--modal-bg); +} + +.formHeader { + text-align: center; + /*margin-bottom: var(--spacing-2xl);*/ + /*padding-bottom: var(--spacing-lg);*/ + /*border-bottom: 1px solid var(--border-light);*/ +} + +.formTitle { + margin: 0 0 var(--spacing-sm) 0; + font-size: var(--font-size-3xl); + font-weight: var(--font-weight-bold); + color: var(--text-primary); +} + +.dateText { + margin: 0; + color: var(--text-secondary); + font-size: var(--font-size-sm); +} + +.section { + margin-bottom: var(--spacing-2xl); +} + +.formField { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.formField label { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--text-primary); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.endTimeDropdown { + width: 100%; +} + +.formActions { + display: flex; + gap: var(--spacing-lg); + /*margin-top: var(--spacing-3xl);*/ + /*padding-top: var(--spacing-2xl);*/ + /*border-top: 1px solid var(--border-light);*/ +} + +.cancelButton { + flex: 1; + background-color: var(--modal-cancel-bg); + height: 2.75rem; + color: var(--modal-cancel-text); + font-weight: var(--font-weight-semibold); + border: 2px solid var(--modal-cancel-border); + border-radius: var(--border-radius-md); + transition: var(--transition-medium); + cursor: pointer; + font-size: var(--font-size-sm); +} + +.cancelButton:hover { + background-color: var(--modal-cancel-hover-bg); + border-color: var(--modal-cancel-hover-border); +} + +.cancelButton:active { + background-color: var(--modal-cancel-active-bg); + transform: translateY(1px); +} + +.saveButton { + flex: 2; + background-color: var(--modal-save-bg); + color: var(--modal-save-text); + height: 2.75rem; + font-weight: var(--font-weight-semibold); + font-size: var(--font-size-sm); + border: 2px solid var(--modal-save-border); + border-radius: var(--border-radius-md); + transition: var(--transition-medium); + box-shadow: var(--modal-save-shadow); + cursor: pointer; +} + +.saveButton:hover { + background-color: var(--modal-save-hover-bg); + box-shadow: var(--modal-save-hover-shadow); +} + +.saveButton:active { + background-color: var(--modal-save-active-bg); + transform: translateY(1px); + box-shadow: var(--modal-save-active-shadow); +} + +.disabledButton { + background-color: var(--button-disabled-bg) !important; + color: var(--button-disabled-text) !important; + border: 2px dashed var(--button-disabled-border) !important; + opacity: 0.6 !important; + box-shadow: none !important; + cursor: default !important; +} + +.disabledButton:hover { + background-color: var(--button-disabled-bg) !important; + transform: none !important; + box-shadow: none !important; +} + +.disabledButton:active { + background-color: var(--button-disabled-bg) !important; + transform: none !important; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} \ No newline at end of file diff --git a/my-app/src/components/forms/ParticipantsSelector.jsx b/my-app/src/components/forms/ParticipantsSelector.jsx index 4a7a621..9c2b5f3 100644 --- a/my-app/src/components/forms/ParticipantsSelector.jsx +++ b/my-app/src/components/forms/ParticipantsSelector.jsx @@ -76,6 +76,17 @@ export function ParticipantsSelector({ compact = false }) { itemRefs.current = []; }; + const handleInputBlur = (e) => { + // Small delay to allow click events on dropdown items to fire first + setTimeout(() => { + // Only close if the new focus target is not within our dropdown + if (!dropdownRef.current?.contains(document.activeElement)) { + setIsDropdownOpen(false); + setFocusedIndex(-1); + } + }, 150); + }; + const handleInputChange = (e) => { setSearchTerm(e.target.value); setIsDropdownOpen(true); @@ -169,6 +180,7 @@ export function ParticipantsSelector({ compact = false }) { onChange={handleInputChange} onFocus={handleInputFocus} onClick={handleInputClick} + onBlur={handleInputBlur} onKeyDown={handleKeyDown} placeholder="Search for participants..." className={compact ? styles.compactSearchInput : styles.searchInput} @@ -196,6 +208,10 @@ export function ParticipantsSelector({ compact = false }) { ref={el => itemRefs.current[index] = el} className={`${styles.dropdownItem} ${isPersonSelected(person.name) ? styles.selectedItem : ''} ${index === focusedIndex ? styles.focusedItem : ''}`} onClick={() => handleSelectPerson(person)} + onMouseDown={(e) => { + e.preventDefault(); // Prevent blur from firing + handleSelectPerson(person); + }} role="option" aria-selected={isPersonSelected(person.name)} > @@ -232,6 +248,10 @@ export function ParticipantsSelector({ compact = false }) { ref={el => itemRefs.current[index] = el} className={`${styles.dropdownItem} ${isPersonSelected(person.name) ? styles.selectedItem : ''} ${index === focusedIndex ? styles.focusedItem : ''}`} onClick={() => handleSelectPerson(person)} + onMouseDown={(e) => { + e.preventDefault(); // Prevent blur from firing + handleSelectPerson(person); + }} role="option" aria-selected={isPersonSelected(person.name)} > diff --git a/my-app/src/components/forms/ParticipantsSelector.module.css b/my-app/src/components/forms/ParticipantsSelector.module.css index ce1fc9a..796be70 100644 --- a/my-app/src/components/forms/ParticipantsSelector.module.css +++ b/my-app/src/components/forms/ParticipantsSelector.module.css @@ -1,5 +1,6 @@ .container { position: relative; + margin-bottom: 4rem; } .elementHeading { @@ -18,7 +19,6 @@ flex-wrap: wrap; gap: 0.5rem; padding: 0; - margin-top: 1rem; } .participantChip { @@ -310,4 +310,5 @@ outline: 2px solid var(--color-primary); outline-offset: 2px; border-color: var(--color-primary); -} \ No newline at end of file +} + diff --git a/my-app/src/components/layout/Navigation.jsx b/my-app/src/components/layout/Navigation.jsx index 989f446..3ac0ad7 100644 --- a/my-app/src/components/layout/Navigation.jsx +++ b/my-app/src/components/layout/Navigation.jsx @@ -19,18 +19,28 @@ const Navigation = () => { // Prevent body scroll when mobile menu is open useEffect(() => { + // Store original overflow value + const originalOverflow = document.body.style.overflow; + if (menuOpen) { document.body.style.overflow = 'hidden'; } else { - document.body.style.overflow = 'unset'; + document.body.style.overflow = originalOverflow || ''; } - // Cleanup on unmount + // Cleanup on unmount - always restore scroll return () => { - document.body.style.overflow = 'unset'; + document.body.style.overflow = originalOverflow || ''; }; }, [menuOpen]); + // Additional cleanup on component unmount + useEffect(() => { + return () => { + document.body.style.overflow = ''; + }; + }, []); + const toggleCourses = () => { setCoursesOpen(!coursesOpen); }; @@ -60,7 +70,7 @@ const Navigation = () => {
- Logo + Logo Studentportalen
diff --git a/my-app/src/components/layout/Navigation.module.css b/my-app/src/components/layout/Navigation.module.css index 872cb95..d92f9c5 100644 --- a/my-app/src/components/layout/Navigation.module.css +++ b/my-app/src/components/layout/Navigation.module.css @@ -6,6 +6,8 @@ display: flex; flex-direction: column; background-color: var(--bg-secondary); + background-color: var(--su-blue); + color: white; box-shadow: var(--shadow-md); position: fixed; top: 0; @@ -27,7 +29,6 @@ align-items: center; gap: var(--spacing-lg); font-weight: var(--font-weight-semibold); - color: var(--header-brand-color); } .right { @@ -39,18 +40,20 @@ .logo img { height: 40px; transition: filter 0.2s ease; + color: white; } .brandText { font-size: 1.1rem; font-weight: var(--font-weight-semibold); - color: var(--header-brand-color); + color: white; } .menuIcon { font-size: 24px; cursor: pointer; color: var(--text-primary); + color: white; padding: var(--spacing-xs); border-radius: var(--border-radius-md); transition: var(--transition-fast); diff --git a/my-app/src/components/ui/Card.jsx b/my-app/src/components/ui/Card.jsx index 7eca5cc..f851527 100644 --- a/my-app/src/components/ui/Card.jsx +++ b/my-app/src/components/ui/Card.jsx @@ -1,17 +1,55 @@ import React from 'react'; -import styles from './Card.module.css'; // Import the CSS Module +import styles from './Card.module.css'; + +const Card = ({ imageUrl, header, subheader, features = [], onClick, as: Component = 'div' }) => { + const cardProps = { + className: styles.card, + onClick, + ...(Component === 'div' && { + role: "button", + tabIndex: 0, + onKeyDown: (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onClick?.(e); + } + } + }) + }; -const Card = ({ imageUrl, header, subheader }) => { return ( -
-
-
-

{header}

-
-

{subheader}

+ +
+
+
+ +
+

{header}

+ {subheader && ( +

{subheader}

+ )} + {features.length > 0 && ( +
+ {features.map((feature, index) => ( +
+ {feature.icon && ( +
+ {feature.icon} +
+ )} + {feature.text} +
+ ))} +
+ )} +
+ +
+
+ →
-
+ ); }; diff --git a/my-app/src/components/ui/Card.module.css b/my-app/src/components/ui/Card.module.css index b7d443b..0a00bdf 100644 --- a/my-app/src/components/ui/Card.module.css +++ b/my-app/src/components/ui/Card.module.css @@ -1,46 +1,116 @@ .card { - width: 100%; /* Adjust width as needed */ - height: 300px; /* Adjust height as needed */ + display: flex; + background: var(--bg-primary); + border: 2px solid var(--border-light); + border-radius: var(--border-radius-xl); + overflow: hidden; + cursor: pointer; + transition: all var(--transition-medium); + box-shadow: var(--shadow-sm); + text-decoration: none; + color: inherit; +} + +.card:hover { + border-color: var(--color-primary); + box-shadow: var(--shadow-lg); + transform: translateY(-1px); +} + +.card:focus { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + +.imageSection { + width: 120px; + min-width: 120px; + height: 100px; background-size: cover; background-position: center; + background-color: var(--bg-secondary); position: relative; - display: flex; - align-items: center; - justify-content: center; } -.gradientOverlay { +.imageOverlay { position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient(to bottom, transparent, #05305E); - display: flex; - align-items: center; - justify-content: center; + inset: 0; + background: linear-gradient(45deg, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.05)); } -.textContainer { - text-align: center; - color: white; - padding: 20px; +.contentSection { + flex: 1; + padding: var(--spacing-lg); + display: flex; + flex-direction: column; + justify-content: center; + gap: var(--spacing-sm); } .header { margin: 0; - font-size: 2em; /* Adjust font size as needed */ -} - -.line { - width: 100%; - height: 3px; /* Adjust thickness as needed */ - background-color: white; - margin: 10px auto; + font-size: var(--font-size-2xl); + font-weight: var(--font-weight-semibold); + color: var(--text-primary); + line-height: var(--line-height-tight); } .subheader { margin: 0; - font-size: 1.2em; /* Adjust font size as needed */ - font-weight: normal; + font-size: var(--font-size-md); + font-weight: var(--font-weight-normal); + color: var(--text-secondary); + line-height: var(--line-height-normal); +} + +.features { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-sm); + margin-top: var(--spacing-xs); +} + +.feature { + display: flex; + align-items: center; + gap: var(--spacing-xs); + font-size: var(--font-size-sm); + color: var(--text-tertiary); + padding: var(--spacing-xs) var(--spacing-sm); + background: var(--bg-secondary); + border-radius: var(--border-radius-sm); +} + +.featureIcon { + width: 14px; + height: 14px; + color: var(--color-primary); +} + +.actionSection { + width: 60px; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, var(--color-primary), var(--color-primary-hover)); + position: relative; +} + +.actionSection::before { + content: ''; + position: absolute; + inset: 0; + background: rgba(255, 255, 255, 0.1); + opacity: 0; + transition: opacity var(--transition-fast); +} + +.card:hover .actionSection::before { + opacity: 1; +} + +.actionIcon { + width: 24px; + height: 24px; + color: white; } diff --git a/my-app/src/components/ui/Dropdown.jsx b/my-app/src/components/ui/Dropdown.jsx index 5807b2f..04fa872 100644 --- a/my-app/src/components/ui/Dropdown.jsx +++ b/my-app/src/components/ui/Dropdown.jsx @@ -3,9 +3,9 @@ import styles from "./Dropdown.module.css"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faChevronDown } from '@fortawesome/free-solid-svg-icons' -const Dropdown = ({ options, value, onChange, placeholder = {value: "", label: "Select an option"}, disabledOptions = false }) => { +const Dropdown = ({ options, value, onChange, placeholder = {value: "", label: "Select an option"}, disabledOptions = false, className }) => { return ( -
+
updateSettings({ bookingFormType: e.target.value })} className={styles.select} > - + +
- Current: {settings.bookingFormType === 'inline' ? 'Inline Form' : 'Modal Popup'} + Current: + {settings.bookingFormType === 'inline' ? 'Inline Form' : + settings.bookingFormType === 'modal' ? 'Modal Popup' : + 'Inline Modal'} + +
+
+ Inline Form: All fields in one form
+ Modal Popup: Time selection in popup, then details page
+ Inline Modal: Time selection inline, then details page
diff --git a/my-app/src/pages/NewBooking.jsx b/my-app/src/pages/NewBooking.jsx index 80c79b0..e3dcf41 100644 --- a/my-app/src/pages/NewBooking.jsx +++ b/my-app/src/pages/NewBooking.jsx @@ -2,8 +2,6 @@ import React, { useState, useEffect } from 'react'; import styles from './NewBooking.module.css'; import { TimeCardContainer } from '../components/ui/TimeCardContainer'; import { BookingDatePicker } from '../components/forms/BookingDatePicker'; -import { BookingTitleField } from '../components/forms/BookingTitleField'; -import { ParticipantsSelector } from '../components/forms/ParticipantsSelector'; import { RoomSelectionField } from '../components/forms/RoomSelectionField'; import { BookingLengthField } from '../components/forms/BookingLengthField'; import { useBookingState } from '../hooks/useBookingState'; @@ -64,13 +62,6 @@ export function NewBooking({ addBooking }) {

Boka litet grupprum

- {/* Only show title and participants fields in modal mode */} - {!useInlineForm && ( - <> - - - - )}
diff --git a/my-app/src/pages/RoomBooking.jsx b/my-app/src/pages/RoomBooking.jsx index 6769ddf..2a2c7cf 100644 --- a/my-app/src/pages/RoomBooking.jsx +++ b/my-app/src/pages/RoomBooking.jsx @@ -10,6 +10,8 @@ export function RoomBooking({ bookings, showSuccessBanner, lastCreatedBooking, o const { settings } = useSettingsContext(); useEffect(() => { + // Ensure body scroll is enabled and scroll to top + document.body.style.overflow = ''; window.scrollTo(0, 0); }, []); @@ -31,6 +33,10 @@ export function RoomBooking({ bookings, showSuccessBanner, lastCreatedBooking, o
)}

Lokalbokning

+

Ny bokning

+ + +

Mina bokingar


-

Ny bokning

- - -
); } \ No newline at end of file diff --git a/my-app/src/react-aria-starter/src/Calendar.css b/my-app/src/react-aria-starter/src/Calendar.css index b7ad985..5bf1f90 100644 --- a/my-app/src/react-aria-starter/src/Calendar.css +++ b/my-app/src/react-aria-starter/src/Calendar.css @@ -4,7 +4,7 @@ .react-aria-Calendar { width: fit-content; max-width: 100%; - color: var(--text-color); + color: var(--text-primary); header { display: flex; @@ -44,46 +44,46 @@ } &:hover:not([data-selected]):not([data-disabled]):not([data-unavailable]) { - background-color: var(--highlight-hover); + background-color: var(--bg-muted); } &[data-pressed] { - background: var(--gray-100); + background: var(--bg-tertiary); } &[data-focus-visible] { - outline: 2px solid var(--focus-ring-color); + outline: 2px solid var(--color-primary); outline-offset: 2px; } &[data-selected] { - background: var(--highlight-background); - color: var(--highlight-foreground); + background: var(--color-primary); + color: var(--color-white); } } .react-aria-CalendarCell { &[data-disabled] { - color: var(--text-color-disabled); + color: var(--text-disabled); } } .react-aria-CalendarCell { &[data-unavailable] { text-decoration: line-through; - color: var(--text-color-disabled); + color: var(--text-disabled); } } .react-aria-CalendarCell { &[data-invalid] { - background: var(--invalid-color); - color: var(--highlight-foreground); + background: var(--notification-error-bg); + color: var(--notification-error-title); } } [slot=errorMessage] { font-size: 12px; - color: var(--invalid-color); + color: var(--notification-error-title); } } diff --git a/my-app/src/react-aria-starter/src/DatePicker.css b/my-app/src/react-aria-starter/src/DatePicker.css index aeea3dd..48d02c1 100644 --- a/my-app/src/react-aria-starter/src/DatePicker.css +++ b/my-app/src/react-aria-starter/src/DatePicker.css @@ -7,7 +7,7 @@ @import "./theme.css"; .react-aria-DatePicker { - color: var(--text-color); + color: var(--text-primary); background-color: var(--bg-secondary); padding: var(--spacing-md); border: 1px solid var(--border-light); @@ -39,38 +39,34 @@ justify-content: center; border-radius: 4px; transition: background-color 0.2s, opacity 0.2s, color 0.2s; - color: var(--chevron-button-color); + color: var(--text-primary); } .chevron-button:hover:not(:disabled) { - background-color: var(--highlight-hover); + background-color: var(--bg-muted); } .chevron-button:active:not(:disabled) { - background-color: var(--highlight-pressed); + background-color: var(--bg-tertiary); } .chevron-button:disabled { cursor: default; - color: var(--chevron-button-disabled-color); + color: var(--text-disabled); opacity: 0.4; } .chevron-button:focus-visible { - outline: 2px solid var(--focus-ring-color); + outline: 2px solid var(--color-primary); outline-offset: 2px; } .react-aria-Button { - /*background: var(--highlight-background);*/ - /*color: var(--highlight-foreground);*/ - border: 2px solid var(--field-background); + background: var(--button-secondary-bg); + color: var(--button-secondary-text); + border: 1px solid var(--border-light); forced-color-adjust: none; - border-radius: 4px; - /*border: none;*/ - border: 1px solid var(--border-color); - /*width: 1.429rem;*/ - /*height: 1.429rem;*/ + border-radius: var(--border-radius-sm); width: fit-content; padding: 0.5rem 1rem; font-size: 1rem; @@ -78,12 +74,11 @@ &[data-pressed] { box-shadow: none; - /*background: var(--highlight-background);*/ - background: var(--button-background-pressed); + background: var(--button-secondary-hover-bg); } &[data-focus-visible] { - outline: 2px solid var(--focus-ring-color); + outline: 2px solid var(--color-primary); outline-offset: 2px; } } @@ -95,12 +90,12 @@ justify-content: space-between !important; gap: 0.75rem !important; cursor: pointer !important; - background: var(--field-background) !important; - border: 1px solid var(--border-color) !important; + background: var(--input-bg) !important; + border: 1px solid var(--input-border) !important; border-radius: 8px !important; padding: 12px 16px !important; font-weight: 500 !important; - color: var(--field-text-color) !important; + color: var(--input-text) !important; transition: all 0.2s ease !important; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important; white-space: nowrap !important; @@ -113,21 +108,21 @@ } .calendar-button:hover { - border-color: var(--border-color-hover) !important; + border-color: var(--color-primary) !important; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; } .calendar-button[data-pressed] { - background: var(--button-background-pressed) !important; - border-color: var(--border-color-pressed) !important; + background: var(--button-secondary-hover-bg) !important; + border-color: var(--color-primary) !important; transform: translateY(1px) !important; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important; } .calendar-button[data-focus-visible] { - outline: 2px solid var(--focus-ring-color) !important; + outline: 2px solid var(--color-primary) !important; outline-offset: 2px !important; - border-color: var(--focus-ring-color) !important; + border-color: var(--color-primary) !important; } .calendar-date { @@ -144,8 +139,6 @@ .react-aria-Popover[data-trigger=DatePicker] { max-width: unset; - transform: translateX(-50%); - left: 50% !important; } .react-aria-DatePicker { @@ -161,7 +154,7 @@ .react-aria-FieldError { font-size: 12px; - color: var(--invalid-color); + color: var(--notification-error-title); } [slot=description] { diff --git a/my-app/src/react-aria-starter/src/DatePicker.tsx b/my-app/src/react-aria-starter/src/DatePicker.tsx index a07e62c..0b1368d 100644 --- a/my-app/src/react-aria-starter/src/DatePicker.tsx +++ b/my-app/src/react-aria-starter/src/DatePicker.tsx @@ -90,7 +90,7 @@ export function DatePicker( {description && {description}} {errorMessage} - +
diff --git a/my-app/src/react-aria-starter/src/Dialog.css b/my-app/src/react-aria-starter/src/Dialog.css index a5051ae..867eaa8 100644 --- a/my-app/src/react-aria-starter/src/Dialog.css +++ b/my-app/src/react-aria-starter/src/Dialog.css @@ -5,10 +5,10 @@ .react-aria-Dialog { outline: none; - padding: 30px; max-height: inherit; box-sizing: border-box; overflow: auto; + padding: 2rem; .react-aria-Heading[slot=title] { line-height: 1em; diff --git a/my-app/src/react-aria-starter/src/Popover.css b/my-app/src/react-aria-starter/src/Popover.css index 0851134..a154fbb 100644 --- a/my-app/src/react-aria-starter/src/Popover.css +++ b/my-app/src/react-aria-starter/src/Popover.css @@ -4,21 +4,19 @@ @import "./theme.css"; .react-aria-Popover { - --background-color: var(--overlay-background); - - border: 1px solid var(--border-color); - box-shadow: 0 8px 20px rgba(0 0 0 / 0.1); - border-radius: 6px; - background: var(--background-color); - color: var(--text-color); + border: 1px solid var(--border-light); + box-shadow: var(--shadow-xl); + border-radius: var(--border-radius-md); + background: var(--bg-primary); + color: var(--text-primary); outline: none; max-width: 250px; transition: transform 200ms, opacity 200ms; .react-aria-OverlayArrow svg { display: block; - fill: var(--background-color); - stroke: var(--border-color); + fill: var(--bg-primary); + stroke: var(--border-light); stroke-width: 1px; } diff --git a/my-app/src/styles/variables.css b/my-app/src/styles/variables.css index a4872e7..cf0af00 100644 --- a/my-app/src/styles/variables.css +++ b/my-app/src/styles/variables.css @@ -354,6 +354,47 @@ --loader-text-color: #333; --loader-border: rgba(0, 0, 0, 0.1); --loader-shadow: 0 20px 40px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.06); + + /* === STOCKHOLM UNIVERSITY BRAND COLORS === */ + + /* Primary university color */ + --su-blue: #002F5F; + --su-blue-80: #33587F; + + /* Secondary colors - Sky/Himmel */ + --su-sky: #ACDEE6; + --su-sky-70: #C4E8ED; + --su-sky-35: #E3F4F7; + --su-sky-20: #EEF9FA; + + /* Secondary colors - Water/Vatten */ + --su-water: #9BB2CE; + --su-water-70: #B8C9DC; + --su-water-35: #DCE4EE; + --su-water-20: #EBF0F5; + + /* Secondary colors - Fire/Eld */ + --su-fire: #EB7125; + --su-fire-70: #F19B66; + --su-fire-35: #F8CDB3; + --su-fire-20: #FBE2D3; + + /* Secondary colors - Olive/Oliv */ + --su-olive: #A3A86B; + --su-olive-70: #BEC297; + --su-olive-35: #DFE1CB; + --su-olive-20: #EDEEE1; + + /* Base colors */ + --su-dark-gray: #4B4B4B; + --su-white: #FFFFFF; + --su-medium-gray: #BABABA; + --su-light-gray: #DADADA; + + /* Utility colors - limited use */ + --su-green: #499943; + --su-red: #B00020; + --su-red-10: #F7E5E8; } /* === DARK MODE === */ @@ -494,7 +535,7 @@ --tooltip-text: #e5e7eb; /* Button colors - dark mode */ - --button-bg: #374151; + --button-bg: #28549c; --button-secondary-bg: #374151; --button-secondary-text: #e5e7eb; --button-secondary-hover-bg: #4b5563; @@ -628,4 +669,46 @@ --loader-text-color: #e5e7eb; --loader-border: rgba(255, 255, 255, 0.1); --loader-shadow: 0 20px 40px rgba(0, 0, 0, 0.4), 0 8px 16px rgba(0, 0, 0, 0.2); + + + /* === STOCKHOLM UNIVERSITY - DARK MODE TEST === */ + + /* Primary university color */ + --su-blue: #132a42; + --su-blue-80: #33587F; + + /* Secondary colors - Sky/Himmel */ + --su-sky: #5d8388; + --su-sky-70: #C4E8ED; + --su-sky-35: #E3F4F7; + --su-sky-20: #EEF9FA; + + /* Secondary colors - Water/Vatten */ + --su-water: #9BB2CE; + --su-water-70: #B8C9DC; + --su-water-35: #DCE4EE; + --su-water-20: #EBF0F5; + + /* Secondary colors - Fire/Eld */ + --su-fire: #EB7125; + --su-fire-70: #F19B66; + --su-fire-35: #F8CDB3; + --su-fire-20: #FBE2D3; + + /* Secondary colors - Olive/Oliv */ + --su-olive: #A3A86B; + --su-olive-70: #BEC297; + --su-olive-35: #DFE1CB; + --su-olive-20: #EDEEE1; + + /* Base colors */ + --su-dark-gray: #4B4B4B; + --su-white: #FFFFFF; + --su-medium-gray: #BABABA; + --su-light-gray: #DADADA; + + /* Utility colors - limited use */ + --su-green: #499943; + --su-red: #B00020; + --su-red-10: #F7E5E8; } \ No newline at end of file