Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use mouse click to add a new time slot #495

Merged
merged 1 commit into from
Nov 27, 2024

Conversation

renefs
Copy link
Contributor

@renefs renefs commented Oct 23, 2024

This issue resolves #490. Continues #494 PR.

  1. When the timeline is clicked, a new slot with the default duration is added.
  2. A tooltip is displayed on mouse hover to indicate the position in the timeline.
  3. The granularity is set to 15 minutes, but it can be changed using the time picker.
Screen.Recording.2024-10-22.at.09.28.58.mov

@renefs renefs force-pushed the simplify-slot-creation branch from 76cb1ad to f270dde Compare October 23, 2024 15:06
@tomasr8 tomasr8 self-requested a review October 23, 2024 16:04
@tomasr8
Copy link
Member

tomasr8 commented Oct 25, 2024

Thanks again for working on this! Couple more ideas for improvements:

  • The new tooltip could be centered over the grey box.
  • The grey box should align with the resulting timeslot, currently it's offset to the left.
  • The grey box shouldn't be shown when you're hovering over a time which already has a timeslot. For example, you have an existing timeslot at 10:00. If you hover at 10:00 again, the grey box should not be shown because you cannot create a second timeslot with the same start time.

image

  • When you scroll, the grey box somehow leaves the bounds of the timetable:
    image

@renefs renefs force-pushed the simplify-slot-creation branch from f270dde to b5119af Compare October 31, 2024 16:11
@renefs
Copy link
Contributor Author

renefs commented Oct 31, 2024

These glitches should be fixed on the last version, which also has the steps for the candidate placeholder and a help message.

@tomasr8
Copy link
Member

tomasr8 commented Nov 1, 2024

Much better! Since we now display all hours 0-24, the timeline feels a bit too small- you have to be more precise with your mouse because there's a only a few pixels between each step. We have some space on the left so I think we could use it for the timeline.

My other issue is with having the placeholder as position: fixed. I think it adds a lot of complexity when it comes to calculating the positions/height/etc as you've seen. There's also the issue with scrolling. I would suggest to use position: absolute relative to div.timeline-rows which is what we already do for the time slots. That way, you don't need to compute the position of the popup nor the height, and scroll also works by default.

Here's what I have in mind:

Diff
diff --git a/newdle/client/src/components/creation/timeslots/CandidatePlaceholder.js b/newdle/client/src/components/creation/timeslots/CandidatePlaceholder.js
index d7a4e19..3816ec8 100644
--- a/newdle/client/src/components/creation/timeslots/CandidatePlaceholder.js
+++ b/newdle/client/src/components/creation/timeslots/CandidatePlaceholder.js
@@ -1,33 +1,43 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import {Popup} from 'semantic-ui-react';
 /**
  * Displays a placeholder for a candidate time slot when the Timeline is hovered.
  */
