Skip to content

Commit

Permalink
iOS big card allow scrollable (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
kubk authored Feb 5, 2024
1 parent 2f46c10 commit 853e0da
Show file tree
Hide file tree
Showing 18 changed files with 195 additions and 17 deletions.
4 changes: 4 additions & 0 deletions src/lib/mobx-form/boolean-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ export class BooleanToggle implements FieldWithValue<boolean> {
setFalse() {
this.value = false;
}

setValue(value: boolean) {
this.value = value;
}
}
18 changes: 18 additions & 0 deletions src/lib/react/use-is-overflowing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useEffect, useRef } from "react";

export const useIsOverflowing = (
key: boolean,
setIsOverflowing: (is: boolean) => void,
) => {
const ref = useRef<any>(null);

useEffect(() => {
const current = ref.current;
if (current) {
const isContentOverflowing = current.scrollHeight > current.clientHeight;
setIsOverflowing(isContentOverflowing);
}
}, [setIsOverflowing, ref, key]);

return { setIsOverflowing, ref };
};
6 changes: 6 additions & 0 deletions src/screens/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ShareDeckOrFormStoreProvider } from "./share-deck/store/share-deck-stor
import { FolderFormStoreProvider } from "./folder-form/store/folder-form-store-context.tsx";
import { FolderScreen } from "./folder-review/folder-screen.tsx";
import { useSettingsButton } from "../lib/telegram/use-settings-button.ts";
import { ComponentCatalogPage } from "./component-catalog/component-catalog-page.tsx";

export const App = observer(() => {
useRestoreFullScreenExpand();
Expand Down Expand Up @@ -119,6 +120,11 @@ export const App = observer(() => {
</DeckCatalogStoreContextProvider>
</PreventTelegramSwipeDownClosingIos>
)}
{screenStore.screen.type === "componentCatalog" && (
<PreventTelegramSwipeDownClosingIos>
<ComponentCatalogPage />
</PreventTelegramSwipeDownClosingIos>
)}
</div>
);
});
45 changes: 45 additions & 0 deletions src/screens/component-catalog/card-preview-story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { CardPreview } from "../deck-form/card-preview.tsx";
import { useState } from "react";
import { CardFormStoreInterface } from "../deck-form/store/card-form-store-interface.ts";
import { TextField } from "../../lib/mobx-form/text-field.ts";
import { CardAnswerType } from "../../../functions/db/custom-types.ts";
import { BooleanToggle } from "../../lib/mobx-form/boolean-toggle.ts";
import { CardAnswerFormType } from "../deck-form/store/deck-form-store.ts";
import { ListField } from "../../lib/mobx-form/list-field.ts";

const createCardPreviewForm = (card: {
front: string;
back: string;
example?: string;
}): CardFormStoreInterface => {
return {
cardForm: {
front: new TextField<string>(card.front),
back: new TextField<string>(card.back),
example: new TextField<string>(card.example ?? ""),
answerType: new TextField<CardAnswerType>("remember"),
answerFormType: "new",
answers: new ListField<CardAnswerFormType>([]),
answerId: "0",
},
form: undefined,
isCardPreviewSelected: new BooleanToggle(false),
isSaveCardButtonActive: false,
onBackCard: () => {},
onSaveCard: () => {},
isSending: false,
markCardAsRemoved: () => {},
};
};

export const CardPreviewStory = (props: {
card: {
front: string;
back: string;
example?: string;
};
}) => {
const [form] = useState(createCardPreviewForm(props.card));

return <CardPreview form={form} onBack={() => {}} />;
};
38 changes: 38 additions & 0 deletions src/screens/component-catalog/component-catalog-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Screen } from "../shared/screen.tsx";
import { useState } from "react";
import { useBackButton } from "../../lib/telegram/use-back-button.tsx";
import { css } from "@emotion/css";
import { theme } from "../../ui/theme.tsx";
import { Component, components } from "./components.tsx";

export const ComponentCatalogPage = () => {
const [selectedComponent, setSelectedComponent] = useState<Component | null>(
null,
);
useBackButton(() => {
setSelectedComponent(null);
});

if (selectedComponent) {
return selectedComponent.component;
}

return (
<Screen title={"Component catalog"}>
<ul>
{components.map((component) => (
<li
key={component.name}
onClick={() => setSelectedComponent(component)}
className={css({
cursor: "pointer",
color: theme.linkColor,
})}
>
{component.name}
</li>
))}
</ul>
</Screen>
);
};
42 changes: 42 additions & 0 deletions src/screens/component-catalog/components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Button } from "../../ui/button.tsx";
import { ReactNode } from "react";
import { CardPreviewStory } from "./card-preview-story.tsx";

