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

PUPIL-442: Match question UI #129

Merged
merged 15 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
href="https://googleapis-fonts.thenational.academy/css2?family=Lexend:wght@300;400;600&display=swap"
rel="stylesheet"
/>
<style>
:root {
color-scheme: only light;
}
</style>
carlmw marked this conversation as resolved.
Show resolved Hide resolved
22 changes: 22 additions & 0 deletions src/animation/usePrefersReducedMotion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { renderHook } from "@testing-library/react";

import { usePrefersReducedMotion } from "./usePrefersReducedMotion";

window.matchMedia = jest.fn().mockReturnValue({
matches: false,
});

describe(usePrefersReducedMotion, () => {
it("is true when the media query matches", () => {
jest.spyOn(window, "matchMedia").mockReturnValue({
matches: true,
} as MediaQueryList);

const { result } = renderHook(() => usePrefersReducedMotion());

expect(window.matchMedia).toHaveBeenCalledWith(
"(prefers-reduced-motion: reduce)",
);
expect(result.current).toBe(true);
});
});
16 changes: 16 additions & 0 deletions src/animation/usePrefersReducedMotion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEffect, useState } from "react";

/**
* Returns true if the user has requested that the system minimize the amount of non-essential motion it uses.
*/
export function usePrefersReducedMotion() {
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);

useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");

setPrefersReducedMotion(mediaQuery.matches);
}, []);

return prefersReducedMotion;
}
30 changes: 30 additions & 0 deletions src/components/atoms/InternalDndContext/InternalDndContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { DndContext, DndContextProps } from "@dnd-kit/core";
import React, { FC, createContext, useContext, useEffect } from "react";

/**
* Facilitates DI for the DndContext
*/
export const injectDndContext = createContext<FC<DndContextProps>>(DndContext);

/**
* Wraps dnd-kit's `DndContext` to normalise scroll behaviour and enable dependency injection
*/
export const InternalDndContext = (props: DndContextProps) => {
const DndContext = useContext(injectDndContext);

/**
* Disable smooth scrolling during drag to ensure that the dragged item is always visible
*/
useEffect(() => {
const originalScrollingBehaviour =
document.documentElement.style.scrollBehavior;
document.documentElement.style.scrollBehavior = "auto";

return () => {
document.documentElement.style.scrollBehavior =
originalScrollingBehaviour;
};
});

return <DndContext {...props} />;
};
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,11 @@ export const ReadOnly: Story = {
isReadOnly: true,
},
};

export const WithColors: Story = {
args: {
color: "text-inverted",
background: "bg-btn-primary",
iconColor: "icon-main",
},
};
61 changes: 48 additions & 13 deletions src/components/molecules/OakDraggable/OakDraggable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { parseBorder } from "@/styles/helpers/parseBorder";
import { parseDropShadow } from "@/styles/helpers/parseDropShadow";
import { parseSpacing } from "@/styles/helpers/parseSpacing";
import { IconName } from "@/image-map";
import { OakColorFilterToken } from "@/styles/theme/color";
import { OakCombinedColorToken } from "@/styles/theme/color";
import { parseBorderWidth } from "@/styles/helpers/parseBorderWidth";
import { parseColorFilter } from "@/styles/helpers/parseColorFilter";

type OakDraggableProps = {
/**
Expand All @@ -34,23 +36,45 @@ type OakDraggableProps = {
*/
iconName?: IconName;
/**
* Icon color
* Icon color when not being dragged or hovered
*/
iconColor?: OakColorFilterToken;
iconColor?: OakCombinedColorToken;
/**
* The background color of the draggable when not being dragged or hovered
*/
background?: OakCombinedColorToken;
/**
* The color of the draggable when not being dragged or hovered
*/
color?: OakCombinedColorToken;
};

const StyledDraggable = styled(OakBox)`
const StyledOakIcon = styled(OakIcon)``;

const StyledDraggable = styled(OakBox)<{ $iconColor: OakCombinedColorToken }>`
cursor: grab;
outline: none;
user-select: none;

${StyledOakIcon} {
filter: ${(props) => parseColorFilter(props.$iconColor)};
}

@media (hover: hover) {
&:hover:not([data-dragging="true"]):not([data-disabled="true"]):not(
[data-readonly="true"]
) {
background-color: ${parseColor("bg-decorative1-subdued")};
color: ${parseColor("text-primary")};
box-shadow: ${parseDropShadow("drop-shadow-standard")};
border-bottom: ${parseBorder("border-solid-xl")}
${parseColor("border-primary")};
padding-bottom: ${parseSpacing("inner-padding-m")};
text-decoration: underline;

${StyledOakIcon} {
filter: ${parseColorFilter("icon-inverted")};
}
}
}