-export default function CandidatePlaceholder({xPosition, yPosition, height, widthPercent}) {
+export default function CandidatePlaceholder({visible, left, width, time}) {
+  if (!visible) {
+    return null;
+  }
+
   return (
-    <div
-      style={{
-        background: 'rgba(0, 0, 0, 0.3)',
-        borderRadius: '3px',
-        color: 'white',
-        display: 'block',
-        height: height,
-        left: xPosition,
-        padding: '4px',
-        position: 'fixed',
-        pointerEvents: 'none',
-        top: yPosition,
-        transform: 'translate(-50%, -100%)',
-        width: `${widthPercent}%`,
-        zIndex: 1000,
-      }}
+    <Popup
+      content={time}
+      open={true}
+      position="top center"
+      trigger={
+        <div
+          style={{
+            boxSizing: 'border-box',
+            position: 'absolute',
+            left: `${left}%`,
+            width: `${width}%`,
+            top: 5,
+            height: 'calc(100% - 10px)',
+            zIndex: 1000,
+            background: 'rgba(0, 0, 0, 0.2)',
+            borderRadius: '3px',
+            display: 'block',
+            pointerEvents: 'none',
+          }}
+        />
+      }
     />
   );
 }
 
 CandidatePlaceholder.propTypes = {
-  height: PropTypes.number.isRequired,
-  widthPercent: PropTypes.number.isRequired,
-  xPosition: PropTypes.number.isRequired,
-  yPosition: PropTypes.number.isRequired,
+  visible: PropTypes.bool.isRequired,
+  width: PropTypes.number.isRequired,
+  left: PropTypes.number.isRequired,
+  time: PropTypes.string.isRequired,
 };
diff --git a/newdle/client/src/components/creation/timeslots/Timeline.js b/newdle/client/src/components/creation/timeslots/Timeline.js
index f7c7468..9b7f32c 100644
--- a/newdle/client/src/components/creation/timeslots/Timeline.js
+++ b/newdle/client/src/components/creation/timeslots/Timeline.js
@@ -56,6 +56,17 @@ function calculatePosition(start, minHour, maxHour) {
   return position < 100 ? position : 100 - OVERFLOW_WIDTH;
 }
 
+function calculatePlaceholderStart(e, minHour, maxHour) {
+  const timelineRect = e.target.getBoundingClientRect();
+  const position = (e.clientX - timelineRect.left) / timelineRect.width;
+  const totalMinutes = (maxHour - minHour) * 60;
+
+  let minutes = minHour * 60 + position * totalMinutes;
+  minutes = Math.floor(minutes / 15) * 15;
+
+  return moment().startOf('day').add(minutes, 'minutes');
+}
+
 function getSlotProps(startTime, endTime, minHour, maxHour) {
   const start = toMoment(startTime, DEFAULT_TIME_FORMAT);
   const end = toMoment(endTime, DEFAULT_TIME_FORMAT);
@@ -165,10 +176,7 @@ function TimelineInput({minHour, maxHour}) {
   const duration = useSelector(getDuration);
   const date = useSelector(getCreationCalendarActiveDate);
   const candidates = useSelector(getTimeslotsForActiveDate);
-  const pastCandidates = useSelector(getPreviousDayTimeslots);
   const availability = useSelector(getParticipantAvailability);
-  const [_editing, setEditing] = useState(false);
-  const editing = _editing || !!candidates.length;
   const latestStartTime = useSelector(getNewTimeslotStartTime);
   const [timeslotTime, setTimeslotTime] = useState(latestStartTime);
   const [newTimeslotPopupOpen, setTimeslotPopupOpen] = useState(false);
@@ -176,41 +184,16 @@ function TimelineInput({minHour, maxHour}) {
   const [candidatePlaceholder, setCandidatePlaceholder] = useState({
     visible: false,
     time: '',
-    x: 0,
-    y: 0,
+    left: 0,
+    width: 0,
   });
   // We don't want to show the tooltip when the mouse is hovering over a slot
   const [isHoveringSlot, setIsHoveringSlot] = useState(false);
-  const placeHolderSlot = getCandidateSlotProps('00:00', duration, minHour, maxHour);
-
-  useEffect(() => {
-    const handleScroll = () => {
-      setCandidatePlaceholder({visible: false});
-    };
-
-    window.addEventListener('scroll', handleScroll);
-
-    return () => {
-      window.removeEventListener('scroll', handleScroll);
-    };
-  }, []);
 
   useEffect(() => {
     setTimeslotTime(latestStartTime);
   }, [latestStartTime, candidates, duration]);
 
-  const handleStartEditing = () => {
-    setEditing(true);
-    setTimeslotPopupOpen(true);
-  };
-
-  const handleCopyClick = () => {
-    pastCandidates.forEach(time => {
-      dispatch(addTimeslot(date, time));
-    });
-    setEditing(true);
-  };
-
   const handlePopupClose = () => {
     setTimeslotPopupOpen(false);
   };
@@ -235,30 +218,10 @@ function TimelineInput({minHour, maxHour}) {
   };
 
   const handleMouseDown = e => {
-    const parentRect = e.target.getBoundingClientRect();
-    const totalMinutes = (maxHour - minHour) * 60;
-
-    // Get the parent rect start position
-    const parentRectStart = parentRect.left;
-    // Get the parent rect end position
-    const parentRectEnd = parentRect.right;
-
-    const clickPositionRelative = (e.clientX - parentRectStart) / (parentRectEnd - parentRectStart);
-
-    let clickTimeRelative = clickPositionRelative * totalMinutes;
-
-    // Round clickTimeRelative to the nearest 15-minute interval
-    clickTimeRelative = Math.round(clickTimeRelative / 15) * 15;
-
-    // Convert clickTimeRelative to a time format (HH:mm)
-    const clickTimeRelativeTime = moment()
-      .startOf('day')
-      .add(clickTimeRelative, 'minutes')
-      .format('HH:mm');
-
-    const canBeAdded = clickTimeRelativeTime && !isTimeSlotTaken(clickTimeRelativeTime);
-    if (canBeAdded) {
-      handleAddSlot(clickTimeRelativeTime);
+    const start = calculatePlaceholderStart(e, minHour, maxHour);
+    const formattedTime = start.format(DEFAULT_TIME_FORMAT);
+    if (!isTimeSlotTaken(formattedTime)) {
+      handleAddSlot(formattedTime);
     }
   };
 
@@ -269,220 +232,152 @@ function TimelineInput({minHour, maxHour}) {
    */
   const handleTimelineMouseMove = e => {
     if (isHoveringSlot) {
-      setCandidatePlaceholder({visible: false});
+      setCandidatePlaceholder(p => ({...p, visible: false}));
       return;
     }
-    const timelineRect = e.target.getBoundingClientRect();
-    const relativeMouseXPosition = e.clientX - timelineRect.left;
 
-    const totalMinutes = (maxHour - minHour) * 60; // Total minutes in the timeline
-    let timeInMinutes = (relativeMouseXPosition / timelineRect.width) * totalMinutes;
-    // Round timeInMinutes to the nearest 15-minute interval
-    timeInMinutes = Math.round(timeInMinutes / 15) * 15;
-    const slotWidth = (placeHolderSlot.width / timelineRect.width) * 100;
-
-    const hours = Math.floor(timeInMinutes / 60) + minHour;
-    const minutes = Math.floor(timeInMinutes % 60);
-    const time = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
-
-    if (time === candidatePlaceholder.time) {
-      return;
-    }
+    const start = calculatePlaceholderStart(e, minHour, maxHour);
+    const end = moment(start).add(duration, 'minutes');
+    const time = start.format(DEFAULT_TIME_FORMAT);
 
     // Check if the time slot is already taken
     if (isTimeSlotTaken(time)) {
-      setCandidatePlaceholder({visible: false});
+      setCandidatePlaceholder(p => ({...p, visible: false}));
       return;
     }
 
-    const timelineVerticalPadding = 16;
-    const timelineVerticalPaddingBottom = 5;
-    const candidatePlaceholderPaddingLeft = 5;
-    const tooltipMarginLeft = -20;
-    const candidatePlaceholderMarginLeft = 5;
-
-    const tempPlaceholder = {
+    setCandidatePlaceholder(p => ({
+      ...p,
       visible: true,
       time,
-      candidateX: e.clientX + candidatePlaceholderMarginLeft,
-      candidateY: timelineRect.top + timelineRect.height - timelineVerticalPaddingBottom,
-      candidateHeight: timelineRect.height - timelineVerticalPadding,
-      tooltipMarginLeft: tooltipMarginLeft,
-      tooltipX: relativeMouseXPosition + candidatePlaceholderPaddingLeft,
-      width: slotWidth,
-    };
-
-    if (hours >= 0 && minutes >= 0) {
-      setCandidatePlaceholder(tempPlaceholder);
-    }
+      left: calculatePosition(start, minHour, maxHour),
+      width: calculateWidth(start, end, minHour, maxHour),
+    }));
   };
 
   const handleTimelineMouseLeave = () => {
-    setCandidatePlaceholder({visible: false});
+    setCandidatePlaceholder(p => ({...p, visible: false}));
   };
 
   const groupedCandidates = splitOverlappingCandidates(candidates, duration);
 
-  return editing ? (
-    <Popup
-      content={candidatePlaceholder.time}
-      open={candidatePlaceholder.visible}
-      popperModifiers={[
-        {
-          name: 'offset',
-          enabled: true,
-          options: {
-            offset: [candidatePlaceholder.tooltipX + candidatePlaceholder.tooltipMarginLeft, 0],
-          },
-        },
-      ]}
-      trigger={
-        <div>
-          <div
-            className={`${styles['timeline-input']} ${styles['edit']}`}
-            onClick={event => {
-              handleMouseDown(event);
-              handleTimelineMouseLeave();
-            }}
-            onMouseMove={handleTimelineMouseMove}
-            onMouseLeave={handleTimelineMouseLeave}
-          >
-            <div className={styles['timeline-candidates']}>
-              {groupedCandidates.map((rowCandidates, i) => (
-                <div
-                  className={styles['candidates-group']}
-                  key={i}
-                  onMouseEnter={() => {
-                    // Prevent the candidate placeholder from showing when hovering over a slot
-                    setIsHoveringSlot(true);
-                  }}
-                  onMouseLeave={() => {
-                    setIsHoveringSlot(false);
-                  }}
-                >
-                  {rowCandidates.map(time => {
-                    const slotProps = getCandidateSlotProps(time, duration, minHour, maxHour);
-                    const participants = availability?.find(a => a.startDt === `${date}T${time}`);
-                    return (
-                      <CandidateSlot
-                        {...slotProps}
-                        key={time}
-                        isValidTime={time => !isTimeSlotTaken(time)}
-                        onDelete={event => {
-                          // Prevent the event from bubbling up to the parent div
-                          event.stopPropagation();
-                          handleRemoveSlot(event, time);
-                        }}
-                        onChangeSlotTime={newStartTime => handleUpdateSlot(time, newStartTime)}
-                        text={
-                          participants &&
-                          plural(participants.availableCount, {
-                            0: 'No participants registered',
-                            one: '# participant registered',
-                            other: '# participants registered',
-                          })
-                        }
-                      />
-                    );
-                  })}
-                </div>
-              ))}
-              {candidatePlaceholder.visible && (
-                <CandidatePlaceholder
-                  xPosition={candidatePlaceholder.candidateX}
-                  yPosition={candidatePlaceholder.candidateY}
-                  height={candidatePlaceholder.candidateHeight}
-                  widthPercent={candidatePlaceholder.width}
-                />
-              )}
-            </div>
-            <div onMouseMove={e => e.stopPropagation()} className={styles['add-btn-wrapper']}>
-              <Popup
-                trigger={
-                  <Icon
-                    className={`${styles['clickable']} ${styles['add-btn']}`}
-                    name="plus circle"
-                    size="large"
-                    onMouseMove={e => e.stopPropagation()}
+  return (
+    <div>
+      <div
+        className={`${styles['timeline-input']} ${styles['edit']}`}
+        onClick={event => {
+          handleMouseDown(event);
+          handleTimelineMouseLeave();
+        }}
+        onMouseMove={handleTimelineMouseMove}
+        onMouseLeave={handleTimelineMouseLeave}
+      >
+        <CandidatePlaceholder {...candidatePlaceholder} />
+        <div className={styles['timeline-candidates']}>
+          {groupedCandidates.map((rowCandidates, i) => (
+            <div
+              className={styles['candidates-group']}
+              key={i}
+              onMouseEnter={() => {
+                // Prevent the candidate placeholder from showing when hovering over a slot
+                setIsHoveringSlot(true);
+              }}
+              onMouseLeave={() => {
+                setIsHoveringSlot(false);
+              }}
+            >
+              {rowCandidates.map(time => {
+                const slotProps = getCandidateSlotProps(time, duration, minHour, maxHour);
+                const participants = availability?.find(a => a.startDt === `${date}T${time}`);
+                return (
+                  <CandidateSlot
+                    {...slotProps}
+                    key={time}
+                    isValidTime={time => !isTimeSlotTaken(time)}
+                    onDelete={event => {
+                      // Prevent the event from bubbling up to the parent div
+                      event.stopPropagation();
+                      handleRemoveSlot(event, time);
+                    }}
+                    onChangeSlotTime={newStartTime => handleUpdateSlot(time, newStartTime)}
+                    text={
+                      participants &&
+                      plural(participants.availableCount, {
+                        0: 'No participants registered',
+                        one: '# participant registered',
+                        other: '# participants registered',
+                      })
+                    }
                   />
-                }
-                on="click"
+                );
+              })}
+            </div>
+          ))}
+        </div>
+        <div onMouseMove={e => e.stopPropagation()} className={styles['add-btn-wrapper']}>
+          <Popup
+            trigger={
+              <Icon
+                className={`${styles['clickable']} ${styles['add-btn']}`}
+                name="plus circle"
+                size="large"
+                onMouseMove={e => e.stopPropagation()}
+              />
+            }
+            on="click"
+            onMouseMove={e => {
+              e.stopPropagation();
+            }}
+            position="bottom center"
+            onOpen={evt => {
+              // Prevent the event from bubbling up to the parent div
+              evt.stopPropagation();
+              setTimeslotPopupOpen(true);
+            }}
+            onClose={handlePopupClose}
+            open={newTimeslotPopupOpen}
+            onKeyDown={evt => {
+              const canBeAdded = timeslotTime && !isTimeSlotTaken(timeslotTime);
+              if (evt.key === 'Enter' && canBeAdded) {
+                handleAddSlot(timeslotTime);
+                handlePopupClose();
+              }
+            }}
+            className={styles['timepicker-popup']}
+            content={
+              <div
+                // We need a div to attach events
+                onClick={e => e.stopPropagation()}
                 onMouseMove={e => {
                   e.stopPropagation();
                 }}
-                position="bottom center"
-                onOpen={evt => {
-                  // Prevent the event from bubbling up to the parent div
-                  evt.stopPropagation();
-                  setTimeslotPopupOpen(true);
-                }}
-                onClose={handlePopupClose}
-                open={newTimeslotPopupOpen}
-                onKeyDown={evt => {
-                  const canBeAdded = timeslotTime && !isTimeSlotTaken(timeslotTime);
-                  if (evt.key === 'Enter' && canBeAdded) {
+              >
+                <TimePicker
+                  showSecond={false}
+                  value={toMoment(timeslotTime, DEFAULT_TIME_FORMAT)}
+                  format={DEFAULT_TIME_FORMAT}
+                  onChange={time => setTimeslotTime(time ? time.format(DEFAULT_TIME_FORMAT) : null)}
+                  onMouseMove={e => e.stopPropagation()}
+                  allowEmpty={false}
+                  // keep the picker in the DOM tree of the surrounding element
+                  getPopupContainer={node => node}
+                />
+                <Button
+                  icon
+                  onMouseMove={e => e.stopPropagation()}
+                  onClick={() => {
                     handleAddSlot(timeslotTime);
                     handlePopupClose();
-                  }
-                }}
-                className={styles['timepicker-popup']}
-                content={
-                  <div
-                    // We need a div to attach events
-                    onClick={e => e.stopPropagation()}
-                    onMouseMove={e => {
-                      e.stopPropagation();
-                    }}
-                  >
-                    <TimePicker
-                      showSecond={false}
-                      value={toMoment(timeslotTime, DEFAULT_TIME_FORMAT)}
-                      format={DEFAULT_TIME_FORMAT}
-                      onChange={time =>
-                        setTimeslotTime(time ? time.format(DEFAULT_TIME_FORMAT) : null)
-                      }
-                      onMouseMove={e => e.stopPropagation()}
-                      allowEmpty={false}
-                      // keep the picker in the DOM tree of the surrounding element
-                      getPopupContainer={node => node}
-                    />
-                    <Button
-                      icon
-                      onMouseMove={e => e.stopPropagation()}
-                      onClick={() => {
-                        handleAddSlot(timeslotTime);
-                        handlePopupClose();
-                      }}
-                      disabled={!timeslotTime || isTimeSlotTaken(timeslotTime)}
-                    >
-                      <Icon name="check" onMouseMove={e => e.stopPropagation()} />
-                    </Button>
-                  </div>
-                }
-              />
-            </div>
-          </div>
-          {candidates.length === 0 && (
-            <div className={styles['add-first-text']}>
-              <Icon name="mouse pointer" />
-              <Trans>Click the timeline to add your first time slot</Trans>
-            </div>
-          )}
+                  }}
+                  disabled={!timeslotTime || isTimeSlotTaken(timeslotTime)}
+                >
+                  <Icon name="check" onMouseMove={e => e.stopPropagation()} />
+                </Button>
+              </div>
+            }
+          />
         </div>
-      }
-    />
-  ) : (
-    <div className={styles['timeline-input-wrapper']}>
-      <div className={`${styles['timeline-input']} ${styles['msg']}`} onClick={handleStartEditing}>
-        <Icon name="plus circle" size="large" />
-        <Trans>Click to add time slots</Trans>
       </div>
-      {pastCandidates && (
-        <div className={`${styles['timeline-input']} ${styles['msg']}`} onClick={handleCopyClick}>
-          <Icon name="copy" size="large" />
-          <Trans>Copy time slots from previous day</Trans>
-        </div>
-      )}
     </div>
   );
 }
@@ -492,23 +387,74 @@ TimelineInput.propTypes = {
   maxHour: PropTypes.number.isRequired,
 };
 
-function TimelineContent({busySlots: allBusySlots, minHour, maxHour}) {
+function ClickToAddTimeSlots({startEditing, copyTimeSlots}) {
+  const pastCandidates = useSelector(getPreviousDayTimeslots);
+
   return (
-    <div className={styles['timeline-rows']}>
-      {allBusySlots.map(slot => (
-        <TimelineRow {...slot} key={slot.participant.email} />
-      ))}
-      {allBusySlots.map(({busySlots, participant}) =>
-        busySlots.map(slot => {
-          const key = `${participant.email}-${slot.startTime}-${slot.endTime}`;
-          return <BusyColumn {...slot} key={key} />;
-        })
+    <div className={styles['timeline-input-wrapper']}>
+      <div className={`${styles['timeline-input']} ${styles['msg']}`} onClick={startEditing}>
+        <Icon name="plus circle" size="large" />
+        <Trans>Click to add time slots</Trans>
+      </div>
+      {pastCandidates && (
+        <div className={`${styles['timeline-input']} ${styles['msg']}`} onClick={copyTimeSlots}>
+          <Icon name="copy" size="large" />
+          <Trans>Copy time slots from previous day</Trans>
+        </div>
       )}
-      <TimelineInput minHour={minHour} maxHour={maxHour} />
     </div>
   );
 }
 
+ClickToAddTimeSlots.propTypes = {
+  startEditing: PropTypes.func.isRequired,
+  copyTimeSlots: PropTypes.func.isRequired,
+};
+
+function TimelineContent({busySlots: allBusySlots, minHour, maxHour}) {
+  const dispatch = useDispatch();
+  const [editing, setEditing] = useState(false);
+  const date = useSelector(getCreationCalendarActiveDate);
+  const pastCandidates = useSelector(getPreviousDayTimeslots);
+  const candidates = useSelector(getTimeslotsForActiveDate);
+
+  const copyTimeSlots = () => {
+    pastCandidates.forEach(time => {
+      dispatch(addTimeslot(date, time));
+    });
+    setEditing(true);
+  };
+
+  if (!editing && candidates.length === 0) {
+    return (
+      <ClickToAddTimeSlots startEditing={() => setEditing(true)} copyTimeSlots={copyTimeSlots} />
+    );
+  }
+
+  return (
+    <>
+      <div className={styles['timeline-rows']}>
+        {allBusySlots.map(slot => (
+          <TimelineRow {...slot} key={slot.participant.email} />
+        ))}
+        {allBusySlots.map(({busySlots, participant}) =>
+          busySlots.map(slot => {
+            const key = `${participant.email}-${slot.startTime}-${slot.endTime}`;
+            return <BusyColumn {...slot} key={key} />;
+          })
+        )}
+        <TimelineInput minHour={minHour} maxHour={maxHour} />
+      </div>
+      {editing && candidates.length === 0 && (
+        <div className={styles['add-first-text']}>
+          <Icon name="mouse pointer" />
+          <Trans>Click the timeline to add your first time slot</Trans>
+        </div>
+      )}
+    </>
+  );
+}
+
 TimelineContent.propTypes = {
   busySlots: PropTypes.array.isRequired,
   minHour: PropTypes.number.isRequired,
diff --git a/newdle/client/src/components/creation/timeslots/Timeline.module.scss b/newdle/client/src/components/creation/timeslots/Timeline.module.scss
index 8ee51ba..3849863 100644
--- a/newdle/client/src/components/creation/timeslots/Timeline.module.scss
+++ b/newdle/client/src/components/creation/timeslots/Timeline.module.scss
@@ -2,17 +2,19 @@
 
 $row-height: 50px;
 $label-width: 180px;
+$rows-border-width: 5px;
 
 .timeline {
   position: relative;
   margin: 4px;
 
-  @media screen and (min-width: 1200px) {
-    margin-left: $label-width;
-  }
   .timeline-title {
     display: flex;
     justify-content: space-between;
+
+    @media screen and (min-width: 1200px) {
+      margin-left: $label-width;
+    }
   }
 
   .timeline-date {
@@ -20,8 +22,8 @@ $label-width: 180px;
   }
 
   .timeline-hours {
-    // margin-left: 30px;
-    // margin-right: 10px;
+    margin-left: $rows-border-width;
+    margin-right: $rows-border-width;
     color: $grey;
     height: $row-height;
     position: relative;
@@ -44,8 +46,9 @@ $label-width: 180px;
   }
   .timeline-rows {
     position: relative;
-    // margin-left: 20px;
-    // margin-right: 10px;
+    background-color: lighten($green, 27%);
+    border: $rows-border-width solid lighten($green, 22%);
+
     .timeline-row {
       height: $row-height;
       display: flex;
@@ -136,6 +139,7 @@ $label-width: 180px;
     z-index: 1;
 
     &.candidate {
+      box-sizing: border-box;
       background-color: $green;
       border: 1px solid darken($green, 4%);
       height: 40px;
@@ -212,9 +216,8 @@ $label-width: 180px;
     }
 
     &.edit {
-      background-color: lighten($green, 27%);
-      border: 5px solid lighten($green, 22%);
-      padding: 10px;
+      padding-top: 10px;
+      padding-bottom: 10px;
 
       .add-btn-wrapper {
         display: flex;

@renefs renefs force-pushed the simplify-slot-creation branch 6 times, most recently from 4d16658 to b546167 Compare November 18, 2024 13:35
Refactor candidate placeholder

Co-authored-by: Tomas Roun <[email protected]>

Fix tooltips and hide placeholder when x button is hovered

Do not expand 2 days if end of slot is next day
@renefs renefs force-pushed the simplify-slot-creation branch from b546167 to 4819972 Compare November 18, 2024 15:21
@tomasr8
Copy link
Member

tomasr8 commented Nov 27, 2024

@renefs is there anything missing in this PR?

Copy link
Member

@ThiefMaster ThiefMaster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works fine now! :)

@renefs
Copy link
Contributor Author

renefs commented Nov 27, 2024

I think it should be fine :)

@ThiefMaster
Copy link
Member

OK will merge later today (leaving for lunch now and don't want to trigger a deployment right before going AFK)

@ThiefMaster ThiefMaster merged commit d3357d2 into indico:master Nov 27, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make it easier to create timeslots
3 participants