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/leaf";
@import "themes/blue";
@import "themes/green";

View file

@ -61,6 +61,20 @@
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.ar {
@ -102,5 +116,5 @@
}
}
@import "themes/moon";
@import "themes/leaf";
@import "themes/blue";
@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";
.row.title {

View file

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

View file

@ -1,6 +1,6 @@
.root .content.theme.moon {
@import "themes/moon/colors";
@import "themes/moon/components/Icon";
.root .content.theme.blue {
@import "themes/blue/colors";
@import "themes/blue/components/Icon";
.row.details {
color: $gold-primary;
@ -20,8 +20,8 @@
}
}
.root .content.theme.moon.ar {
@import "themes/moon/colors";
.root .content.theme.blue.ar {
@import "themes/blue/colors";
.row.dropdown-row {
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";
.header .image {
background-image: url("/images/leaf.svg");
background-size: cover;
}
.row.title {
color: $green;
}

View file

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

View file

@ -1,6 +1,6 @@
.root .content.theme.leaf {
@import "themes/leaf/colors";
@import "themes/leaf/components/Icon";
.root .content.theme.green {
@import "themes/green/colors";
@import "themes/green/components/Icon";
.row.details {
color: $green;
@ -33,8 +33,8 @@
}
}
.root .content.theme.leaf.ar {
@import "themes/leaf/colors";
.root .content.theme.green.ar {
@import "themes/green/colors";
.row.dropdown-row {
.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 { Select, SelectOption } from "components/Select";
import { Select } from "components/Select";
interface Props {
locale: string;
onChange: (o: SelectOption) => void;
path?: string;
}
export function LanguageSelect({ locale, onChange }: Props) {
export function LanguageSelect({ locale, path = "" }: Props) {
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="en">English</option>
</Select>

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,6 @@ import classNames from "classnames";
import { useTheme } from "hooks/useTheme";
import { Timer } from "components/Timer";
import { Stream } from "components/Stream";
import { SelectOption } from "components/Select";
import { ThemeSelect } from "components/ThemeSelect";
import { LanguageSelect } from "components/LanguageSelect";
import { AudioControl } from "components/AudioControl";
@ -41,15 +40,6 @@ function SurahStream({ node, recitations, locale, paused, t }: Props) {
const readyToRender = stream.length > 0;
const ayah = stream[stream.length - 1];
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(() => {
setEndOfStream(false);
@ -68,7 +58,7 @@ function SurahStream({ node, recitations, locale, paused, t }: Props) {
{hasCompactLayout && (
<span className="surah-name">{surah.localizedName}</span>
)}
<LanguageSelect locale={locale} onChange={onLanguageChange} />
<LanguageSelect locale={locale} path={surah.slug} />
</div>
</>
)}