Rename themes after colors (leaf = green, moon = blue)

This commit introduces the language and theme dropdowns as being
invisible, and includes work related to improving how the dropdowns
are implemented.
This commit is contained in:
0x1eef 2023-10-22 20:40:19 -03:00
parent 47847b2545
commit 9e393fcdf7
22 changed files with 88 additions and 109 deletions

View file

@ -100,5 +100,5 @@ body .root .content.theme.ar {
} }
} }
@import "themes/moon"; @import "themes/blue";
@import "themes/leaf"; @import "themes/green";

View file

@ -61,6 +61,20 @@
justify-content: flex-end !important; justify-content: flex-end !important;
} }
.content.theme {
.row.dropdown-row {
.react-select.theme {
cursor: pointer;
ul li.blue {
display: block;
width: 16px;
height: 16px;
background: #000;
}
}
}
}
.content.theme.en {} .content.theme.en {}
.content.theme.ar { .content.theme.ar {
@ -102,5 +116,5 @@
} }
} }
@import "themes/moon"; @import "themes/blue";
@import "themes/leaf"; @import "themes/green";

7
src/css/themes/blue.scss Normal file
View file

@ -0,0 +1,7 @@
@import "blue/layout";
@import "blue/pages/SurahIndex";
@import "blue/pages/SurahStream";
.root .content.theme.blue.ar {
direction: rtl;
}

View file

