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:
parent
47847b2545
commit
9e393fcdf7
22 changed files with 88 additions and 109 deletions
|
@ -100,5 +100,5 @@ body .root .content.theme.ar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@import "themes/moon";
|
@import "themes/blue";
|
||||||
@import "themes/leaf";
|
@import "themes/green";
|
||||||
|
|
|
@ -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
7
src/css/themes/blue.scss
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
@import "blue/layout";
|
||||||
|
@import "blue/pages/SurahIndex";
|
||||||
|
@import "blue/pages/SurahStream";
|
||||||
|
|
||||||
|
.root .content.theme.blue.ar {
|
||||||
|
direction: rtl;
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
.root .content.theme.moon {
|
.root .content.theme.blue {
|
||||||
@import "colors";
|
@import "colors";
|
||||||
|
|
||||||
.row.title {
|
.row.title {
|
|
@ -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;
|
|
@ -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;
|
7
src/css/themes/green.scss
Normal file
7
src/css/themes/green.scss
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
@import "green/layout";
|
||||||
|
@import "green/pages/SurahIndex";
|
||||||
|
@import "green/pages/SurahStream";
|
||||||
|
|
||||||
|
.root .content.theme.green.ar {
|
||||||
|
direction: rtl;
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -1,7 +0,0 @@
|
||||||
@import "leaf/layout";
|
|
||||||
@import "leaf/pages/SurahIndex";
|
|
||||||
@import "leaf/pages/SurahStream";
|
|
||||||
|
|
||||||
.root .content.theme.leaf.ar {
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
@import "moon/layout";
|
|
||||||
@import "moon/pages/SurahIndex";
|
|
||||||
@import "moon/pages/SurahStream";
|
|
||||||
|
|
||||||
.root .content.theme.moon.ar {
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) => (
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
Loading…
Reference in a new issue