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:
parent
7a27036425
commit
431bde851c
9 changed files with 129 additions and 35 deletions
5
Rules
5
Rules
|
@ -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"
|
||||
|
|
28
src/css/components/Select.scss
Normal file
28
src/css/components/Select.scss
Normal 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%);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,10 +9,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
.flex-row select {
|
||||
.flex-row .react-select {
|
||||
background-color: transparent;
|
||||
color: $green;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.about-surah {
|
||||
|
|
|
@ -9,10 +9,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.flex-row select {
|
||||
background-color: transparent;
|
||||
.flex-row .react-select {
|
||||
color: $gold;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.about-surah {
|
||||
|
|
75
src/js/components/Select.tsx
Normal file
75
src/js/components/Select.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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}/>}
|
||||
|
|
Loading…
Reference in a new issue