- This will delete all the data associated with your account. Texts
- you might have added will not be associated with your user anymore.
+ This will delete all the data associated with your account. Texts you might have added will not be
+ associated with your user anymore.
If you need to contact us, please reach us at
diff --git a/src/pages/Settings/Pages/DeleteAccount.js b/src/pages/Settings/Pages/DeleteAccount.js
index e7f9752eb..f3e021aa5 100644
--- a/src/pages/Settings/Pages/DeleteAccount.js
+++ b/src/pages/Settings/Pages/DeleteAccount.js
@@ -1,5 +1,5 @@
import { useEffect } from "react";
-import DeleteAccountButton from "../../DeleteAccount/DeleteAccountButton";
+import DeleteAccountButton from "../DeleteAccount/DeleteAccountButton";
import CardPage from "../../_pages_shared/CardPage";
import Main from "../../_pages_shared/Main.sc";
import SettingsPageHeader from "../SharedComponents/SettingsPageHeader";
diff --git a/src/pages/_pages_shared/Footer.sc.js b/src/pages/_pages_shared/CardPageFooter.sc.js
similarity index 100%
rename from src/pages/_pages_shared/Footer.sc.js
rename to src/pages/_pages_shared/CardPageFooter.sc.js
diff --git a/src/pages/onboarding/CreateAccount.js b/src/pages/onboarding/CreateAccount.js
index 4d2616214..bd8ab67b2 100644
--- a/src/pages/onboarding/CreateAccount.js
+++ b/src/pages/onboarding/CreateAccount.js
@@ -27,7 +27,7 @@ import FullWidthErrorMsg from "../../components/FullWidthErrorMsg.sc";
import FormSection from "../_pages_shared/FormSection.sc";
import InputField from "../../components/InputField";
import useShadowRef from "../../hooks/useShadowRef";
-import Footer from "../_pages_shared/Footer.sc";
+import Footer from "../_pages_shared/CardPageFooter.sc";
import ButtonContainer from "../_pages_shared/ButtonContainer.sc";
import Button from "../_pages_shared/Button.sc";
import Modal from "../../components/modal_shared/Modal";
diff --git a/src/pages/onboarding/ExtensionInstalled.js b/src/pages/onboarding/ExtensionInstalled.js
index 48fd2823d..10ebea6cd 100644
--- a/src/pages/onboarding/ExtensionInstalled.js
+++ b/src/pages/onboarding/ExtensionInstalled.js
@@ -7,7 +7,7 @@ import PageTitle from "../_pages_shared/PageTitle.sc";
import Main from "../_pages_shared/Main.sc";
import FullWidthImage from "../../components/FullWidthImage";
import ButtonContainer from "../_pages_shared/ButtonContainer.sc";
-import Footer from "../_pages_shared/Footer.sc";
+import Footer from "../_pages_shared/CardPageFooter.sc";
import Button from "../_pages_shared/Button.sc";
import strings from "../../i18n/definitions";
diff --git a/src/pages/onboarding/InstallExtension.js b/src/pages/onboarding/InstallExtension.js
index b6625b609..db2a1747e 100644
--- a/src/pages/onboarding/InstallExtension.js
+++ b/src/pages/onboarding/InstallExtension.js
@@ -9,7 +9,7 @@ import Header from "../_pages_shared/Header";
import PageTitle from "../_pages_shared/PageTitle.sc";
import Main from "../_pages_shared/Main.sc";
import ButtonContainer from "../_pages_shared/ButtonContainer.sc";
-import Footer from "../_pages_shared/Footer.sc";
+import Footer from "../_pages_shared/CardPageFooter.sc";
import Button from "../_pages_shared/Button.sc";
import FullWidthImage from "../../components/FullWidthImage";
import RoundedForwardArrow from "@mui/icons-material/ArrowForwardRounded";
diff --git a/src/pages/onboarding/SelectInterests.js b/src/pages/onboarding/SelectInterests.js
index b4a64d58f..7cad38187 100644
--- a/src/pages/onboarding/SelectInterests.js
+++ b/src/pages/onboarding/SelectInterests.js
@@ -7,7 +7,7 @@ import Header from "../_pages_shared/Header";
import PageTitle from "../_pages_shared/PageTitle.sc";
import Main from "../_pages_shared/Main.sc";
import ButtonContainer from "../_pages_shared/ButtonContainer.sc";
-import Footer from "../_pages_shared/Footer.sc";
+import Footer from "../_pages_shared/CardPageFooter.sc";
import Button from "../_pages_shared/Button.sc";
import Tag from "../_pages_shared/Tag.sc";
import TagContainer from "../_pages_shared/TagContainer.sc";
diff --git a/src/pages/onboarding/VerifyEmail.js b/src/pages/onboarding/VerifyEmail.js
index f36f0c0ef..26ae3cf49 100644
--- a/src/pages/onboarding/VerifyEmail.js
+++ b/src/pages/onboarding/VerifyEmail.js
@@ -13,7 +13,7 @@ import Form from "../_pages_shared/Form.sc";
import FullWidthErrorMsg from "../../components/FullWidthErrorMsg.sc";
import FormSection from "../_pages_shared/FormSection.sc";
import InputField from "../../components/InputField";
-import Footer from "../_pages_shared/Footer.sc";
+import Footer from "../_pages_shared/CardPageFooter.sc";
import ButtonContainer from "../_pages_shared/ButtonContainer.sc";
import Button from "../_pages_shared/Button.sc";
import LocalStorage from "../../assorted/LocalStorage";
diff --git a/src/reader/ArticleAuthors.js b/src/reader/ArticleAuthors.js
index 8b7225a5b..72dd5dc33 100644
--- a/src/reader/ArticleAuthors.js
+++ b/src/reader/ArticleAuthors.js
@@ -1,4 +1,5 @@
import * as s from "./ArticleReader.sc";
+//only visible if the article has authors (like is a teacher has written it), otherwise it is not rendered at all
// Scrapers sometimes store a "published on " fragment as the first
// author (e.g. "Publiceret D.", "Publié Le Mai À"). The backend now strips
diff --git a/src/reader/ArticleLanguageModal.js b/src/reader/ArticleLanguageModal.js
index 001e6e29b..252cc2048 100644
--- a/src/reader/ArticleLanguageModal.js
+++ b/src/reader/ArticleLanguageModal.js
@@ -22,15 +22,7 @@ function LevelBars({ level }) {
aria-hidden="true"
>
{CEFR_BAR_GEOMETRY.map((bar, i) => (
-
+
))}
);
@@ -128,9 +120,7 @@ export default function ArticleLanguageModal({
<>
Article is above your level
-
- Do you want to adapt it before saving?
-
+ Do you want to adapt it before saving?
>
);
} else if (articleCefrLevel) {
diff --git a/src/reader/ArticleReader.sc.js b/src/reader/ArticleReader.sc.js
index c26c6dd1f..8b6968957 100644
--- a/src/reader/ArticleReader.sc.js
+++ b/src/reader/ArticleReader.sc.js
@@ -208,23 +208,6 @@ let Title = styled.div`
line-height: 2em;
`;
-let BookmarkButton = styled.div`
- cursor: pointer;
- float: right;
- img {
- height: 1.4em;
- }
-
- .tooltiptext {
- visibility: hidden;
- color: ${zeeguuOrange};
- }
-
- :hover .tooltiptext {
- visibility: visible;
- }
-`;
-
let MainText = styled.div`
font-size: 1.2em;
/* 2em leading floated the lines too far apart to track comfortably; 1.7
@@ -480,7 +463,6 @@ export {
TopReaderButtonsContainer,
Toolbar,
Title,
- BookmarkButton,
MainText,
BottomFade,
OrangeButton,
diff --git a/src/reader/BookmarkButton.js b/src/reader/BookmarkButton.js
deleted file mode 100644
index 1db48ce6e..000000000
--- a/src/reader/BookmarkButton.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import strings from "../i18n/definitions";
-
-export default function BookmarkButton({ bookmarked, toggleBookmarkedState }) {
- let fileName = (bookmarked ? "bookmarked" : "not-bookmarked") + ".svg";
- let actionDescription =
- //(bookmarked ? "Remove from" : "Add to") + " bookmarks";
- (bookmarked ? strings.removeFromBookmarks : strings.addToBookmarks)
-
- return (
- <>
- {actionDescription}
-
- >
- );
-}
diff --git a/src/reader/TranslatableWord.js b/src/reader/TranslatableWord.js
index a5704bdae..171b23b98 100644
--- a/src/reader/TranslatableWord.js
+++ b/src/reader/TranslatableWord.js
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef } from "react";
import AlterMenu from "./AlterMenu";
+import TranslationDisplay from "./TranslationDisplay";
import extractDomain from "../utils/web/extractDomain";
import addProtocolToLink from "../utils/web/addProtocolToLink";
import redirect from "../utils/routing/routing";
@@ -7,7 +8,6 @@ import LinkOffIcon from "@mui/icons-material/LinkOff";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import EditBookmarkModal from "../words/EditBookmarkModal";
-
export default function TranslatableWord({
interactiveText,
word,
@@ -80,25 +80,32 @@ export default function TranslatableWord({
}
// For MWE words, update prevWord to fused text and trigger re-render
// This ensures the loading animation shows "suntem prezenți" not just "suntem"
- const onFusionComplete = mweGroupId ? () => {
- setPreviousWord(word.word); // word.word is now the fused MWE text
- wordUpdated();
- } : null;
- interactiveText.translate(word, true, () => {
- wordUpdated();
- setIsLoading(false);
- setIsWordTranslating(false);
- setIsTranslationVisible(true);
- // Clear MWE loading state
- if (mweGroupId && setLoadingMWEGroupId) {
- setLoadingMWEGroupId(null);
- }
- // Backend flagged a 3-way provider disagreement; surface the
- // alternatives menu so the user can pick the right answer.
- if (word.disagreement) {
- openAlterMenu(word);
- }
- }, onFusionComplete);
+ const onFusionComplete = mweGroupId
+ ? () => {
+ setPreviousWord(word.word); // word.word is now the fused MWE text
+ wordUpdated();
+ }
+ : null;
+ interactiveText.translate(
+ word,
+ true,
+ () => {
+ wordUpdated();
+ setIsLoading(false);
+ setIsWordTranslating(false);
+ setIsTranslationVisible(true);
+ // Clear MWE loading state
+ if (mweGroupId && setLoadingMWEGroupId) {
+ setLoadingMWEGroupId(null);
+ }
+ // Backend flagged a 3-way provider disagreement; surface the
+ // alternatives menu so the user can pick the right answer.
+ if (word.disagreement) {
+ openAlterMenu(word);
+ }
+ },
+ onFusionComplete,
+ );
} else {
// For non-translatable words in exercises, track the click
if (interactiveText.trackWordClick) {
@@ -322,8 +329,10 @@ export default function TranslatableWord({
if (word.token.is_punct) {
classes.push("punct", "no-hover");
}
- if (word.token.is_left_punct ||
- (word.token.is_punct && word.prev && [":", ".", ","].includes(word.prev.word.trim()))) {
+ if (
+ word.token.is_left_punct ||
+ (word.token.is_punct && word.prev && [":", ".", ","].includes(word.prev.word.trim()))
+ ) {
classes.push("left-punct");
}
if (["–", "—", "\u201c", "\u2019", '"'].includes(word.word.trim())) {
@@ -368,7 +377,9 @@ export default function TranslatableWord({
}
function getWordClass(word) {
- return [...getSpecialTokenClasses(word), ...getMWEClasses(word), ...getSolutionHighlightClasses()].filter(Boolean).join(" ");
+ return [...getSpecialTokenClasses(word), ...getMWEClasses(word), ...getSolutionHighlightClasses()]
+ .filter(Boolean)
+ .join(" ");
}
const wordClass = getWordClass(word);
@@ -407,7 +418,8 @@ export default function TranslatableWord({
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
- {word.word + (word.token.has_space === true && !word.word.endsWith("-") && !word.next?.word?.startsWith("-") ? " " : "")}
+ {word.word +
+ (word.token.has_space === true && !word.word.endsWith("-") && !word.next?.word?.startsWith("-") ? " " : "")}
>
);
@@ -416,40 +428,21 @@ export default function TranslatableWord({
return (
<>
- {word.translation && (isTranslationVisible || word.isTranslationVisible) && (
-
-
-
- {
- // Toggle both React state and word object flag
- const newVisibility = !(isTranslationVisible || word.isTranslationVisible);
- setIsTranslationVisible(newVisibility);
- word.isTranslationVisible = newVisibility;
- setShowingAlterMenu(false);
- }}
- />
-
- toggleAlterMenu(e, word)}>
- {word.translation}
-
- toggleAlterMenu(e, word)}>
- {showingAlterMenu ? "▲" : "▼"}
-
- {word.mergedTokens.length > 1 && !word.mweExpression && (
-
- {
- unlinkLastWord(e, word);
- }}
- />
-
- )}
-
-
- )}
+ {
+ const newVisibility = !(isTranslationVisible || word.isTranslationVisible);
+ setIsTranslationVisible(newVisibility);
+ word.isTranslationVisible = newVisibility;
+ setShowingAlterMenu(false);
+ }}
+ />
{isWordTranslating ? (
{prevWord}
diff --git a/src/reader/TranslationDisplay.js b/src/reader/TranslationDisplay.js
new file mode 100644
index 000000000..7d7726abc
--- /dev/null
+++ b/src/reader/TranslationDisplay.js
@@ -0,0 +1,48 @@
+import LinkOffIconButton from "../components/Icons/LinkOffIconButton";
+import VisibilityOffIconButton from "../components/Icons/VisibilityOffIconButton";
+
+export default function TranslationDisplay({
+ translation,
+ isTranslationVisible,
+ word,
+ showingAlterMenu,
+ refToTranslation,
+ toggleAlterMenu,
+ unlinkLastWord,
+ onTranslationVisibilityToggle,
+}) {
+ if (!translation || !(isTranslationVisible || word.isTranslationVisible)) {
+ return null;
+ }
+
+ return (
+
+
+
+ {
+ e.stopPropagation();
+ onTranslationVisibilityToggle();
+ }}
+ />
+
+ toggleAlterMenu(e, word)}>
+ {translation}
+
+ toggleAlterMenu(e, word)}>
+ {showingAlterMenu ? "▲" : "▼"}
+
+ {word.mergedTokens.length > 1 && !word.mweExpression && (
+
+ {
+ e.stopPropagation();
+ unlinkLastWord(e, word);
+ }}
+ />
+
+ )}
+
+
+ );
+}
diff --git a/src/stories/Bars/ExerciseSessionProgressBar.stories.js b/src/stories/Bars/ExerciseSessionProgressBar.stories.js
new file mode 100644
index 000000000..af50d7a76
--- /dev/null
+++ b/src/stories/Bars/ExerciseSessionProgressBar.stories.js
@@ -0,0 +1,10 @@
+import ExerciseSessionProgressBar from "../../exercises/ExerciseSessionProgressBar";
+
+export default {
+ title: "Bars/ExerciseSessionProgressBar",
+ component: ExerciseSessionProgressBar,
+};
+
+export const Default = {
+ render: () => ,
+};
diff --git a/src/stories/Bars/LevelIndicator.stories.js b/src/stories/Bars/LevelIndicator.stories.js
new file mode 100644
index 000000000..e17743823
--- /dev/null
+++ b/src/stories/Bars/LevelIndicator.stories.js
@@ -0,0 +1,63 @@
+import LevelIndicator from "../../exercises/progressBars/levelIndicator/LevelIndicator";
+
+export default {
+ title: "Bars/LevelIndicator",
+ component: LevelIndicator,
+};
+
+const bookmark = {
+ from: "learn",
+ to: "apprendre",
+ level: 2,
+ cooling_interval: 1,
+ is_last_in_cycle: false,
+};
+
+export const InProgress = {
+ args: {
+ bookmark,
+ userIsCorrect: false,
+ userIsWrong: false,
+ isGreyedOutBar: false,
+ },
+};
+
+export const Correct = {
+ args: {
+ bookmark,
+ userIsCorrect: true,
+ userIsWrong: false,
+ isGreyedOutBar: false,
+ },
+};
+
+export const Incorrect = {
+ args: {
+ bookmark,
+ userIsCorrect: false,
+ userIsWrong: true,
+ isGreyedOutBar: false,
+ },
+};
+
+export const GreyedOut = {
+ args: {
+ bookmark: null,
+ userIsCorrect: false,
+ userIsWrong: false,
+ isGreyedOutBar: true,
+ },
+};
+
+export const NewBookmark = {
+ args: {
+ bookmark: {
+ ...bookmark,
+ level: 0,
+ cooling_interval: null,
+ },
+ userIsCorrect: false,
+ userIsWrong: false,
+ isGreyedOutBar: false,
+ },
+};
diff --git a/src/stories/BigComponents/AlternativeMenu.stories.js b/src/stories/BigComponents/AlternativeMenu.stories.js
new file mode 100644
index 000000000..d53a21a79
--- /dev/null
+++ b/src/stories/BigComponents/AlternativeMenu.stories.js
@@ -0,0 +1,28 @@
+import AlterMenu from "../../reader/AlterMenu";
+import "../../index.css";
+
+const baseWord = {
+ translation: "house",
+ alternatives: [
+ { translation: "home", source: "Google - v1" },
+ { translation: "dwelling", source: "Microsoft - v2" },
+ ],
+ competing_translations: null,
+ disagreement: false,
+ mergedTokens: [],
+ _llmAsked: null,
+};
+
+export default { title: "BigComponents/AlterMenu", component: AlterMenu };
+
+export const NoAlternatives = {
+ render: () => ,
+};
+
+export const WithAlternatives = {
+ render: () => ,
+};
+
+export const AskingLLM = {
+ render: () => ,
+};
diff --git a/src/stories/BigComponents/DifficultyFeedbackBox.stories.js b/src/stories/BigComponents/DifficultyFeedbackBox.stories.js
new file mode 100644
index 000000000..7a66ecbc1
--- /dev/null
+++ b/src/stories/BigComponents/DifficultyFeedbackBox.stories.js
@@ -0,0 +1,23 @@
+import DifficultyFeedbackBox from "../../reader/DifficultyFeedbackBox";
+
+export default { title: "BigComponents/DifficultyFeedbackBox", component: DifficultyFeedbackBox };
+
+const noop = () => {};
+
+export const TooEasy = {
+ render: () => (
+
+ ),
+};
+
+export const JustRight = {
+ render: () => (
+
+ ),
+};
+
+export const TooHard = {
+ render: () => (
+
+ ),
+};
diff --git a/src/stories/BigComponents/ExerciseShell.stories.js b/src/stories/BigComponents/ExerciseShell.stories.js
new file mode 100644
index 000000000..c58e71323
--- /dev/null
+++ b/src/stories/BigComponents/ExerciseShell.stories.js
@@ -0,0 +1,50 @@
+import * as s from "../../exercises/Exercises.sc";
+
+export default {
+ title: "BigComponents/ExerciseShell",
+ component: s.ExercisesColumn,
+};
+
+export const Default = {
+ render: () => (
+
+
+
+
+
+
Translate the sentence
+
I have been learning a lot lately.
+
+
Check
+
+
+
+ ),
+};
+
+export const WithMessageAbove = {
+ render: () => (
+
+
+
+ Hint: Pay attention to verb tense
+
+
+
+
Translate the sentence
+
I have been learning a lot lately.
+
+
Check
+
+
+
+ ),
+};
diff --git a/src/stories/BigComponents/LikeFeedbackBox.stories.js b/src/stories/BigComponents/LikeFeedbackBox.stories.js
new file mode 100644
index 000000000..591dd089a
--- /dev/null
+++ b/src/stories/BigComponents/LikeFeedbackBox.stories.js
@@ -0,0 +1,17 @@
+import LikeFeedbackBox from "../../reader/LikeFeedbackBox";
+
+export default { title: "BigComponents/LikeFeedbackBox", component: LikeFeedbackBox };
+
+const noop = () => {};
+
+export const Neutral = {
+ render: () => ,
+};
+
+export const Liked = {
+ render: () => ,
+};
+
+export const Disliked = {
+ render: () => ,
+};
diff --git a/src/stories/BigComponents/TextSizeControl.stories.js b/src/stories/BigComponents/TextSizeControl.stories.js
new file mode 100644
index 000000000..7ed570243
--- /dev/null
+++ b/src/stories/BigComponents/TextSizeControl.stories.js
@@ -0,0 +1,12 @@
+import TextSizeControl from "../../components/Controls/TextSizeControl";
+
+export default {
+ title: "BigComponents/TextSizeControl",
+ component: TextSizeControl,
+};
+
+export const Default = {
+ args: {
+ value: 16,
+ },
+};
diff --git a/src/stories/BigComponents/TranslationDisplay.stories.js b/src/stories/BigComponents/TranslationDisplay.stories.js
new file mode 100644
index 000000000..a37a4699c
--- /dev/null
+++ b/src/stories/BigComponents/TranslationDisplay.stories.js
@@ -0,0 +1,18 @@
+import TranslationDisplay from "../../reader/TranslationDisplay";
+import "../../index.css";
+
+const baseWord = {
+ id: 1,
+ isTranslationVisible: true,
+ mergedTokens: [],
+ mweExpression: false,
+};
+
+export default {
+ title: "BigComponents/TranslationDisplay",
+ component: TranslationDisplay,
+};
+
+export const Default = {
+ render: () => ,
+};
diff --git a/src/stories/Buttons/Button.stories.js b/src/stories/Buttons/Button.stories.js
new file mode 100644
index 000000000..045b01f2e
--- /dev/null
+++ b/src/stories/Buttons/Button.stories.js
@@ -0,0 +1,78 @@
+import React from "react";
+import Button from "../../pages/_pages_shared/Button.sc";
+
+export default {
+ title: "Buttons/Button",
+ component: Button,
+ args: {
+ children: "Continue",
+ },
+};
+
+export const Default = {};
+
+export const Small = {
+ args: {
+ className: "small",
+ },
+};
+
+export const LeftAligned = {
+ args: {
+ className: "left-aligned",
+ },
+};
+
+export const Warning = {
+ args: {
+ className: "warning small",
+ },
+};
+
+export const Grey = {
+ args: {
+ className: "grey small",
+ },
+};
+
+export const Pressed = {
+ args: {
+ className: "pressed",
+ },
+};
+
+export const FullWidth = {
+ args: {
+ className: "full-width-btn",
+ },
+};
+
+export const SmallSquare = {
+ args: {
+ className: "small-square-btn",
+ },
+};
+
+export const SmallBorder = {
+ args: {
+ className: "small-border-btn",
+ },
+};
+
+export const Blue = {
+ args: {
+ className: "blue-btn",
+ },
+};
+
+export const White = {
+ args: {
+ className: "small-border-btn white-btn",
+ },
+};
+
+export const BlueOutline = {
+ args: {
+ className: "small-border-btn blue-outline-btn",
+ },
+};
diff --git a/src/stories/Buttons/ButtonContainer.stories.js b/src/stories/Buttons/ButtonContainer.stories.js
new file mode 100644
index 000000000..134b9f7d9
--- /dev/null
+++ b/src/stories/Buttons/ButtonContainer.stories.js
@@ -0,0 +1,50 @@
+import React from "react";
+import ButtonContainer from "../../pages/_pages_shared/ButtonContainer.sc";
+import Button from "../../pages/_pages_shared/Button.sc";
+
+export default {
+ title: "Buttons/ButtonContainer",
+ component: ButtonContainer,
+};
+
+export const Default = {
+ render: () => (
+
+ Continue
+
+ ),
+};
+
+export const Link = {
+ render: () => (
+
+
+ Learn more
+
+
+ ),
+};
+
+export const AdaptiveAlignmentHorizontal = {
+ render: () => (
+
+ Continue
+
+ ),
+};
+
+export const VerticalPaddingMedium = {
+ render: () => (
+
+ Continue
+
+ ),
+};
+
+export const VerticalPaddingLarge = {
+ render: () => (
+
+ Continue
+
+ ),
+};
diff --git a/src/stories/Buttons/ExerciseButtons.stories.js b/src/stories/Buttons/ExerciseButtons.stories.js
new file mode 100644
index 000000000..252e3f90f
--- /dev/null
+++ b/src/stories/Buttons/ExerciseButtons.stories.js
@@ -0,0 +1,99 @@
+import * as s from "../../exercises/exerciseTypes/Exercise.sc";
+
+export default {
+ title: "Buttons/ExerciseButtons",
+};
+
+export const OrangeButton = {
+ render: () => (
+
+ Orange button
+
+ ),
+};
+
+export const FeedbackButton = {
+ render: () => (
+
+ Next
+
+ ),
+};
+
+export const StyledGreyButton = {
+ render: () => (
+
+ Show solution
+
+ ),
+};
+
+export const BlueButton = {
+ render: () => (
+
+ Change example
+
+ ),
+};
+
+export const MatchButton = {
+ render: () => (
+
+ Match option
+
+ ),
+};
+
+export const LeftFeedbackButton = {
+ render: () => (
+
+ Hint
+
+ ),
+};
+export const RightFeedbackButton = {
+ render: () => (
+
+ Check
+
+ ),
+};
+
+export const ReportButton = {
+ render: () => (
+
+ Report
+
+ ),
+};
+
+export const ReportedBadge = {
+ render: () => (
+
+ Reported
+
+ ),
+};
+
+export const AnimatedOrangeButton = {
+ render: () => (
+
+ Wrong answer
+
+ ),
+};
+export const AnimatedMatchButton = {
+ render: () => (
+
+ Wrong match
+
+ ),
+};
+
+export const AnimatedInput = {
+ render: () => (
+
+
+
+ ),
+};
diff --git a/src/stories/Buttons/FriendActionButtons.stories.js b/src/stories/Buttons/FriendActionButtons.stories.js
new file mode 100644
index 000000000..a0a9dc636
--- /dev/null
+++ b/src/stories/Buttons/FriendActionButtons.stories.js
@@ -0,0 +1,62 @@
+import { FriendActionButtons } from "../../profile/FriendActionButtons";
+import "../../index.css";
+
+const friendActionHandlers = {
+ onAdd: () => {},
+ onCancel: () => {},
+ onAccept: () => {},
+ onReject: () => {},
+ onUnfriend: () => {},
+};
+
+export default {
+ title: "Buttons/FriendActionButtons",
+ component: FriendActionButtons,
+};
+
+export const Add = {
+ render: () => (
+
+ ),
+};
+
+export const PendingRequestFromMe = {
+ render: () => (
+
+ ),
+};
+
+export const PendingRequestFromThem = {
+ render: () => (
+
+ ),
+};
+
+export const Accepted = {
+ render: () => (
+
+ ),
+};
diff --git a/src/stories/Buttons/LogOutButton.stories.js b/src/stories/Buttons/LogOutButton.stories.js
new file mode 100644
index 000000000..154cd9986
--- /dev/null
+++ b/src/stories/Buttons/LogOutButton.stories.js
@@ -0,0 +1,9 @@
+import LogOutButton from "../../pages/Settings/LogOut/LogOutButton";
+import "../../index.css";
+
+export default {
+ title: "Buttons/LogOutButton",
+ component: LogOutButton,
+};
+
+export const Default = {};
diff --git a/src/stories/Buttons/ReviewWordsButton.stories.js b/src/stories/Buttons/ReviewWordsButton.stories.js
new file mode 100644
index 000000000..220e2d9d3
--- /dev/null
+++ b/src/stories/Buttons/ReviewWordsButton.stories.js
@@ -0,0 +1,13 @@
+import ReviewWordsButton from "../../reader/ReviewVocabularyInfoBox";
+import "../../index.css";
+
+export default {
+ title: "Buttons/ReviewWordsButton",
+ component: ReviewWordsButton,
+};
+
+export const Default = {
+ args: {
+ bookmarks: ["word1"],
+ },
+};
diff --git a/src/stories/Buttons/SaveAndEditButton.stories.js b/src/stories/Buttons/SaveAndEditButton.stories.js
new file mode 100644
index 000000000..a4675f4dc
--- /dev/null
+++ b/src/stories/Buttons/SaveAndEditButton.stories.js
@@ -0,0 +1,8 @@
+import SaveAndEditButton from "../../components/Buttons/SaveAndEditButton";
+
+export default {
+ title: "Buttons/SaveAndEditButton",
+ component: SaveAndEditButton,
+};
+
+export const Default = {};
diff --git a/src/stories/Buttons/SelectionButton.stories.js b/src/stories/Buttons/SelectionButton.stories.js
new file mode 100644
index 000000000..3948fcf22
--- /dev/null
+++ b/src/stories/Buttons/SelectionButton.stories.js
@@ -0,0 +1,18 @@
+import SelectionButton from "../../pages/_pages_shared/SelectionButton.sc";
+
+export default {
+ title: "Buttons/SelectionButton",
+ component: SelectionButton,
+ args: {
+ children: "Continue",
+ },
+};
+
+export const Default = {};
+
+export const Selected = {
+ args: {
+ className: "selected",
+ children: "Selected option",
+ },
+};
diff --git a/src/stories/Buttons/SettingsItemButton.stories.js b/src/stories/Buttons/SettingsItemButton.stories.js
new file mode 100644
index 000000000..09c9f1a3f
--- /dev/null
+++ b/src/stories/Buttons/SettingsItemButton.stories.js
@@ -0,0 +1,9 @@
+import SettingsItemButton from "../../pages/Settings/SharedComponents/SettingsItemButton";
+import "../../index.css";
+
+export default {
+ title: "Buttons/SettingsItemButton",
+ component: SettingsItemButton,
+};
+
+export const Default = {};
diff --git a/src/stories/Buttons/SpeakButton.stories.js b/src/stories/Buttons/SpeakButton.stories.js
new file mode 100644
index 000000000..96bc2dd2b
--- /dev/null
+++ b/src/stories/Buttons/SpeakButton.stories.js
@@ -0,0 +1,39 @@
+import SpeakButton from "../../exercises/exerciseTypes/SpeakButton";
+
+export default {
+ title: "Buttons/SpeakButton",
+ component: SpeakButton,
+};
+
+export const Default = {
+ render: () => ,
+};
+export const Small = {
+ args: { styling: "small" },
+};
+
+export const Inline = {
+ args: { styling: "inline" },
+};
+
+export const Large = {
+ args: { styling: "large" },
+};
+
+export const Selected = {
+ args: {
+ styling: "selected",
+ isSelected: true,
+ },
+};
+
+export const Square = {
+ args: { styling: "square" },
+};
+
+export const Speaking = {
+ args: {
+ styling: "next",
+ parentIsSpeakingControl: true,
+ },
+};
diff --git a/src/stories/Configure.mdx b/src/stories/Configure.mdx
new file mode 100644
index 000000000..2edddcf9c
--- /dev/null
+++ b/src/stories/Configure.mdx
@@ -0,0 +1,388 @@
+import { Meta } from "@storybook/addon-docs/blocks";
+
+import Github from "./assets/github.svg";
+import Discord from "./assets/discord.svg";
+import Youtube from "./assets/youtube.svg";
+import Tutorials from "./assets/tutorials.svg";
+import Styling from "./assets/styling.png";
+import Context from "./assets/context.png";
+import Assets from "./assets/assets.png";
+import Docs from "./assets/docs.png";
+import Share from "./assets/share.png";
+import FigmaPlugin from "./assets/figma-plugin.png";
+import Testing from "./assets/testing.png";
+import Accessibility from "./assets/accessibility.png";
+import Theming from "./assets/theming.png";
+import AddonLibrary from "./assets/addon-library.png";
+
+export const RightArrow = () =>
+
+
+
+
+
+
+
+ # Configure your project
+
+ Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community.
+
+
+
+
+
Add styling and CSS
+
Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.
+
Learn more
+
+
+
+
Provide context and mocking
+
Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.
+
Learn more
+
+
+
+
+
Load assets and resources
+
To link static files (like fonts) to your projects and stories, use the
+ `staticDirs` configuration option to specify folders to load when
+ starting Storybook.
+
Learn more
+
+
+
+
+
+
+ # Do more with Storybook
+
+ Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs.
+
+
+
+
+
+
+
Autodocs
+
Auto-generate living,
+ interactive reference documentation from your components and stories.
+
Learn more
+
+
+
+
Publish to Chromatic
+
Publish your Storybook to review and collaborate with your entire team.
+
Learn more
+
+
+
+
Figma Plugin
+
Embed your stories into Figma to cross-reference the design and live
+ implementation in one place.
+
Learn more
+
+
+
+
Testing
+
Use stories to test a component in all its variations, no matter how
+ complex.
+
Learn more
+
+
+
+
Accessibility
+
Automatically test your components for a11y issues as you develop.
+
Learn more
+
+
+
+
Theming
+
Theme Storybook's UI to personalize it to your project.
+
Learn more
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Join our contributors building the future of UI development.
+
+
Star on GitHub
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/stories/Dialogs/CelebrationModal.stories.js b/src/stories/Dialogs/CelebrationModal.stories.js
new file mode 100644
index 000000000..55f672113
--- /dev/null
+++ b/src/stories/Dialogs/CelebrationModal.stories.js
@@ -0,0 +1,12 @@
+import CelebrationModal from "../../exercises/CelebrationModal";
+
+export default {
+ title: "Dialogs/CelebrationModal",
+ component: CelebrationModal,
+};
+
+export const Default = {
+ args: {
+ open: true,
+ },
+};
diff --git a/src/stories/Dialogs/ExplainTopicsModal.stories.js b/src/stories/Dialogs/ExplainTopicsModal.stories.js
new file mode 100644
index 000000000..4f77947c7
--- /dev/null
+++ b/src/stories/Dialogs/ExplainTopicsModal.stories.js
@@ -0,0 +1,10 @@
+import ExplainTopicsModal from "../../pages/ExplainTopicsModal";
+
+export default {
+ title: "Dialogs/ExplainTopicsModal",
+ component: ExplainTopicsModal,
+};
+
+export const Default = {
+ render: () => ,
+};
diff --git a/src/stories/Dialogs/KeyboardTestSection.stories.js b/src/stories/Dialogs/KeyboardTestSection.stories.js
new file mode 100644
index 000000000..18af0a232
--- /dev/null
+++ b/src/stories/Dialogs/KeyboardTestSection.stories.js
@@ -0,0 +1,20 @@
+import KeyboardTestSection from "../../pages/KeyboardTest/KeyboardTestSection";
+import "../../index.css";
+
+export default {
+ title: "Dialogs/KeyboardTestSection",
+ component: KeyboardTestSection,
+};
+
+export const Danish = {
+ args: {
+ title: "Danish Keyboard (da)",
+ value: "",
+ onChange: () => {},
+ placeholder: "Type or use the virtual keyboard below...",
+ languageCode: "da",
+ infoText: "Danish keyboard includes special characters: æ, ø, å",
+ inputMode: "none",
+ initialCollapsed: false,
+ },
+};
diff --git a/src/stories/Dialogs/LanguagesModal.stories.js b/src/stories/Dialogs/LanguagesModal.stories.js
new file mode 100644
index 000000000..e50e688aa
--- /dev/null
+++ b/src/stories/Dialogs/LanguagesModal.stories.js
@@ -0,0 +1,26 @@
+import { LanguagesModal } from "../../profile/LanguagesModal";
+import "../../index.css";
+
+const activeLanguages = [
+ {
+ code: "en",
+ language: "English",
+ daily_streak: 4,
+ max_streak: 12,
+ },
+ {
+ code: "da",
+ language: "Danish",
+ daily_streak: 2,
+ max_streak: 8,
+ },
+];
+
+export default {
+ title: "Dialogs/LanguagesModal",
+ component: LanguagesModal,
+};
+
+export const Default = {
+ render: () => {}} isOwnProfile={true} activeLanguages={activeLanguages} />,
+};
diff --git a/src/stories/Dialogs/RemoveBookmarkModal.stories.js b/src/stories/Dialogs/RemoveBookmarkModal.stories.js
new file mode 100644
index 000000000..1087b1201
--- /dev/null
+++ b/src/stories/Dialogs/RemoveBookmarkModal.stories.js
@@ -0,0 +1,51 @@
+import Header from "../../components/modal_shared/Header.sc";
+import Main from "../../components/modal_shared/Main.sc";
+import ModalTitle from "../../components/modal_shared/ModalTitle.sc";
+import ButtonContainer from "../../components/modal_shared/ButtonContainer.sc";
+import Button from "../../pages/_pages_shared/Button.sc";
+import Modal from "../../components/modal_shared/Modal";
+import FormSection from "../../pages/_pages_shared/FormSection.sc";
+import Form from "../../pages/_pages_shared/Form.sc";
+import TextField from "../../components/TextField";
+
+export default {
+ title: "Dialogs/RemoveBookmarkModal",
+};
+
+export const Default = {
+ render: () => (
+
+
+ Why remove learn/apprendre?
+
+
+
+ I know this word
+ Bad translation
+ Other
+
+
+
+ ),
+};
+
+export const OtherFeedback = {
+ render: () => (
+
+
+ Why remove learn/apprendre?
+
+
+
+
+
+ ),
+};
diff --git a/src/stories/Dialogs/ReplaceExampleModal.stories.js b/src/stories/Dialogs/ReplaceExampleModal.stories.js
new file mode 100644
index 000000000..70de7a6ad
--- /dev/null
+++ b/src/stories/Dialogs/ReplaceExampleModal.stories.js
@@ -0,0 +1,87 @@
+import * as s from "../../exercises/replaceExample/ReplaceExampleModal.sc";
+
+export default {
+ title: "Dialogs/ReplaceExampleModal",
+};
+
+export const Default = {
+ render: () => (
+
+
+
+ Replace Example
+ ×
+
+
+
+ Available Examples
+
+
+ "I have been learning for years"
+ Jeg har været ved at lære i årevis
+ B2
+
+
+
+ "We are studying hard"
+ Vi studerer hårdt
+ A2
+
+
+
+ "They have finished the project"
+ De har afsluttet projektet
+ B1
+
+
+
+
+
+ Cancel
+ Save
+
+
+
+ ),
+};
+
+export const Loading = {
+ render: () => (
+
+
+
+ Replace Example
+ ×
+
+
+
+
+ Loading...
+ Finding alternative examples
+
+
+
+
+ ),
+};
+
+export const Empty = {
+ render: () => (
+
+
+
+ Replace Example
+ ×
+
+
+
+ No alternative examples available for this word.
+
+
+
+ Close
+
+
+
+ ),
+};
diff --git a/src/stories/Dialogs/ReportDialog.stories.js b/src/stories/Dialogs/ReportDialog.stories.js
new file mode 100644
index 000000000..310b8a377
--- /dev/null
+++ b/src/stories/Dialogs/ReportDialog.stories.js
@@ -0,0 +1,22 @@
+import ReportDialog from "../../components/ReportDialog";
+import "../../index.css";
+
+export default {
+ title: "Dialogs/ReportDialog",
+ component: ReportDialog,
+};
+
+export const Default = {
+ args: {
+ open: true,
+ title: "Report broken article",
+ onClose: () => {},
+ onFeedbackChange: () => {},
+ onSubmit: () => {},
+ feedback: "",
+ isSubmitting: false,
+ isFeedbackSent: false,
+ reportInfo: null,
+ error: null,
+ },
+};
diff --git a/src/stories/Dialogs/ReportExerciseDialog.stories.js b/src/stories/Dialogs/ReportExerciseDialog.stories.js
new file mode 100644
index 000000000..cab72b0a3
--- /dev/null
+++ b/src/stories/Dialogs/ReportExerciseDialog.stories.js
@@ -0,0 +1,14 @@
+import ReportExerciseDialog from "../../exercises/exerciseTypes/ReportExerciseDialog";
+
+export default {
+ title: "Dialogs/ReportExerciseDialog",
+ component: ReportExerciseDialog,
+};
+
+export const BeforeExercise = {
+ render: () => {}} isExerciseOver={false} />,
+};
+
+export const AfterExercise = {
+ render: () => {}} isExerciseOver={true} />,
+};
diff --git a/src/stories/Dialogs/ResetPassword/ResetPasswordStep1.stories.js b/src/stories/Dialogs/ResetPassword/ResetPasswordStep1.stories.js
new file mode 100644
index 000000000..6d5c88c7d
--- /dev/null
+++ b/src/stories/Dialogs/ResetPassword/ResetPasswordStep1.stories.js
@@ -0,0 +1,20 @@
+import ResetPasswordStep1 from "../../../pages/ResetPasswordStep1";
+import "../../../index.css";
+
+export default {
+ title: "Dialogs/ResetPassword/ResetPasswordStep1",
+ component: ResetPasswordStep1,
+};
+
+export const Default = {
+ render: () => (
+ {}}
+ validateEmail={() => true}
+ isEmailValid={true}
+ emailErrorMsg={""}
+ notifyEmailSent={() => {}}
+ />
+ ),
+};
diff --git a/src/stories/Dialogs/ResetPassword/ResetPasswordStep2.stories.js b/src/stories/Dialogs/ResetPassword/ResetPasswordStep2.stories.js
new file mode 100644
index 000000000..a6af3eeb8
--- /dev/null
+++ b/src/stories/Dialogs/ResetPassword/ResetPasswordStep2.stories.js
@@ -0,0 +1,11 @@
+import ResetPasswordStep2 from "../../../pages/ResetPasswordStep2";
+import "../../../index.css";
+
+export default {
+ title: "Dialogs/ResetPassword/ResetPasswordStep2",
+ component: ResetPasswordStep2,
+};
+
+export const Default = {
+ render: () => ,
+};
diff --git a/src/stories/Dialogs/SharedArticleErrorDialog.stories.js b/src/stories/Dialogs/SharedArticleErrorDialog.stories.js
new file mode 100644
index 000000000..620b9de43
--- /dev/null
+++ b/src/stories/Dialogs/SharedArticleErrorDialog.stories.js
@@ -0,0 +1,13 @@
+import ErrorDialog from "../../components/ErrorDialog";
+import "../../index.css";
+
+export default {
+ title: "Dialogs/ErrorDialog",
+ component: ErrorDialog,
+};
+
+export const Default = {
+ args: {
+ title: "Could not open video",
+ },
+};
diff --git a/src/stories/Dialogs/UnfriendConfirmModal.stories.js b/src/stories/Dialogs/UnfriendConfirmModal.stories.js
new file mode 100644
index 000000000..3d5a45ae2
--- /dev/null
+++ b/src/stories/Dialogs/UnfriendConfirmModal.stories.js
@@ -0,0 +1,16 @@
+import { UnfriendConfirmModal } from "../../profile/UnfriendConfirmModal";
+
+export default {
+ title: "Dialogs/UnfriendConfirmModal",
+ component: UnfriendConfirmModal,
+};
+
+export const Default = {
+ args: {
+ open: true,
+ onClose: () => {},
+ onConfirm: () => {},
+ displayName: "Ada",
+ isPendingConfirm: false,
+ },
+};
diff --git a/src/stories/Exercises/OrderWordsInput.stories.js b/src/stories/Exercises/OrderWordsInput.stories.js
new file mode 100644
index 000000000..942554977
--- /dev/null
+++ b/src/stories/Exercises/OrderWordsInput.stories.js
@@ -0,0 +1,58 @@
+import * as sOW from "../../exercises/exerciseTypes/orderWords/ExerciseTypeOW.sc.js";
+import OrderWordsInput from "../../exercises/exerciseTypes/orderWords/OrderWordsInput";
+
+const wordPool = [
+ { id: 1, word: "The", status: "normal", inUse: false, feedback: "" },
+ { id: 2, word: "cat", status: "normal", inUse: false, feedback: "" },
+ { id: 3, word: "is", status: "normal", inUse: false, feedback: "" },
+ { id: 4, word: "sleeping", status: "normal", inUse: false, feedback: "" },
+];
+
+const sentencePreview = [
+ { id: 1, word: "The", status: "correct", inUse: true, feedback: "" },
+ { id: 2, word: "cat", status: "incorrect", inUse: true, feedback: "A noun comes here." },
+ { id: 3, word: "is", status: "normal", inUse: false, feedback: "" },
+ { id: 4, word: "sleeping", status: "normal", inUse: false, feedback: "" },
+];
+
+export default {
+ title: "Exercises/OrderWordsInput",
+ component: OrderWordsInput,
+ args: {
+ buttonOptions: wordPool,
+ isWordSoup: false,
+ isCorrect: false,
+ },
+};
+
+export const Default = {
+ render: (args) => (
+
+
+
+ ),
+};
+
+export const WordSoup = {
+ render: (args) => (
+
+
+
+ ),
+ args: {
+ buttonOptions: wordPool,
+ isWordSoup: true,
+ },
+};
+
+export const Constructed = {
+ render: (args) => (
+
+
+
+ ),
+ args: {
+ buttonOptions: sentencePreview,
+ isWordSoup: false,
+ },
+};
diff --git a/src/stories/Headers/ExerciseInstructionHeader.stories.js b/src/stories/Headers/ExerciseInstructionHeader.stories.js
new file mode 100644
index 000000000..5d8331dd5
--- /dev/null
+++ b/src/stories/Headers/ExerciseInstructionHeader.stories.js
@@ -0,0 +1,13 @@
+import ExerciseInstructionHeader from "../../exercises/components/ExerciseInstructionHeader";
+
+export default {
+ title: "Headers/ExerciseInstructionHeader",
+ component: ExerciseInstructionHeader,
+ args: {
+ headline: "Instruction Header",
+ l2Prompt: "example",
+ isExerciseOver: false,
+ },
+};
+
+export const Default = {};
diff --git a/src/stories/Headers/Header.stories.js b/src/stories/Headers/Header.stories.js
new file mode 100644
index 000000000..64387962b
--- /dev/null
+++ b/src/stories/Headers/Header.stories.js
@@ -0,0 +1,18 @@
+import Header from "../../pages/_pages_shared/Header";
+
+export default {
+ title: "Headers/Header",
+ component: Header,
+};
+
+export const Default = {
+ render: () => ,
+};
+
+export const WithoutLogo = {
+ render: () => ,
+};
+
+export const ShowVersion = {
+ render: () => ,
+};
diff --git a/src/stories/Headers/LandingPageTopNav.stories.js b/src/stories/Headers/LandingPageTopNav.stories.js
new file mode 100644
index 000000000..27d9b1cac
--- /dev/null
+++ b/src/stories/Headers/LandingPageTopNav.stories.js
@@ -0,0 +1,15 @@
+import LandingPageTopNav from "../../landingPage/LandingPageTopNav";
+import { MemoryRouter } from "react-router-dom";
+
+export default {
+ title: "Headers/LandingPageTopNav",
+ component: LandingPageTopNav,
+};
+
+export const Default = {
+ render: () => (
+
+
+
+ ),
+};
diff --git a/src/stories/Headers/ProfileHeaderCard.stories.js b/src/stories/Headers/ProfileHeaderCard.stories.js
new file mode 100644
index 000000000..8f54b37c1
--- /dev/null
+++ b/src/stories/Headers/ProfileHeaderCard.stories.js
@@ -0,0 +1,37 @@
+import { ProfileHeaderCard } from "../../profile/ProfileHeaderCard";
+import "../../index.css";
+
+const profileData = {
+ username: "witty beaver",
+ name: "Ada Lovelace",
+ created_at: "2024-01-10T00:00:00.000Z",
+ user_avatar: {
+ image_name: "avatar-1",
+ character_color: "#2f80ed",
+ background_color: "#f2f2f2",
+ },
+ friendship: null,
+};
+
+export default {
+ title: "Headers/ProfileHeaderCard",
+ component: ProfileHeaderCard,
+};
+
+export const Default = {
+ render: () => (
+ {}}
+ onEditProfile={() => {}}
+ friendActionHandlers={{}}
+ isPendingFriendAction={false}
+ />
+ ),
+};
diff --git a/src/stories/Headers/SettingsPageHeader.stories.js b/src/stories/Headers/SettingsPageHeader.stories.js
new file mode 100644
index 000000000..4d3978c6f
--- /dev/null
+++ b/src/stories/Headers/SettingsPageHeader.stories.js
@@ -0,0 +1,13 @@
+import SettingsPageHeader from "../../pages/Settings/SharedComponents/SettingsPageHeader";
+import "../../index.css";
+
+export default {
+ title: "Headers/SettingsPageHeader",
+ component: SettingsPageHeader,
+};
+
+export const Default = {
+ args: {
+ title: "Settings",
+ },
+};
diff --git a/src/stories/Icons/BackArrow.stories.js b/src/stories/Icons/BackArrow.stories.js
new file mode 100644
index 000000000..82aaf2c3f
--- /dev/null
+++ b/src/stories/Icons/BackArrow.stories.js
@@ -0,0 +1,15 @@
+import BackArrow from "../../pages/Settings/SharedComponents/BackArrow";
+import "../../index.css";
+
+export default {
+ title: "Icons/BackArrow",
+ component: BackArrow,
+};
+
+export const Default = {};
+
+export const WithRedirect = {
+ args: {
+ redirectLink: "/account_settings",
+ },
+};
diff --git a/src/stories/Icons/LinkOffButton.stories.js b/src/stories/Icons/LinkOffButton.stories.js
new file mode 100644
index 000000000..ac94f0ee6
--- /dev/null
+++ b/src/stories/Icons/LinkOffButton.stories.js
@@ -0,0 +1,8 @@
+import LinkOffIcon from "../../components/Icons/LinkOffIconButton";
+
+export default {
+ title: "Icons/LinkOffIcon",
+ component: LinkOffIcon,
+};
+
+export const Default = {};
diff --git a/src/stories/Icons/Logo.stories.js b/src/stories/Icons/Logo.stories.js
new file mode 100644
index 000000000..89c8b444c
--- /dev/null
+++ b/src/stories/Icons/Logo.stories.js
@@ -0,0 +1,8 @@
+import Logo from "../../pages/_pages_shared/Logo";
+
+export default {
+ title: "Icons/Logo",
+ component: Logo,
+};
+
+export const Default = {};
diff --git a/src/stories/Icons/ReportIcon.stories.js b/src/stories/Icons/ReportIcon.stories.js
new file mode 100644
index 000000000..c8d403366
--- /dev/null
+++ b/src/stories/Icons/ReportIcon.stories.js
@@ -0,0 +1,13 @@
+import ReportIcon from "../../components/Icons/ReportIcon";
+import "../../index.css";
+
+export default {
+ title: "Icons/ReportIcon",
+ component: ReportIcon,
+};
+
+export const Default = {
+ args: {
+ onClick: () => {},
+ },
+};
diff --git a/src/stories/Icons/ShareArticleButton.stories.js b/src/stories/Icons/ShareArticleButton.stories.js
new file mode 100644
index 000000000..f3a1b1f5e
--- /dev/null
+++ b/src/stories/Icons/ShareArticleButton.stories.js
@@ -0,0 +1,9 @@
+import ShareArticleIcon from "../../reader/ShareArticle";
+import "../../index.css";
+
+export default {
+ title: "Icons/ShareArticleIcon",
+ component: ShareArticleIcon,
+};
+
+export const Default = {};
diff --git a/src/stories/Icons/VisibilityOffIconButton.stories.js b/src/stories/Icons/VisibilityOffIconButton.stories.js
new file mode 100644
index 000000000..348acfbcd
--- /dev/null
+++ b/src/stories/Icons/VisibilityOffIconButton.stories.js
@@ -0,0 +1,8 @@
+import VisibilityOffIconIcon from "../../components/Icons/VisibilityOffIconButton";
+
+export default {
+ title: "Icons/VisibilityOffIcon",
+ component: VisibilityOffIconIcon,
+};
+
+export const Default = {};
diff --git a/src/stories/Items/SettingsItem.stories.js b/src/stories/Items/SettingsItem.stories.js
new file mode 100644
index 000000000..a62c0af43
--- /dev/null
+++ b/src/stories/Items/SettingsItem.stories.js
@@ -0,0 +1,27 @@
+import SettingsItem from "../../pages/Settings/SharedComponents/SettingsItem";
+import { MemoryRouter } from "react-router-dom";
+import "../../index.css";
+
+export default {
+ title: "Items/SettingsItem",
+ component: SettingsItem,
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+export const Default = {
+ render: () => Profile Details ,
+};
+
+export const WithState = {
+ render: () => (
+
+ Language Settings
+
+ ),
+};
diff --git a/src/stories/Lists&Tables/Contributors.stories.js b/src/stories/Lists&Tables/Contributors.stories.js
new file mode 100644
index 000000000..cdc6aa7e2
--- /dev/null
+++ b/src/stories/Lists&Tables/Contributors.stories.js
@@ -0,0 +1,10 @@
+import Contributors from "../../landingPage/Contributors";
+
+export default {
+ title: "Lists&Tables/Contributors",
+ component: Contributors,
+};
+
+export const Default = {
+ render: () => ,
+};
diff --git a/src/stories/Lists&Tables/FriendRow.stories.js b/src/stories/Lists&Tables/FriendRow.stories.js
new file mode 100644
index 000000000..2152d9c1b
--- /dev/null
+++ b/src/stories/Lists&Tables/FriendRow.stories.js
@@ -0,0 +1,26 @@
+import FriendRow from "../../friends/FriendRow";
+
+export default {
+ title: "Lists&Tables/FriendRow",
+ component: FriendRow,
+};
+
+const sampleUser = {
+ username: "brad",
+ name: "Brad Pitt",
+ avatar: {
+ image_name: "1",
+ character_color: "blue",
+ background_color: "light",
+ },
+ languages: [{ code: "en" }, { code: "es" }, { code: "fr" }],
+ friendship: { friend_streak: 12 },
+};
+
+export const Default = {
+ render: () => (
+
+ ),
+};
diff --git a/src/stories/Lists&Tables/LeaderboardRow.stories.js b/src/stories/Lists&Tables/LeaderboardRow.stories.js
new file mode 100644
index 000000000..bcf597451
--- /dev/null
+++ b/src/stories/Lists&Tables/LeaderboardRow.stories.js
@@ -0,0 +1,26 @@
+import LeaderboardRow from "../../leaderboards/LeaderboardRow";
+
+export default {
+ title: "Lists&Tables/LeaderboardRow",
+ component: LeaderboardRow,
+};
+
+const sampleUser = {
+ username: "brad",
+ name: "Brad Pitt",
+ avatar: {
+ image_name: "1",
+ character_color: "blue",
+ background_color: "light",
+ },
+};
+
+export const Default = {
+ render: () => (
+
+ ),
+};
diff --git a/src/stories/Lists&Tables/ListOfSettingsItems.stories.js b/src/stories/Lists&Tables/ListOfSettingsItems.stories.js
new file mode 100644
index 000000000..89aa17441
--- /dev/null
+++ b/src/stories/Lists&Tables/ListOfSettingsItems.stories.js
@@ -0,0 +1,26 @@
+import ListOfSettingsItems from "../../pages/Settings/SharedComponents/ListOfSettingsItems";
+import SettingsItem from "../../pages/Settings/SharedComponents/SettingsItem";
+import { MemoryRouter } from "react-router-dom";
+import "../../index.css";
+
+export default {
+ title: "Lists&Tables/ListOfSettingsItems",
+ component: ListOfSettingsItems,
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+export const Default = {
+ render: () => (
+
+ Profile Details
+ Language Settings
+ Feed Preferences
+
+ ),
+};
diff --git a/src/stories/Messages/CorrectMessage.stories.js b/src/stories/Messages/CorrectMessage.stories.js
new file mode 100644
index 000000000..5ffeac8df
--- /dev/null
+++ b/src/stories/Messages/CorrectMessage.stories.js
@@ -0,0 +1,12 @@
+import CorrectMessage from "../../exercises/exerciseTypes/CorrectMessage";
+
+export default {
+ title: "Messages/CorrectMessage",
+ component: CorrectMessage,
+};
+
+export const Default = {
+ args: {
+ info: "The exercise is correct!",
+ },
+};
diff --git a/src/stories/Messages/EmptyMessageState.stories.js b/src/stories/Messages/EmptyMessageState.stories.js
new file mode 100644
index 000000000..db59d5549
--- /dev/null
+++ b/src/stories/Messages/EmptyMessageState.stories.js
@@ -0,0 +1,16 @@
+import EmptyMessageState from "../../components/EmptyMessageState";
+
+export default {
+ title: "Messages/EmptyMessageState",
+ component: EmptyMessageState,
+ args: {
+ message: "some message",
+ fillHeight: true,
+ },
+};
+
+export const Default = {
+ args: {
+ message: "The exercise is empty!",
+ },
+};
diff --git a/src/stories/Pages/CardPage.stories.js b/src/stories/Pages/CardPage.stories.js
new file mode 100644
index 000000000..80941d1b1
--- /dev/null
+++ b/src/stories/Pages/CardPage.stories.js
@@ -0,0 +1,102 @@
+import CardPage from "../../pages/_pages_shared/CardPage";
+import { MemoryRouter } from "react-router-dom";
+import LogIn from "../../pages/LogIn";
+import NotFound from "../../pages/NotFound";
+import "../../index.css";
+
+export default {
+ title: "Pages/CardPage",
+ component: CardPage,
+ args: {
+ pageWidth: "default",
+ layoutVariant: "default",
+ isBackgroundFixed: false,
+ isTransparent: false,
+ reducedPadding: false,
+ defaultEmail: "user@example.com",
+ defaultPassword: "password",
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+export const Default = {
+ render: (args) => (
+
+ Card Page
+ This is sample content inside the card.
+
+ ),
+};
+
+export const NarrowWidth = {
+ args: {
+ pageWidth: "narrow",
+ },
+ render: (args) => (
+
+ Narrow Card
+ Uses the narrow page width variant.
+
+ ),
+};
+
+export const CardUnderMenu = {
+ args: {
+ layoutVariant: "card-under-menu",
+ },
+ render: (args) => (
+
+ Card Under Menu
+ Uses the card-under-menu layout variant.
+
+ ),
+};
+
+export const FixedBackground = {
+ args: {
+ isBackgroundFixed: true,
+ },
+ render: (args) => (
+
+ Fixed Background
+ Backdrop is fixed to viewport.
+
+ ),
+};
+
+export const TransparentCard = {
+ args: {
+ isTransparent: true,
+ },
+ render: (args) => (
+
+ Transparent Card
+ Card surface background is transparent.
+
+ ),
+};
+
+export const ReducedPadding = {
+ args: {
+ reducedPadding: true,
+ },
+ render: (args) => (
+
+ Reduced Padding
+ Card uses compact inner spacing.
+
+ ),
+};
+export const LoginPage = {
+ render: () => {}} />,
+};
+
+export const NotFoundPage = {
+ render: () => ,
+};
diff --git a/src/stories/Pages/Form.stories.js b/src/stories/Pages/Form.stories.js
new file mode 100755
index 000000000..bbdd71b36
--- /dev/null
+++ b/src/stories/Pages/Form.stories.js
@@ -0,0 +1,17 @@
+import Form from "../../pages/_pages_shared/Form.sc";
+import Button from "../../pages/_pages_shared/Button.sc";
+
+export default {
+ title: "Pages/Form",
+ component: Form,
+};
+
+export const Default = {
+ render: () => (
+
+ ),
+};
diff --git a/src/stories/Pages/FormSection.stories.js b/src/stories/Pages/FormSection.stories.js
new file mode 100644
index 000000000..afeec04c1
--- /dev/null
+++ b/src/stories/Pages/FormSection.stories.js
@@ -0,0 +1,16 @@
+import FormSection from "../../pages/_pages_shared/FormSection.sc";
+
+export default {
+ title: "Pages/FormSection",
+ component: FormSection,
+};
+
+export const Default = {
+ render: () => (
+
+ Email
+
+ We will only use this for account notifications.
+
+ ),
+};
diff --git a/src/stories/Pages/HowToDeleteAccount.stories.js b/src/stories/Pages/HowToDeleteAccount.stories.js
new file mode 100644
index 000000000..4a359e266
--- /dev/null
+++ b/src/stories/Pages/HowToDeleteAccount.stories.js
@@ -0,0 +1,9 @@
+import HowToDeleteAccount from "../../pages/HowToDeleteAccount";
+import "../../index.css";
+
+export default {
+ title: "Pages/HowToDeleteAccount",
+ component: HowToDeleteAccount,
+};
+
+export const Default = {};
diff --git a/src/stories/Pills/Tag.stories.js b/src/stories/Pills/Tag.stories.js
new file mode 100644
index 000000000..5bda50db4
--- /dev/null
+++ b/src/stories/Pills/Tag.stories.js
@@ -0,0 +1,48 @@
+import Tag from "../../pages/_pages_shared/Tag.sc";
+import TagContainer from "../../pages/_pages_shared/TagContainer.sc";
+
+export default {
+ title: "Pills/Tag",
+ component: Tag,
+ args: {
+ children: "Tag",
+ },
+};
+
+export const Default = {};
+
+export const Small = {
+ args: {
+ className: "small",
+ },
+};
+
+export const OutlinedOrange = {
+ args: {
+ className: "outlined-orange",
+ },
+};
+
+export const OutlinedBlue = {
+ args: {
+ className: "outlined-blue",
+ },
+};
+
+export const Selected = {
+ args: {
+ className: "selected",
+ children: "Selected tag",
+ },
+};
+
+// TagContainer is the wrapper that arranges multiple tags in a row/wrap layout.
+export const MultipleTags = () => (
+
+ News
+ tag
+ Sport
+ Science
+ Story
+
+);
diff --git a/src/stories/Tabs/ProfileTabs.stories.js b/src/stories/Tabs/ProfileTabs.stories.js
new file mode 100644
index 000000000..7b219d12c
--- /dev/null
+++ b/src/stories/Tabs/ProfileTabs.stories.js
@@ -0,0 +1,18 @@
+import { ProfileTabs } from "../../profile/ProfileTabs";
+
+export default {
+ title: "Tabs/ProfileTabs",
+ component: ProfileTabs,
+};
+
+export const Default = {
+ args: {
+ tabs: [
+ { key: "profile", label: "Profile" },
+ { key: "settings", label: "Settings" },
+ ],
+ activeTab: "profile",
+ onTabChange: () => {},
+ children: Profile content
,
+ },
+};
diff --git a/src/stories/Text/SolutionFeedbackLinks.stories.js b/src/stories/Text/SolutionFeedbackLinks.stories.js
new file mode 100755
index 000000000..ee2db06dd
--- /dev/null
+++ b/src/stories/Text/SolutionFeedbackLinks.stories.js
@@ -0,0 +1,52 @@
+import SolutionFeedbackLinks from "../../exercises/exerciseTypes/SolutionFeedbackLinks";
+import { EXERCISE_TYPES } from "../../exercises/ExerciseTypeConstants";
+
+export default {
+ title: "Text/SolutionFeedbackLinks",
+ component: SolutionFeedbackLinks,
+};
+
+const exerciseBookmark = {
+ from: "learn",
+ to: "вчити",
+ user_word_id: 1,
+};
+
+const commonArgs = {
+ exerciseBookmark,
+ exerciseType: EXERCISE_TYPES.translateL2toL1,
+ onReportClick: () => {},
+};
+
+export const BeforeExercise = {
+ args: {
+ ...commonArgs,
+ isExerciseOver: false,
+ isReported: false,
+ },
+};
+
+export const AfterExercise = {
+ args: {
+ ...commonArgs,
+ isExerciseOver: true,
+ isReported: false,
+ },
+};
+
+export const WithShareLink = {
+ args: {
+ ...commonArgs,
+ isExerciseOver: true,
+ isReported: false,
+ shareableUrl: "https://zeeguu.org/exercise/example",
+ },
+};
+
+export const Reported = {
+ args: {
+ ...commonArgs,
+ isExerciseOver: true,
+ isReported: true,
+ },
+};
diff --git a/src/stories/Toggles/DarkModeToggle.stories.js b/src/stories/Toggles/DarkModeToggle.stories.js
new file mode 100644
index 000000000..76424313a
--- /dev/null
+++ b/src/stories/Toggles/DarkModeToggle.stories.js
@@ -0,0 +1,31 @@
+import DarkModeToggle from "../../pages/Settings/ThemeToggle/DarkModeToggle";
+import { ThemeContext } from "../../contexts/ThemeContext";
+import "../../index.css";
+
+const ThemeDecorator = (Story, { args }) => (
+ {} }}>
+
+
+);
+
+export default {
+ title: "Toggles/DarkModeToggle",
+ component: DarkModeToggle,
+ decorators: [ThemeDecorator],
+};
+
+export const Default = {
+ args: {
+ value: "auto",
+ },
+};
+export const LightMode = {
+ args: {
+ value: "light",
+ },
+};
+export const DarkMode = {
+ args: {
+ value: "dark",
+ },
+};
diff --git a/src/stories/Toggles/ToggleOption.stories.js b/src/stories/Toggles/ToggleOption.stories.js
new file mode 100644
index 000000000..23c247d96
--- /dev/null
+++ b/src/stories/Toggles/ToggleOption.stories.js
@@ -0,0 +1,9 @@
+import ToggleOption from "../../components/Toggles/ToggleOption";
+import "../../index.css";
+
+export default {
+ title: "Toggles/ToggleOption",
+ component: ToggleOption,
+};
+
+export const Default = {};
diff --git a/src/stories/assets/accessibility.png b/src/stories/assets/accessibility.png
new file mode 100644
index 000000000..6ffe6feab
Binary files /dev/null and b/src/stories/assets/accessibility.png differ
diff --git a/src/stories/assets/accessibility.svg b/src/stories/assets/accessibility.svg
new file mode 100644
index 000000000..107e93f83
--- /dev/null
+++ b/src/stories/assets/accessibility.svg
@@ -0,0 +1 @@
+Accessibility
\ No newline at end of file
diff --git a/src/stories/assets/addon-library.png b/src/stories/assets/addon-library.png
new file mode 100644
index 000000000..95deb38a8
Binary files /dev/null and b/src/stories/assets/addon-library.png differ
diff --git a/src/stories/assets/assets.png b/src/stories/assets/assets.png
new file mode 100644
index 000000000..cfba6817a
Binary files /dev/null and b/src/stories/assets/assets.png differ
diff --git a/src/stories/assets/avif-test-image.avif b/src/stories/assets/avif-test-image.avif
new file mode 100644
index 000000000..530709bc1
Binary files /dev/null and b/src/stories/assets/avif-test-image.avif differ
diff --git a/src/stories/assets/context.png b/src/stories/assets/context.png
new file mode 100644
index 000000000..e5cd249a2
Binary files /dev/null and b/src/stories/assets/context.png differ
diff --git a/src/stories/assets/discord.svg b/src/stories/assets/discord.svg
new file mode 100644
index 000000000..d638958b6
--- /dev/null
+++ b/src/stories/assets/discord.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/stories/assets/docs.png b/src/stories/assets/docs.png
new file mode 100644
index 000000000..a749629df
Binary files /dev/null and b/src/stories/assets/docs.png differ
diff --git a/src/stories/assets/figma-plugin.png b/src/stories/assets/figma-plugin.png
new file mode 100644
index 000000000..8f79b08cd
Binary files /dev/null and b/src/stories/assets/figma-plugin.png differ
diff --git a/src/stories/assets/github.svg b/src/stories/assets/github.svg
new file mode 100644
index 000000000..dc513528c
--- /dev/null
+++ b/src/stories/assets/github.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/stories/assets/share.png b/src/stories/assets/share.png
new file mode 100644
index 000000000..8097a3707
Binary files /dev/null and b/src/stories/assets/share.png differ
diff --git a/src/stories/assets/styling.png b/src/stories/assets/styling.png
new file mode 100644
index 000000000..d341e8263
Binary files /dev/null and b/src/stories/assets/styling.png differ
diff --git a/src/stories/assets/testing.png b/src/stories/assets/testing.png
new file mode 100644
index 000000000..d4ac39a0c
Binary files /dev/null and b/src/stories/assets/testing.png differ
diff --git a/src/stories/assets/theming.png b/src/stories/assets/theming.png
new file mode 100644
index 000000000..1535eb9b8
Binary files /dev/null and b/src/stories/assets/theming.png differ
diff --git a/src/stories/assets/tutorials.svg b/src/stories/assets/tutorials.svg
new file mode 100644
index 000000000..b492a9c66
--- /dev/null
+++ b/src/stories/assets/tutorials.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/stories/assets/youtube.svg b/src/stories/assets/youtube.svg
new file mode 100644
index 000000000..a7515d7e9
--- /dev/null
+++ b/src/stories/assets/youtube.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/words/Learning.js b/src/words/Learning.js
index 63c112429..491de27a7 100644
--- a/src/words/Learning.js
+++ b/src/words/Learning.js
@@ -2,7 +2,7 @@ import React, { useContext, useEffect, useState } from "react";
import { Link } from "react-router-dom";
import LoadingAnimation from "../components/LoadingAnimation";
-import EmptyState from "../components/EmptyState";
+import EmptyMessageState from "../components/EmptyMessageState";
import { setTitle } from "../assorted/setTitle";
import strings from "../i18n/definitions";
import Word from "./Word";
@@ -161,7 +161,7 @@ export default function Learning() {
<>
{inLearning.length === 0 && (
-
diff --git a/src/words/SessionHistory.js b/src/words/SessionHistory.js
index ff029a5b4..ef5434573 100644
--- a/src/words/SessionHistory.js
+++ b/src/words/SessionHistory.js
@@ -1,7 +1,7 @@
import { useState, useEffect, useContext } from "react";
import { Link } from "react-router-dom";
import LoadingAnimation from "../components/LoadingAnimation";
-import EmptyState from "../components/EmptyState";
+import EmptyMessageState from "../components/EmptyMessageState";
import strings from "../i18n/definitions";
import { setTitle } from "../assorted/setTitle";
import { APIContext } from "../contexts/APIContext";
@@ -558,7 +558,7 @@ export default function SessionHistory() {
{loading && }
{!loading && sessions && sessions.length === 0 && (
-
+
)}
{!loading && sessions && sessions.length > 0 && }
diff --git a/src/words/WordHistory.js b/src/words/WordHistory.js
index 702fdaac2..fd96733da 100644
--- a/src/words/WordHistory.js
+++ b/src/words/WordHistory.js
@@ -6,7 +6,7 @@ import strings from "../i18n/definitions";
import * as sc from "../components/ColumnWidth.sc";
import { setTitle } from "../assorted/setTitle";
import { PageTitle } from "../components/PageTitle";
-import EmptyState from "../components/EmptyState";
+import EmptyMessageState from "../components/EmptyMessageState";
import { APIContext } from "../contexts/APIContext";
export default function ReadingHistory() {
@@ -37,12 +37,7 @@ export default function ReadingHistory() {
return (
{strings.wordHistoryTitle}
- {wordsByDay.length === 0 && (
-
- )}
+ {wordsByDay.length === 0 && }
{wordsByDay.map((day) => (
))}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 000000000..35c7bb11a
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "lib": ["ES2020", "DOM"],
+ "jsx": "react-jsx",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": false,
+ "resolveJsonModule": true,
+ "noEmit": true
+ },
+ "include": ["src", ".storybook"],
+ "exclude": ["node_modules"]
+}
diff --git a/vitest.shims.d.ts b/vitest.shims.d.ts
new file mode 100644
index 000000000..f923d47d4
--- /dev/null
+++ b/vitest.shims.d.ts
@@ -0,0 +1 @@
+///
\ No newline at end of file