mirror of
https://github.com/aykhans/slash-e.git
synced 2025-09-09 18:40:44 +00:00
chore: init web project
This commit is contained in:
120
web/src/components/common/DatePicker.tsx
Normal file
120
web/src/components/common/DatePicker.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { DAILY_TIMESTAMP } from "../../helpers/consts";
|
||||
import Icon from "../Icon";
|
||||
import "../../less/common/date-picker.less";
|
||||
|
||||
interface DatePickerProps {
|
||||
className?: string;
|
||||
datestamp: DateStamp;
|
||||
handleDateStampChange: (datastamp: DateStamp) => void;
|
||||
}
|
||||
|
||||
const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) => {
|
||||
const { className, datestamp, handleDateStampChange } = props;
|
||||
const [currentDateStamp, setCurrentDateStamp] = useState<DateStamp>(getMonthFirstDayDateStamp(datestamp));
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentDateStamp(getMonthFirstDayDateStamp(datestamp));
|
||||
}, [datestamp]);
|
||||
|
||||
const firstDate = new Date(currentDateStamp);
|
||||
const firstDateDay = firstDate.getDay() === 0 ? 7 : firstDate.getDay();
|
||||
const dayList = [];
|
||||
for (let i = 1; i < firstDateDay; i++) {
|
||||
dayList.push({
|
||||
date: 0,
|
||||
datestamp: firstDate.getTime() - DAILY_TIMESTAMP * (7 - i),
|
||||
});
|
||||
}
|
||||
const dayAmount = getMonthDayAmount(currentDateStamp);
|
||||
for (let i = 1; i <= dayAmount; i++) {
|
||||
dayList.push({
|
||||
date: i,
|
||||
datestamp: firstDate.getTime() + DAILY_TIMESTAMP * (i - 1),
|
||||
});
|
||||
}
|
||||
|
||||
const handleDateItemClick = (datestamp: DateStamp) => {
|
||||
handleDateStampChange(datestamp);
|
||||
};
|
||||
|
||||
const handleChangeMonthBtnClick = (i: -1 | 1) => {
|
||||
const year = firstDate.getFullYear();
|
||||
const month = firstDate.getMonth() + 1;
|
||||
let nextDateStamp = 0;
|
||||
if (month === 1 && i === -1) {
|
||||
nextDateStamp = new Date(`${year - 1}/12/1`).getTime();
|
||||
} else if (month === 12 && i === 1) {
|
||||
nextDateStamp = new Date(`${year + 1}/1/1`).getTime();
|
||||
} else {
|
||||
nextDateStamp = new Date(`${year}/${month + i}/1`).getTime();
|
||||
}
|
||||
setCurrentDateStamp(getMonthFirstDayDateStamp(nextDateStamp));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`date-picker-wrapper ${className}`}>
|
||||
<div className="date-picker-header">
|
||||
<span className="btn-text" onClick={() => handleChangeMonthBtnClick(-1)}>
|
||||
<Icon.ChevronLeft className="icon-img" />
|
||||
</span>
|
||||
<span className="normal-text">
|
||||
{firstDate.getFullYear()}/{firstDate.getMonth() + 1}
|
||||
</span>
|
||||
<span className="btn-text" onClick={() => handleChangeMonthBtnClick(1)}>
|
||||
<Icon.ChevronRight className="icon-img" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="date-picker-day-container">
|
||||
<div className="date-picker-day-header">
|
||||
<span className="day-item">Mon</span>
|
||||
<span className="day-item">Tue</span>
|
||||
<span className="day-item">Web</span>
|
||||
<span className="day-item">Thu</span>
|
||||
<span className="day-item">Fri</span>
|
||||
<span className="day-item">Sat</span>
|
||||
<span className="day-item">Sun</span>
|
||||
</div>
|
||||
|
||||
{dayList.map((d) => {
|
||||
if (d.date === 0) {
|
||||
return (
|
||||
<span key={d.datestamp} className="day-item null">
|
||||
{""}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span
|
||||
key={d.datestamp}
|
||||
className={`day-item ${d.datestamp === datestamp ? "current" : ""}`}
|
||||
onClick={() => handleDateItemClick(d.datestamp)}
|
||||
>
|
||||
{d.date}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function getMonthDayAmount(datestamp: DateStamp): number {
|
||||
const dateTemp = new Date(datestamp);
|
||||
const currentDate = new Date(`${dateTemp.getFullYear()}/${dateTemp.getMonth() + 1}/1`);
|
||||
const nextMonthDate =
|
||||
currentDate.getMonth() === 11
|
||||
? new Date(`${currentDate.getFullYear() + 1}/1/1`)
|
||||
: new Date(`${currentDate.getFullYear()}/${currentDate.getMonth() + 2}/1`);
|
||||
|
||||
return (nextMonthDate.getTime() - currentDate.getTime()) / DAILY_TIMESTAMP;
|
||||
}
|
||||
|
||||
function getMonthFirstDayDateStamp(timestamp: TimeStamp): DateStamp {
|
||||
const dateTemp = new Date(timestamp);
|
||||
const currentDate = new Date(`${dateTemp.getFullYear()}/${dateTemp.getMonth() + 1}/1`);
|
||||
return currentDate.getTime();
|
||||
}
|
||||
|
||||
export default DatePicker;
|
40
web/src/components/common/Dropdown.tsx
Normal file
40
web/src/components/common/Dropdown.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { ReactNode, useEffect, useRef } from "react";
|
||||
import useToggle from "../../hooks/useToggle";
|
||||
import Icon from "../Icon";
|
||||
import "../../less/common/dropdown.less";
|
||||
|
||||
interface DropdownProps {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Dropdown: React.FC<DropdownProps> = (props: DropdownProps) => {
|
||||
const { children, className } = props;
|
||||
const [dropdownStatus, toggleDropdownStatus] = useToggle(false);
|
||||
const dropdownWrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (dropdownStatus) {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (!dropdownWrapperRef.current?.contains(event.target as Node)) {
|
||||
toggleDropdownStatus(false);
|
||||
}
|
||||
};
|
||||
window.addEventListener("click", handleClickOutside, {
|
||||
capture: true,
|
||||
once: true,
|
||||
});
|
||||
}
|
||||
}, [dropdownStatus]);
|
||||
|
||||
return (
|
||||
<div ref={dropdownWrapperRef} className={`dropdown-wrapper ${className ?? ""}`} onClick={() => toggleDropdownStatus()}>
|
||||
<span className="trigger-button">
|
||||
<Icon.MoreHorizontal className="icon-img" />
|
||||
</span>
|
||||
<div className={`action-buttons-container ${dropdownStatus ? "" : "!hidden"}`}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dropdown;
|
15
web/src/components/common/OnlyWhen.tsx
Normal file
15
web/src/components/common/OnlyWhen.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface OnlyWhenProps {
|
||||
children: ReactNode;
|
||||
when: boolean;
|
||||
}
|
||||
|
||||
const OnlyWhen: React.FC<OnlyWhenProps> = (props: OnlyWhenProps) => {
|
||||
const { children, when } = props;
|
||||
return when ? <>{children}</> : null;
|
||||
};
|
||||
|
||||
const Only = OnlyWhen;
|
||||
|
||||
export default Only;
|
95
web/src/components/common/Selector.tsx
Normal file
95
web/src/components/common/Selector.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { memo, useEffect, useRef } from "react";
|
||||
import useToggle from "../../hooks/useToggle";
|
||||
import Icon from "../Icon";
|
||||
import "../../less/common/selector.less";
|
||||
|
||||
interface SelectorItem {
|
||||
text: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
value: string;
|
||||
dataSource: SelectorItem[];
|
||||
handleValueChanged?: (value: string) => void;
|
||||
}
|
||||
|
||||
const nullItem = {
|
||||
text: "Select",
|
||||
value: "",
|
||||
};
|
||||
|
||||
const Selector: React.FC<Props> = (props: Props) => {
|
||||
const { className, dataSource, handleValueChanged, value } = props;
|
||||
const [showSelector, toggleSelectorStatus] = useToggle(false);
|
||||
|
||||
const seletorElRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
let currentItem = nullItem;
|
||||
for (const d of dataSource) {
|
||||
if (d.value === value) {
|
||||
currentItem = d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (showSelector) {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (!seletorElRef.current?.contains(event.target as Node)) {
|
||||
toggleSelectorStatus(false);
|
||||
}
|
||||
};
|
||||
window.addEventListener("click", handleClickOutside, {
|
||||
capture: true,
|
||||
once: true,
|
||||
});
|
||||
}
|
||||
}, [showSelector]);
|
||||
|
||||
const handleItemClick = (item: SelectorItem) => {
|
||||
if (handleValueChanged) {
|
||||
handleValueChanged(item.value);
|
||||
}
|
||||
toggleSelectorStatus(false);
|
||||
};
|
||||
|
||||
const handleCurrentValueClick = (event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
toggleSelectorStatus();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`selector-wrapper ${className ?? ""}`} ref={seletorElRef}>
|
||||
<div className={`current-value-container ${showSelector ? "active" : ""}`} onClick={handleCurrentValueClick}>
|
||||
<span className="value-text">{currentItem.text}</span>
|
||||
<span className="arrow-text">
|
||||
<Icon.ChevronDown className="icon-img" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={`items-wrapper ${showSelector ? "" : "!hidden"}`}>
|
||||
{dataSource.length > 0 ? (
|
||||
dataSource.map((d) => {
|
||||
return (
|
||||
<div
|
||||
className={`item-container ${d.value === value ? "selected" : ""}`}
|
||||
key={d.value}
|
||||
onClick={() => {
|
||||
handleItemClick(d);
|
||||
}}
|
||||
>
|
||||
{d.text}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<p className="tip-text">Null</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Selector);
|
Reference in New Issue
Block a user