Browse Source

feat: add experience story support for mobile

pull/2/head
Muthu Kumar 3 months ago
parent
commit
c11f785409
Failed to extract signature
  1. 166
      src/components/Exp/Story.tsx
  2. 2
      src/components/Exp/Tags.tsx
  3. 37
      src/components/Exp/Unit.tsx
  4. 6
      src/pages/main/Exp.tsx

166
src/components/Exp/Story.tsx

@ -1,6 +1,8 @@
import React from "react"; import React from "react";
import { css, cx } from "@emotion/css"; import { css, cx } from "@emotion/css";
import { ReactComponent as Close } from "../../assets/close.svg";
import { Experience } from "./types"; import { Experience } from "./types";
import { offscreenWidth } from "../constants";
const story = css` const story = css`
position: absolute; position: absolute;
@ -9,43 +11,134 @@ const story = css`
border-radius: 0.5rem; border-radius: 0.5rem;
display: flex; display: flex;
overflow: hidden; & * {
line-height: 140%;
& .contents { }
padding: 1.5rem;
line-height: 1.25rem;
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 2rem;
margin-block-start: 1rem;
height: var(--story-height);
& ul { & .story-content {
& > div {
max-height: 100%; max-height: 100%;
margin: 0; margin: 0;
column-count: 3; column-count: 3;
column-gap: 2rem; column-gap: 2.5rem;
color: var(--text-subdued); color: var(--text-subdued);
font-weight: 400; font-weight: 400;
& li + li { & p {
margin-block-start: 0.5rem; font-size: 0.9rem;
}
& > p + p {
margin-block-start: 0.5em;
} }
}
}
/* desktop & tablet */
@media screen and (min-width: ${offscreenWidth}) {
/* offset padding */
transform: translateX(calc(var(--item-padding) * -1));
& .story-handle {
display: none;
}
& .story-content {
padding: 1.5rem;
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 3rem;
margin-block-start: 1rem;
height: var(--story-height);
}
}
/* mobile */
@media screen and (max-width: ${offscreenWidth}) {
position: fixed;
display: flex;
justify-content: center;
height: calc(100vh - 10rem);
width: 100vw;
left: 0;
z-index: 900;
font-size: 1.25rem;
background: var(--bg-colour);
border-inline: 1px solid var(--offscreen-handle);
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior: contain;
/*
Push this offscreen element out of screen;
Unit.ts will pull it up when .active
*/
top: 100vh;
& .story-handle {
content: "";
width: 100%;
height: 1rem;
background-color: var(--offscreen-handle);
position: absolute;
top: 0;
left: 0;
& li::marker { &::before {
content: ""; content: "";
font-weight: 800; position: absolute;
padding-top: 1rem; width: 2rem;
height: 0.2rem;
background-color: var(--offscreen-handle-tab);
z-index: 900;
inset: 0;
top: 0;
margin: auto;
}
}
& .story-content {
display: flex;
flex-direction: column;
gap: 3rem;
/* height: 100%; */
max-width: 40rem;
margin-block: 4rem;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
& > img {
margin-inline: 4rem;
}
& > div {
column-count: unset;
padding-inline: 4rem;
height: 100%;
overflow-y: auto;
& p {
font-size: 1rem;
}
}
& > .closer {
display: flex;
} }
} }
} }
`; `;
export const Story = ({ description, logo }: Experience) => { export const Story = ({ description, logo, active }: Experience) => {
return ( return (
<div className={cx(story, "story")}> <div
<div className="contents"> className={cx(story, "story")}
id={active ? "active-story" : undefined}>
<div aria-hidden className="story-handle" />
<div className="story-content">
<img <img
src={`/assets/logos/` + logo} src={`/assets/logos/` + logo}
className={cx( className={cx(
@ -59,7 +152,36 @@ export const Story = ({ description, logo }: Experience) => {
`, `,
)} )}
/> />
<ul>{description}</ul> <button
className={cx(
"closer",
css`
display: none;
appearance: none;
border: none;
background: var(--card-active);
color: var(--text-subdued);
width: 3rem;
height: 3rem;
border-radius: 3rem;
/* set in mobile mode */
/* display: flex; */
justify-content: center;
align-items: center;
position: absolute;
top: 4rem;
right: 4rem;
cursor: pointer;
transform: rotate(90deg);
`,
)}
onClick={() => window.history.back()}>
<Close />
</button>
<div>{description}</div>
</div> </div>
</div> </div>
); );

2
src/components/Exp/Tags.tsx

@ -100,7 +100,7 @@ export const Clear = (props: { selected: Tags }) => {
fill: var(--text-colour); fill: var(--text-colour);
`} `}
/> />
Clear Clear filters
</button> </button>
); );
}; };

37
src/components/Exp/Unit.tsx

@ -3,24 +3,25 @@ import { css, cx } from "@emotion/css";
import { Story } from "./Story"; import { Story } from "./Story";
import { Experience } from "./types"; import { Experience } from "./types";
import { Content } from "./Content"; import { Content } from "./Content";
import { offscreenWidth } from "../constants";
const expUnit = css` const expUnit = css`
--final-height: 20rem; --final-height: 20rem;
--unit-height: 9rem; --unit-height: 9rem;
--story-height: calc(var(--final-height) - var(--unit-height)); --story-height: calc(var(--final-height) - var(--unit-height));
--transition-time: 300ms; --transition-time: 200ms;
& > * { & > * {
line-height: 1em; line-height: 1em;
font-size: 1rem; font-size: 1rem;
} }
& button { & > button {
border: 1px solid transparent; border: 1px solid transparent;
transition: all calc(var(--transition-time) * 2); transition: all calc(var(--transition-time) * 2);
} }
&.active button { &.active > button {
background-color: var(--card-active); background-color: var(--card-active);
border: 1px solid var(--card-active-border); border: 1px solid var(--card-active-border);
box-shadow: 0 0 50rem 0 rgba(190, 190, 190, 0.5); box-shadow: 0 0 50rem 0 rgba(190, 190, 190, 0.5);
@ -41,19 +42,34 @@ const expUnit = css`
& .story { & .story {
opacity: 0; opacity: 0;
transition: opacity var(--transition-time) ease-in-out; transition: all calc(var(--transition-time)) ease-in-out;
transition-delay: 0; transition-delay: 0ms;
} }
&.active { &.active {
height: var(--final-height); transition-delay: 0ms;
transition-delay: 0;
transition-delay: var(--transition-time); .timeline-circle {
background: #ffffff;
}
.story { .story {
transition-delay: var(--transition-time);
opacity: 1; opacity: 1;
transition: opacity calc(var(--transition-time) * 2) ease-in-out; }
@media screen and (min-width: ${offscreenWidth}) {
transition-delay: var(--transition-time); transition-delay: var(--transition-time);
height: var(--final-height);
}
@media screen and (max-width: ${offscreenWidth}) {
.story {
transition-delay: 0ms;
position: fixed;
inset: 0;
top: 10rem;
}
} }
} }
@ -61,8 +77,9 @@ const expUnit = css`
`; `;
export const ExpUnit = (props: Experience) => { export const ExpUnit = (props: Experience) => {
const { active } = props;
return ( return (
<div className={cx(expUnit, { active: props.active })}> <div className={cx(expUnit, { active })}>
<Content {...props} /> <Content {...props} />
<Story {...props} /> <Story {...props} />
</div> </div>

6
src/pages/main/Exp.tsx

@ -19,7 +19,7 @@ const Exp: React.FC = () => {
return null; return null;
} }
const slug = location.replace("/experience/", "").replace("/", ""); const slug = location.replace(/^\/experience\/?/, "").replace("/", "");
useEffect(() => { useEffect(() => {
const handler = (e: KeyboardEvent) => { const handler = (e: KeyboardEvent) => {
@ -82,7 +82,9 @@ const Exp: React.FC = () => {
{...unit} {...unit}
onClick={() => { onClick={() => {
if (slug === unit.slug) return navigate("/experience"); if (slug === unit.slug) return navigate("/experience");
navigate(`/experience/${unit.slug}`); if (slug)
navigate(`/experience/${unit.slug}`, { replace: true });
else navigate(`/experience/${unit.slug}`);
}} }}
/> />
))} ))}

Loading…
Cancel
Save