Expand All @@ -62,15 +86,26 @@ const StyledDraggable = styled(OakBox)`
&[data-dragging="true"] {
cursor: move;
background-color: ${parseColor("bg-decorative1-main")};
border: ${parseBorder("border-solid-xl")} ${parseColor("border-primary")};
color: ${parseColor("text-primary")};
outline: ${parseBorder("border-solid-xl")} ${parseColor("border-primary")};
outline-offset: -${parseBorderWidth("border-solid-xl")};
carlmw marked this conversation as resolved.
Show resolved Hide resolved
box-shadow: ${parseDropShadow("drop-shadow-lemon")},
${parseDropShadow("drop-shadow-grey")};
text-decoration: underline;

${StyledOakIcon} {
filter: ${parseColorFilter("icon-inverted")};
}
}

&[data-disabled="true"] {
cursor: default;
background-color: ${parseColor("bg-neutral")};
color: ${parseColor("text-disabled")};

${StyledOakIcon} {
filter: ${parseColorFilter("icon-disabled")};
}
}

&[data-readonly="true"] {
Expand All @@ -90,17 +125,18 @@ export const OakDraggable: FC<
ComponentPropsWithRef<OakDraggableProps & typeof OakBox>
> = forwardRef<
HTMLDivElement,
ComponentPropsWithoutRef<OakDraggableProps & typeof OakBox>
OakDraggableProps & ComponentPropsWithoutRef<typeof OakBox>
>(
(
{
children,
iconName = "move-arrows",
iconColor = "icon-primary",
iconColor = "icon-inverted",
color = "text-primary",
background = "bg-primary",
isDragging,
isDisabled,
isReadOnly,
$borderColor = "transparent",
...props
},
ref,
Expand All @@ -111,20 +147,19 @@ export const OakDraggable: FC<
$pv="inner-padding-l"
$pl="inner-padding-s"
$pr="inner-padding-m"
$background="bg-primary"
$background={background}
$color={color}
$borderRadius="border-radius-m2"
$borderColor={$borderColor}
$bb="border-solid-xl"
$minHeight="all-spacing-10"
data-dragging={isDragging}
data-disabled={isDisabled}
data-readonly={isReadOnly}
$iconColor={iconColor}
{...props}
>
<StyledFlex $gap="space-between-s" $alignItems="center">
<OakIcon
<StyledOakIcon
iconName={iconName}
$colorFilter={iconColor}
$width="all-spacing-7"
$height="all-spacing-7"
alt=""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ exports[`OakDraggable matches snapshot 1`] = `
padding-bottom: 1.25rem;
padding-left: 0.75rem;
padding-right: 1rem;
color: #222222;
background: #ffffff;
border-bottom: 0.25rem solid;
border-color: transparent;
border-radius: 0.5rem;
font-family: Lexend,sans-serif;
}
Expand Down Expand Up @@ -64,6 +63,10 @@ exports[`OakDraggable matches snapshot 1`] = `
cursor: -moz-grab;
cursor: grab;
outline: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

.c1:focus-visible:not([data-dragging="true"]):not([data-disabled="true"]) {
Expand All @@ -73,8 +76,12 @@ exports[`OakDraggable matches snapshot 1`] = `
.c1[data-dragging="true"] {
cursor: move;
background-color: #bef2bd;
border: 0.25rem solid #222222;
color: #222222;
outline: 0.25rem solid #222222;
outline-offset: -0.25rem;
box-shadow: 0.125rem 0.125rem 0 rgba(255,229,85,100%), 0.25rem 0.25rem 0 rgba(87,87,87,100%);
-webkit-text-decoration: underline;
text-decoration: underline;
}

.c1[data-disabled="true"] {
Expand All @@ -94,8 +101,12 @@ exports[`OakDraggable matches snapshot 1`] = `
@media (hover:hover) {
.c1:hover:not([data-dragging="true"]):not([data-disabled="true"]):not( [data-readonly="true"] ) {
background-color: #dff9de;
color: #222222;
box-shadow: 0 0.5rem 0.5rem rgba(92,92,92,20%);
border-bottom: 0.25rem solid #222222;
padding-bottom: 1rem;
-webkit-text-decoration: underline;
text-decoration: underline;
}
}

Expand All @@ -106,7 +117,7 @@ exports[`OakDraggable matches snapshot 1`] = `
className="c2 c3 c4"
>
<div
className="c5"
className="c5 "
>
<img
alt=""
Expand Down
56 changes: 54 additions & 2 deletions src/components/molecules/OakDroppable/OakDroppable.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ const meta: Meta<typeof OakDroppable> = {
title: "components/molecules/OakDroppable",
argTypes: {
children: { type: "string" },
labelSlot: { type: "string" },
isOver: { type: "boolean" },
},
parameters: {
controls: {
include: ["children", "isOver"],
include: ["children", "labelSlot", "isOver"],
},
backgrounds: {
default: "light",
Expand All @@ -29,14 +30,65 @@ type Story = StoryObj<typeof OakDroppable>;

export const Default: Story = {};

export const ADraggableHasEnteredTheDroppable: Story = {
export const Disabled: Story = {
args: {
isDisabled: true,
},
};

/**
* A draggable has entered the droppable so it has entered an active state
*/
export const DraggingOver: Story = {
args: {
isOver: true,
},
};

export const Occupied: Story = {
args: {
canDrop: true,
children: <OakDraggable>Elephant</OakDraggable>,
},
};

export const WithSlotLabel: Story = {
args: {
canDrop: true,
labelSlot: "never forgets",
},
};

export const OccupiedWithSlotLabel: Story = {
args: {
canDrop: true,
labelSlot: "never forgets",
children: <OakDraggable>Elephant</OakDraggable>,
},
};
/**
* A draggable has entered the droppable so it has entered an active state
*/
export const DraggingOverWithSlotLabel: Story = {
args: {
canDrop: true,
isOver: true,
labelSlot: "never forgets",
},
};

export const WithLongSlotLabel: Story = {
args: {
canDrop: true,
labelSlot:
"which animal never forgets and is the largest land animal on earth?",
},
};

export const WithAVeryLongSlotLabel: Story = {
args: {
canDrop: true,
labelSlot:
"which animal is the largest land mammal with a long trunk, large ears, and tusks? Known for intelligence and social behavior, it symbolizes strength and conservation efforts worldwide.",
},
};
Loading