export type Component = { name: string; component: ReactNode };

export const components: Array<Component> = [
{
name: "Button",
component: <Button>Button</Button>,
},

{
name: "Button - outline",
component: <Button outline>Button</Button>,
},
{
name: "Card preview - normal",
component: (
<CardPreviewStory
card={{
example: "Example",
back: "Back",
front: "Front",
}}
/>
),
},
{
name: "Card preview - big text",
component: (
<CardPreviewStory
card={{
example: "Example",
front: "Front",
// eslint-disable-next-line
back: `<ul><li>FAS-Risiko</li><li>viele Eltern meiden den Kontakt zum Hilfesystem</li><li>keine verlässliche Bezugsperson/häufiger Wechsel</li><li>Erleben von Armut/Arbeitslosigkeit/beengten Wohnverhältnissen</li><li>desolater elterlicher Gesundheitszustand/Notfälle/Sorge um die Eltern</li><li>Gefährdung durch elterliche Intoxikation (direkte Gewalterfahrung oder Zeugenschaft)</li><li>Familienklima: Familiengeheimnisse, Tabuisierungen, Loyalitätskon-flikte</li><li>kindliche Verhaltensauffälligkeiten (Hyperaktivität, Angste, Rückzug, extrem schüchtern)</li><li>typische Rollen und Familienregeln (Umkreisen und Verschweigen des Alkoholthemas)</li><li>Familienstruktur: aufgeweichte Generationsgrenzen, altersunangemes-sene Aufgaben,</li><li>erhöhte Gefahr emotionalen/sexuellen Mißbrauchs</li></ul><p>grundlegende Stressoren:</p><ul><li>elterliche Unzuverlässigkeit</li><li>Bindungsstörungen: unsicher, ambivalent</li><li>Duldungs- und Katastrophenstress</li><li>Krisenstress (bei Unfähigkeit zur Problembewältigung)</li><li>überfordernde Aufgaben und Rollen</li></ul><p>Quelle: GVS (2012, 2014)</p>`,
}}
/>
),
},
];
2 changes: 1 addition & 1 deletion src/screens/deck-form/card-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const CardList = observer(() => {
padding: 12,
// If the card content is too big then hide it
maxHeight: 120,
overflow: 'hidden',
overflow: "hidden",
...tapScale,
})}
>
Expand Down
9 changes: 7 additions & 2 deletions src/screens/deck-form/card-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { observer } from "mobx-react-lite";
import { useBackButton } from "../../lib/telegram/use-back-button.tsx";
import { css } from "@emotion/css";
import { CardReviewWithControls } from "../deck-review/card-review-with-controls.tsx";
import { useState } from "react";
import React, { useState } from "react";
import { CardPreviewStore } from "../deck-review/store/card-preview-store.ts";
import { CardFormStoreInterface } from "./store/card-form-store-interface.ts";
import { createPortal } from "react-dom";

