diff --git a/assets/src/js/v3/entries/course-builder/components/layouts/header/Header.tsx b/assets/src/js/v3/entries/course-builder/components/layouts/header/Header.tsx index 4a412b3995..1fc2419244 100644 --- a/assets/src/js/v3/entries/course-builder/components/layouts/header/Header.tsx +++ b/assets/src/js/v3/entries/course-builder/components/layouts/header/Header.tsx @@ -188,6 +188,7 @@ const styles = { 'logo closeButton' 'container container'; row-gap: ${spacing[8]}; + top: 0; } `, container: css` diff --git a/assets/src/js/v3/entries/course-builder/pages/Curriculum.tsx b/assets/src/js/v3/entries/course-builder/pages/Curriculum.tsx index cc6084d5ce..cfe47be1cc 100644 --- a/assets/src/js/v3/entries/course-builder/pages/Curriculum.tsx +++ b/assets/src/js/v3/entries/course-builder/pages/Curriculum.tsx @@ -562,7 +562,7 @@ export default Curriculum; const styles = { container: css` - margin-top: ${spacing[32]}; + margin-top: calc(${spacing[32]} - 1px); width: 100%; ${Breakpoint.smallTablet} { diff --git a/assets/src/js/v3/shared/components/fields/FormDateInput.tsx b/assets/src/js/v3/shared/components/fields/FormDateInput.tsx index f9700a3318..99f746698f 100644 --- a/assets/src/js/v3/shared/components/fields/FormDateInput.tsx +++ b/assets/src/js/v3/shared/components/fields/FormDateInput.tsx @@ -11,6 +11,7 @@ import { DateFormats, isRTL } from '@TutorShared/config/constants'; import { borderRadius, colorTokens, fontSize, fontWeight, shadow, spacing } from '@TutorShared/config/styles'; import { typography } from '@TutorShared/config/typography'; import { POPOVER_PLACEMENTS, Portal, usePortalPopover } from '@TutorShared/hooks/usePortalPopover'; +import { usePrefersReducedMotion } from '@TutorShared/hooks/usePrefersReducedMotion'; import type { FormControllerProps } from '@TutorShared/utils/form'; import { styleUtils } from '@TutorShared/utils/style-utils'; @@ -69,6 +70,7 @@ const FormDateInput = ({ onChange, dateFormat = DateFormats.monthDayYear, }: FormDateInputProps) => { + const prefersReducedMotion = usePrefersReducedMotion(); const inputRef = useRef(null); const [isOpen, setIsOpen] = useState(false); const parsedDate = parseDate(field.value); @@ -159,7 +161,7 @@ const FormDateInput = ({ > { + if (typeof document === 'undefined') { + return null; + } + return document.documentElement.getAttribute(MOTION_ATTRIBUTE) as MotionAttribute; +}; + +const systemPrefersReducedMotion = () => { + if (typeof window === 'undefined' || !window.matchMedia) { + return false; + } + return window.matchMedia('(prefers-reduced-motion: reduce)').matches; +}; + +const resolvePreference = () => { + const attribute = readMotionAttribute(); + + // Explicit override always wins. + if (attribute === 'reduce') return true; + + // 'auto' (or unset) defers to the OS/browser setting. + return systemPrefersReducedMotion(); +}; + +/** + * Resolves whether motion should be reduced, honoring: + * - data-tutor-motion="reduce" on -> always reduce + * - data-tutor-motion="auto" (or absent) -> defer to prefers-reduced-motion + */ +export const usePrefersReducedMotion = () => { + const [prefersReduced, setPrefersReduced] = useState(resolvePreference); + + useEffect(() => { + if (typeof window === 'undefined' || !window.matchMedia) { + return; + } + + const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); + const update = () => setPrefersReduced(resolvePreference()); + + update(); + mediaQuery.addEventListener('change', update); + + // In case data-tutor-motion is toggled at runtime (e.g. a settings panel) + const observer = new MutationObserver(update); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: [MOTION_ATTRIBUTE], + }); + + return () => { + mediaQuery.removeEventListener('change', update); + observer.disconnect(); + }; + }, []); + + return prefersReduced; +};