Add Select component

The Select component renders a list, and provides more flexibility
in how the element is styled (compared to a regular "<select>").
This commit is contained in:
0x1eef 2022-12-27 03:20:37 -03:00 committed by Robert
parent 7a27036425
commit 431bde851c
9 changed files with 129 additions and 35 deletions

5
Rules
View file

@ -55,7 +55,10 @@ end
##
# /js/pages/surah.js
compile "/js/pages/TheSurahPage.tsx" do
filter :webpack, depend_on: "/js/{lib,components,hooks}/[!WebPackage]**/*.{ts,tsx,js}"
filter :webpack, depend_on: [
"/js/components/*.{ts,tsx}",
"/js/{lib,components,hooks}/[!WebPackage]**/*.{ts,tsx,js}"
]
write "/js/pages/surah.js"
filter :gzip_text
write "/js/pages/surah.js.gz"

View file

@ -0,0 +1,28 @@
.react-select {
height: 20px;
z-index: 0;
}
.react-select span.active-option {
cursor: pointer;
}
.react-select ul {
position: relative;
margin: 0;
padding: 0;
background: #FFF;
list-style-type: none;
border: 1px solid #CCCCCC;
border-radius: 7px;
}
.react-select ul li {
display: block;
width: 100px;
padding: 2px;
&:hover {
cursor: pointer;
background-color: lighten(#CCCCCC, 15%);
}
}

View file

@ -1,4 +1,5 @@
@import "fonts";
@import "components/Select";
$black: #454545;
@ -31,25 +32,15 @@ body {
justify-content: space-between;
direction: ltr;
select {
padding: 3px 0 3px 0;
outline: none;
direction: ltr;
appearance: none;
width: 69px;
.react-select.language {
text-align: right;
option:hover, option:focus-within, option:focus {
background: none;
li:first-child {
font-family: "Vazirmatn Regular";
}
li:last-child {
font-family: "Kanit Regular";
}
}
select:first-child {
text-align: left;
}
select:last-child {
text-align: right;
}
}

View file

@ -9,10 +9,9 @@
}
}
.flex-row select {
.flex-row .react-select {
background-color: transparent;
color: $green;
border: none;
}
.about-surah {

View file

@ -9,10 +9,8 @@
}
}
.flex-row select {
background-color: transparent;
.flex-row .react-select {
color: $gold;
border: none;
}
.about-surah {

View file

@ -0,0 +1,75 @@
import React, { useState, useEffect } from "react";
import classnames from "classnames";
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
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
};
};
export function Select(props: Props) {
const { children, className, value, onChange } = props;
const [open, setOpen] = useState<boolean>(false);
const [activeOption, setActiveOption] = useState<string | null>(findOption(value, children));
const openSelect = (e: React.MouseEvent<HTMLSpanElement>) => {
e.stopPropagation();
setOpen(true);
};
const selectOption = (e: ChangeEvent) => {
e.stopPropagation();
const target: HTMLLIElement = e.target;
const option = createOption(e, children);
onChange(option);
setActiveOption(target.innerText);
setOpen(false);
};
useEffect(() => {
document.body.addEventListener("click", () => setOpen(false));
}, []);
return (
<div className={classnames("react-select", className)}>
<span className="active-option" onClick={openSelect}>
{activeOption}
</span>
<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>
);
})}
</ul>
</div>
);
}

View file

@ -1,4 +1,5 @@
import React from "react";
import { Select, SelectOption } from "components/Select";
import { Surah } from "lib/Quran";
interface Props {
@ -8,14 +9,15 @@ interface Props {
export function LanguageSelect(props: Props) {
const { locale, surah } = props;
const changeLanguage = (e: React.ChangeEvent<HTMLSelectElement>) => {
location.replace(`/${e.target.value}/${surah.slug}/`);
const changeLanguage = (o: SelectOption) => {
const locale = o.value;
location.replace(`/${locale}/${surah.slug}/`);
};
return (
<select value={locale} onChange={changeLanguage}>
<Select value={locale} className="language" onChange={changeLanguage}>
<option value="ar">عربي</option>
<option value="en">English</option>
</select>
</Select>
);
}

View file

@ -1,4 +1,5 @@
import React from 'react';
import { Select, SelectOption } from "components/Select";
import { set as setCookie } from 'es-cookie';
interface Props {
@ -7,18 +8,15 @@ interface Props {
}
export function ThemeSelect ({ setTheme, theme }: Props) {
const onThemeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setCookie('theme', e.target.value, { domain: location.host, expires: 365 });
setTheme(e.target.value);
const onThemeChange = (o: SelectOption) => {
setCookie('theme', o.value, { domain: location.host, expires: 365 });
setTheme(o.value);
};
return (
<select
name="theme"
value={theme}
onChange={onThemeChange}>
<Select value={theme} onChange={onThemeChange}>
<option value='moon'>🌛</option>
<option value='leaf'>🌿</option>
</select>
</Select>
);
}

View file

@ -39,7 +39,7 @@ function TheSurahPage({ locale, surahId }: PageProps) {
{readyToRender && (
<div className="flex-row">
<ThemeSelect theme={theme} setTheme={setTheme} />
<LanguageSelect locale={locale} surah={surah}/>
<LanguageSelect locale={locale} surah={surah} />
</div>
)}
{readyToRender && <AboutSurah locale={locale} surah={surah}/>}