type Props = {
form: CardFormStoreInterface;
Expand All @@ -19,7 +20,7 @@ export const CardPreview = observer((props: Props) => {
onBack();
});

return (
const component = (
<div
className={css({
display: "flex",
Expand Down Expand Up @@ -60,4 +61,8 @@ export const CardPreview = observer((props: Props) => {
/>
</div>
);

return cardPreviewStore.isOverflowing.value
? createPortal(component, document.body)
: component;
});
2 changes: 1 addition & 1 deletion src/screens/deck-review/card-review-with-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const CardReviewWithControls = observer((props: Props) => {
{card && card.answerType === "remember" && (
<div
className={css({
position: "absolute",
position: "sticky",
bottom: 32,
display: "flex",
alignItems: "center",
Expand Down
4 changes: 1 addition & 3 deletions src/screens/deck-review/deck-finished-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@ import { LazyLoadFramerMotion } from "../../lib/framer-motion/lazy-load-framer-m

type Props = {
children: ReactNode;
marginTop?: string;
};

export const DeckFinishedModal = (props: Props) => {
const { children } = props;
const marginTop = props.marginTop || "200px";

const modal = {
hidden: {
y: "-100vh",
opacity: 0,
},
visible: {
y: marginTop,
y: "32px",
opacity: 1,
transition: { delay: 0.2 },
},
Expand Down
2 changes: 1 addition & 1 deletion src/screens/deck-review/deck-finished.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const DeckFinished = observer((props: Props) => {
useTelegramProgress(() => reviewStore.isReviewSending);

return (
<DeckFinishedModal marginTop={"32px"}>
<DeckFinishedModal>
<div
className={css({
display: "flex",
Expand Down
7 changes: 6 additions & 1 deletion src/screens/deck-review/review.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useBackButton } from "../../lib/telegram/use-back-button.tsx";
import { useHotkeys } from "react-hotkeys-hook";
import { ReviewDeckName } from "./review-deck-name.tsx";
import { CardReviewWithControls } from "./card-review-with-controls.tsx";
import { createPortal } from "react-dom";

export const Review = observer(() => {
const reviewStore = useReviewStore();
Expand Down Expand Up @@ -53,7 +54,7 @@ export const Review = observer(() => {
}
});

return (
const component = (
<div
className={css({
display: "flex",
Expand Down Expand Up @@ -91,4 +92,8 @@ export const Review = observer(() => {
/>
</div>
);

return reviewStore.currentCard?.isOverflowing.value
? createPortal(component, document.body)
: component;
});
4 changes: 4 additions & 0 deletions src/screens/deck-review/store/card-preview-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { speak, SpeakLanguageEnum } from "../../../lib/voice-playback/speak.ts";
import { removeAllTags } from "../../../lib/sanitize-html/remove-all-tags.ts";
import { CardFormStoreInterface } from "../../deck-form/store/card-form-store-interface.ts";
import { assert } from "../../../lib/typescript/assert.ts";
import { BooleanToggle } from "../../../lib/mobx-form/boolean-toggle.ts";

export class CardPreviewStore implements LimitedCardUnderReviewStore {
id: number;
Expand All @@ -26,6 +27,9 @@ export class CardPreviewStore implements LimitedCardUnderReviewStore {

isOpened = false;

// A hack for iOS when the card content is too large
isOverflowing = new BooleanToggle(false);

constructor(cardFormStore: CardFormStoreInterface) {
makeAutoObservable(this, {}, { autoBind: true });
const form = cardFormStore.cardForm;
Expand Down
4 changes: 4 additions & 0 deletions src/screens/deck-review/store/card-under-review-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CardAnswerType } from "../../../../functions/db/custom-types.ts";
import { assert } from "../../../lib/typescript/assert.ts";
import { removeAllTags } from "../../../lib/sanitize-html/remove-all-tags.ts";
import { userStore } from "../../../store/user-store.ts";
import { BooleanToggle } from "../../../lib/mobx-form/boolean-toggle.ts";

export enum CardState {
Remember = "remember",
Expand All @@ -32,6 +33,9 @@ export class CardUnderReviewStore {
isOpened = false;
state?: CardState;

// A hack for iOS when the card content is too large
isOverflowing = new BooleanToggle(false);

constructor(card: DeckCardDbType, deck: DeckWithCardsWithReviewType) {
this.id = card.id;
this.front = card.front;
Expand Down
12 changes: 11 additions & 1 deletion src/screens/shared/card/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { HorizontalDivider } from "../../../ui/horizontal-divider.tsx";
import { CardSpeaker } from "./card-speaker.tsx";
import { CardFieldView } from "./card-field-view.tsx";
import { assert } from "../../../lib/typescript/assert.ts";
import { useIsOverflowing } from "../../../lib/react/use-is-overflowing.ts";

export const cardSize = 310;

Expand All @@ -23,15 +24,23 @@ export type LimitedCardUnderReviewStore = Pick<
| "answer"
| "openWithAnswer"
| "open"
| "isOverflowing"
>;

type Props = {
card: LimitedCardUnderReviewStore;
};

export const Card = observer(({ card }: Props) => {
export const Card = observer((props: Props) => {
const { card } = props;
const { ref: cardRef } = useIsOverflowing(
card.isOpened,
(is) => card?.isOverflowing.setValue(is),
);

return (
<div
ref={cardRef}
className={
card.answerType === "remember"
? css({
Expand All @@ -48,6 +57,7 @@ export const Card = observer(({ card }: Props) => {
placeItems: "center center",
padding: 10,
background: theme.secondaryBgColor,
overflowX: "auto",
})
: css({
color: theme.textColor,
Expand Down
1 change: 1 addition & 0 deletions src/store/screen-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Route =
| { type: "deckCatalog" }
| { type: "shareDeck"; deckId: number; shareId: string }
| { type: "shareFolder"; folderId: number; shareId: string }
| { type: "componentCatalog" }
| { type: "userSettings" };

export type RouteType = Route["type"];
Expand Down
Loading

0 comments on commit 853e0da

Please sign in to comment.