@ -1,4 +1,4 @@
.root .content.theme.moon { .root .content.theme.blue {
@import "colors"; @import "colors";
.row.title { .row.title {

View file

@ -1,5 +1,5 @@
.root .content.theme.moon { .root .content.theme.blue {
@import "themes/moon/colors"; @import "themes/blue/colors";
.row.title { .row.title {
justify-content: center; justify-content: center;

View file

@ -1,6 +1,6 @@
.root .content.theme.moon { .root .content.theme.blue {
@import "themes/moon/colors"; @import "themes/blue/colors";
@import "themes/moon/components/Icon"; @import "themes/blue/components/Icon";
.row.details { .row.details {
color: $gold-primary; color: $gold-primary;
@ -20,8 +20,8 @@
} }
} }
.root .content.theme.moon.ar { .root .content.theme.blue.ar {
@import "themes/moon/colors"; @import "themes/blue/colors";
.row.dropdown-row { .row.dropdown-row {
line-height: 30px; line-height: 30px;

View file

@ -0,0 +1,7 @@
@import "green/layout";
@import "green/pages/SurahIndex";
@import "green/pages/SurahStream";
.root .content.theme.green.ar {
direction: rtl;
}

View file

@ -1,11 +1,6 @@
.root .content.theme.leaf { .root .content.theme.green {
@import "colors"; @import "colors";
.header .image {
background-image: url("/images/leaf.svg");
background-size: cover;
}
.row.title { .row.title {
color: $green; color: $green;
} }

View file

@ -1,5 +1,5 @@
.root .content.theme.leaf { .root .content.theme.green {
@import "themes/leaf/colors"; @import "themes/green/colors";
ul.body.index a { ul.body.index a {
&:active, &:link, &:visited { &:active, &:link, &:visited {

View file

@ -1,6 +1,6 @@
.root .content.theme.leaf { .root .content.theme.green {
@import "themes/leaf/colors"; @import "themes/green/colors";
@import "themes/leaf/components/Icon"; @import "themes/green/components/Icon";
.row.details { .row.details {
color: $green; color: $green;
@ -33,8 +33,8 @@
} }
} }
.root .content.theme.leaf.ar { .root .content.theme.green.ar {
@import "themes/leaf/colors"; @import "themes/green/colors";
.row.dropdown-row { .row.dropdown-row {
.surah-name { .surah-name {

View file

@ -1,7 +0,0 @@
@import "leaf/layout";
@import "leaf/pages/SurahIndex";
@import "leaf/pages/SurahStream";
.root .content.theme.leaf.ar {
direction: rtl;
}

View file

@ -1,7 +0,0 @@
@import "moon/layout";
@import "moon/pages/SurahIndex";
@import "moon/pages/SurahStream";
.root .content.theme.moon.ar {
direction: rtl;
}

View file

@ -1,14 +1,21 @@
import React from "react"; import React from "react";
import { Select, SelectOption } from "components/Select"; import { Select } from "components/Select";
interface Props { interface Props {
locale: string; locale: string;
onChange: (o: SelectOption) => void; path?: string;
} }
export function LanguageSelect({ locale, onChange }: Props) { export function LanguageSelect({ locale, path = "" }: Props) {
return ( return (
<Select value={locale} className="language" onChange={onChange}> <Select
value={locale}
className="language"
onChange={(el: JSX.Element) => {
const locale = el.props.value;
location.replace([locale, path].join("/"));
}}
>
<option value="ar">عربي</option> <option value="ar">عربي</option>
<option value="en">English</option> <option value="en">English</option>
</Select> </Select>

View file

@ -5,43 +5,20 @@ export type ChangeEvent = React.MouseEvent<HTMLLIElement> & {
target: HTMLLIElement; target: HTMLLIElement;
}; };
export interface SelectOption {
innerText: string;
value: string;
reactEvent: ChangeEvent;
}
interface Props { interface Props {
value: string; value: string;
children: JSX.Element[]; children: JSX.Element[];
onChange: (e: SelectOption) => void; onChange: (e: JSX.Element) => void;
className?: string; className?: string;
} }
const findOption = (value: string, children: JSX.Element[]) => { const find = (option: string, options: JSX.Element[]) => {
const activeOption = children.find(o => o.props.value === value); return options.find(o => o.props.value === option);
if (activeOption) {
return activeOption.props.children;
} else {
return null;
}
};
const createOption = (e: ChangeEvent, children: JSX.Element[]): SelectOption => {
const { target } = e;
const value = target.getAttribute("data-value")!;
return {
innerText: findOption(value, children),
value,
reactEvent: e,
};
}; };
export function Select({ value, children, onChange, className }: Props) { export function Select({ value, children, onChange, className }: Props) {
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
const [activeOption, setActiveOption] = useState<string | null>( const [activeOption, setActiveOption] = useState<JSX.Element>(find(value, children));
findOption(value, children),
);
const openSelect = (e: React.MouseEvent<HTMLSpanElement>) => { const openSelect = (e: React.MouseEvent<HTMLSpanElement>) => {
e.stopPropagation(); e.stopPropagation();
setOpen(true); setOpen(true);
@ -49,9 +26,9 @@ export function Select({ value, children, onChange, className }: Props) {
const selectOption = (e: ChangeEvent) => { const selectOption = (e: ChangeEvent) => {
e.stopPropagation(); e.stopPropagation();
const target: HTMLLIElement = e.target; const target: HTMLLIElement = e.target;
const option = createOption(e, children); const option = find(String(target.value), children);
onChange(option); onChange(option);
setActiveOption(target.innerText); setActiveOption(option);
setOpen(false); setOpen(false);
}; };
@ -61,15 +38,19 @@ export function Select({ value, children, onChange, className }: Props) {
return ( return (
<div className={classnames("react-select", className)}> <div className={classnames("react-select", className)}>
<span className="active-option" onClick={openSelect}> <span
{activeOption} className={classnames("active-option", activeOption.props.value)}
</span> onClick={openSelect}
/>
<ul hidden={!open}> <ul hidden={!open}>
{children.map((option: JSX.Element, key: number) => { {children.map((option: JSX.Element, key: number) => {
return ( return (
<li key={key} data-value={option.props.value} onClick={selectOption}> <li
{option.props.children} key={key}
</li> data-value={option.props.value}
className={option.props.value}
onClick={selectOption}
/>
); );
})} })}
</ul> </ul>

View file

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Select, SelectOption } from "components/Select"; import { Select } from "components/Select";
interface Props { interface Props {
setTheme: (theme: string) => void; setTheme: (theme: string) => void;
@ -11,14 +11,10 @@ export function ThemeSelect({ setTheme, theme }: Props) {
<Select <Select
className="theme" className="theme"
value={theme} value={theme}
onChange={(o: SelectOption) => setTheme(o.value)} onChange={(o: JSX.Element) => setTheme(o.props.value)}
> >
<option value="blue"> <option className="blue" value="blue" />
<span className="blue" /> <option className="green" value="green" />
</option>
<option value="green">
<span className="green" />
</option>
</Select> </Select>
); );
} }

View file

@ -6,13 +6,7 @@ const THEMES: Theme[] = ["blue", "green"];
const DEFAULT_THEME = "blue"; const DEFAULT_THEME = "blue";
export function useTheme(): [Theme, (t: string) => void] { export function useTheme(): [Theme, (t: string) => void] {
const [theme, setTheme] = useState<Theme | null>(null); const [theme, setTheme] = useState<Theme>(DEFAULT_THEME);
const cookie = getCookie("theme");
useEffect(() => {
const _theme = THEMES.find((theme: Theme) => cookie === theme);
setTheme(_theme || DEFAULT_THEME);
}, []);
function _setTheme(newTheme: string) { function _setTheme(newTheme: string) {
const matchedTheme = THEMES.find((theme: Theme) => newTheme === theme); const matchedTheme = THEMES.find((theme: Theme) => newTheme === theme);
@ -22,5 +16,11 @@ export function useTheme(): [Theme, (t: string) => void] {
} }
} }
useEffect(() => {
const cookie = getCookie("theme");
const _theme = THEMES.find((theme: Theme) => cookie === theme);
_setTheme(_theme || DEFAULT_THEME);
}, []);
return [theme, _setTheme]; return [theme, _setTheme];
} }

View file

@ -4,7 +4,6 @@ import classNames from "classnames";
import * as Quran from "lib/Quran"; import * as Quran from "lib/Quran";
import { useTheme } from "hooks/useTheme"; import { useTheme } from "hooks/useTheme";
import { SelectOption } from "components/Select";
import { ThemeSelect } from "components/ThemeSelect"; import { ThemeSelect } from "components/ThemeSelect";
import { LanguageSelect } from "components/LanguageSelect"; import { LanguageSelect } from "components/LanguageSelect";
import { i18n, formatNumber, TFunction } from "lib/i18n"; import { i18n, formatNumber, TFunction } from "lib/i18n";
@ -17,9 +16,6 @@ interface Props {
function SurahIndex({ locale, surahs, t }: Props) { function SurahIndex({ locale, surahs, t }: Props) {
const [theme, setTheme] = useTheme(); const [theme, setTheme] = useTheme();
const onLanguageChange = (o: SelectOption) => {
document.location.replace(`/${o.value}/`);
};
return ( return (
<div className={classNames("content", "theme", theme, locale)}> <div className={classNames("content", "theme", theme, locale)}>
@ -28,7 +24,7 @@ function SurahIndex({ locale, surahs, t }: Props) {
</a> </a>
<div className="row dropdown-row"> <div className="row dropdown-row">
<ThemeSelect theme={theme} setTheme={setTheme} /> <ThemeSelect theme={theme} setTheme={setTheme} />
<LanguageSelect locale={locale} onChange={onLanguageChange} /> <LanguageSelect locale={locale} />
</div> </div>
<ul className="body index scroll-y"> <ul className="body index scroll-y">
{surahs.map((surah, key) => ( {surahs.map((surah, key) => (

View file

@ -5,7 +5,6 @@ import classNames from "classnames";
import { useTheme } from "hooks/useTheme"; import { useTheme } from "hooks/useTheme";
import { Timer } from "components/Timer"; import { Timer } from "components/Timer";
import { Stream } from "components/Stream"; import { Stream } from "components/Stream";
import { SelectOption } from "components/Select";
import { ThemeSelect } from "components/ThemeSelect"; import { ThemeSelect } from "components/ThemeSelect";
import { LanguageSelect } from "components/LanguageSelect"; import { LanguageSelect } from "components/LanguageSelect";
import { AudioControl } from "components/AudioControl"; import { AudioControl } from "components/AudioControl";
@ -41,15 +40,6 @@ function SurahStream({ node, recitations, locale, paused, t }: Props) {
const readyToRender = stream.length > 0; const readyToRender = stream.length > 0;
const ayah = stream[stream.length - 1]; const ayah = stream[stream.length - 1];
const hasCompactLayout = ["ar"].includes(locale); const hasCompactLayout = ["ar"].includes(locale);
const onLanguageChange = (o: SelectOption) => {
const locale = o.value;
const params = [["paused", isPaused ? "t" : null]];
const query = params
.filter(([, v]) => v)
.flatMap(([k, v]) => `${k}=${v}`)
.join("&");
location.replace(`/${locale}/${surah.slug}/?${query}`);
};
useEffect(() => { useEffect(() => {
setEndOfStream(false); setEndOfStream(false);
@ -68,7 +58,7 @@ function SurahStream({ node, recitations, locale, paused, t }: Props) {
{hasCompactLayout && ( {hasCompactLayout && (
<span className="surah-name">{surah.localizedName}</span> <span className="surah-name">{surah.localizedName}</span>
)} )}
<LanguageSelect locale={locale} onChange={onLanguageChange} /> <LanguageSelect locale={locale} path={surah.slug} />
</div> </div>
</> </>
)} )}