[front]: gitignore fix
This commit is contained in:
parent
52eef8cfbf
commit
17ba979e41
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,7 +17,6 @@ eggs/
|
|||||||
.eggs/
|
.eggs/
|
||||||
lib/
|
lib/
|
||||||
lib64/
|
lib64/
|
||||||
parts/
|
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
wheels/
|
wheels/
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import styles from './style.module.scss';
|
||||||
|
import { RippleWaveProps } from './types';
|
||||||
|
|
||||||
|
export function RippleWave({ style, onDone }: RippleWaveProps) {
|
||||||
|
const [isMouseUp, setIsMouseUp] = useState(false);
|
||||||
|
const [isAnimationEnd, setIsAnimationEnd] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mouseUpListener = () => setIsMouseUp(true);
|
||||||
|
document.addEventListener('mouseup', mouseUpListener, { once: true });
|
||||||
|
document.addEventListener('touchend', mouseUpListener, { once: true });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const visible = !isMouseUp || !isAnimationEnd;
|
||||||
|
|
||||||
|
const className = clsx(
|
||||||
|
styles.wave,
|
||||||
|
visible ? styles.visible : styles.invisible,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAnimationEnd = (event: React.AnimationEvent) => {
|
||||||
|
if (event.animationName === styles.fadein) {
|
||||||
|
setIsAnimationEnd(true);
|
||||||
|
} else {
|
||||||
|
onDone();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={className}
|
||||||
|
onAnimationEnd={handleAnimationEnd}
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { RippleWave } from './component';
|
@ -0,0 +1,33 @@
|
|||||||
|
.wave {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: var(--clr-ripple);
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible {
|
||||||
|
animation: fadein 0.3s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invisible {
|
||||||
|
animation: fadeout 0.3s linear forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadein {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
scale: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
scale: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeout {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import { CSSProperties } from 'react';
|
||||||
|
|
||||||
|
export type RippleWaveProps = {
|
||||||
|
style: CSSProperties;
|
||||||
|
onDone: () => void;
|
||||||
|
};
|
@ -0,0 +1,81 @@
|
|||||||
|
import { IconButton } from '@components/ui/icon-button';
|
||||||
|
import { RawButton } from '@components/ui/raw';
|
||||||
|
import { Span } from '@components/ui/span';
|
||||||
|
import ArrowDownIcon from '@public/images/svg/arrow-down.svg';
|
||||||
|
import ArrowUpIcon from '@public/images/svg/arrow-up.svg';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { DAYS_OF_THE_WEEK, MONTHS } from './constants';
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
import { CalendarDaysProps } from './types';
|
||||||
|
import { getCalendarDays } from './utils';
|
||||||
|
|
||||||
|
export function CalendarDays({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
date,
|
||||||
|
onMonthChange,
|
||||||
|
}: CalendarDaysProps) {
|
||||||
|
const today = useMemo(() => new Date(), []);
|
||||||
|
|
||||||
|
const days = useMemo(() => {
|
||||||
|
return getCalendarDays({
|
||||||
|
year: date.getFullYear(),
|
||||||
|
monthIndex: date.getMonth(),
|
||||||
|
today,
|
||||||
|
selectedDateStr: value,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
});
|
||||||
|
}, [date, min, max]);
|
||||||
|
|
||||||
|
const handleChange = (newValue: string) => {
|
||||||
|
onChange?.(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<Span color="t300" className={styles.title}>
|
||||||
|
{MONTHS[date.getMonth()]} {date.getFullYear()}
|
||||||
|
</Span>
|
||||||
|
<IconButton
|
||||||
|
className={styles.turnButton}
|
||||||
|
onClick={() => onMonthChange(-1)}
|
||||||
|
>
|
||||||
|
<ArrowUpIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
className={styles.turnButton}
|
||||||
|
onClick={() => onMonthChange(1)}
|
||||||
|
>
|
||||||
|
<ArrowDownIcon />
|
||||||
|
</IconButton>
|
||||||
|
</header>
|
||||||
|
<div className={styles.daysGrid}>
|
||||||
|
{DAYS_OF_THE_WEEK.map((day) => (
|
||||||
|
<Span className={styles.dayOfTheWeek} scale="none" key={day}>
|
||||||
|
{day}
|
||||||
|
</Span>
|
||||||
|
))}
|
||||||
|
{days.map((day, index) => (
|
||||||
|
<RawButton
|
||||||
|
key={index}
|
||||||
|
disabled={day.isDisabled}
|
||||||
|
className={clsx(styles.day, {
|
||||||
|
[styles.selectedDay]: day.isSelected,
|
||||||
|
[styles.currentMonthDay]: day.isCurrentMonth,
|
||||||
|
})}
|
||||||
|
onClick={() => handleChange(day.string)}
|
||||||
|
>
|
||||||
|
{day.number}
|
||||||
|
{day.isToday && <div className={styles.todayIndicator} />}
|
||||||
|
</RawButton>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
export const DAYS_OF_THE_WEEK = [
|
||||||
|
'Mon',
|
||||||
|
'Tue',
|
||||||
|
'Wed',
|
||||||
|
'Thu',
|
||||||
|
'Fri',
|
||||||
|
'Sat',
|
||||||
|
'Sun',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const MONTHS = [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December',
|
||||||
|
];
|
@ -0,0 +1,2 @@
|
|||||||
|
export { CalendarDays } from './component';
|
||||||
|
export { type CalendarDaysProps } from './types';
|
@ -0,0 +1,85 @@
|
|||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
flex: 1;
|
||||||
|
padding-left: 5px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.turnButton {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daysGrid {
|
||||||
|
display: grid;
|
||||||
|
gap: 5px;
|
||||||
|
grid-template-columns: repeat(7, auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayOfTheWeek {
|
||||||
|
display: flex;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: var(--clr-text-100);
|
||||||
|
transition: all var(--td-100) ease-in-out;
|
||||||
|
|
||||||
|
&:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--clr-layer-300-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: var(--clr-text-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.currentMonthDay {
|
||||||
|
color: var(--clr-text-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedDay {
|
||||||
|
background-color: var(--clr-primary);
|
||||||
|
box-shadow: 0px 2px 2px var(--clr-shadow-200);
|
||||||
|
color: var(--clr-on-primary);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--clr-primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todayIndicator {
|
||||||
|
background-color: var(--clr-on-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.todayIndicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 12%;
|
||||||
|
left: 50%;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--clr-text-300);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
export type CalendarDay = {
|
||||||
|
number: number;
|
||||||
|
isDisabled: boolean;
|
||||||
|
isSelected: boolean;
|
||||||
|
isToday: boolean;
|
||||||
|
string: string;
|
||||||
|
isCurrentMonth: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CalendarDaysProps = {
|
||||||
|
value?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
min: Date | null;
|
||||||
|
max: Date | null;
|
||||||
|
date: Date;
|
||||||
|
onMonthChange: (delta: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetCalendarDaysParams = {
|
||||||
|
year: number;
|
||||||
|
monthIndex: number;
|
||||||
|
today: Date;
|
||||||
|
selectedDateStr?: string;
|
||||||
|
min: Date | null;
|
||||||
|
max: Date | null;
|
||||||
|
};
|
@ -0,0 +1,52 @@
|
|||||||
|
import { dateToInputString } from '@utils/date';
|
||||||
|
|
||||||
|
import { CalendarDay, GetCalendarDaysParams } from './types';
|
||||||
|
|
||||||
|
const addDays = (date: Date, days: number) => {
|
||||||
|
date.setDate(date.getDate() + days);
|
||||||
|
};
|
||||||
|
|
||||||
|
const daysAreEqual = (date1: Date, date2: Date) => {
|
||||||
|
return (
|
||||||
|
date1.getDate() === date2.getDate() &&
|
||||||
|
date1.getMonth() === date2.getMonth() &&
|
||||||
|
date1.getFullYear() === date2.getFullYear()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCalendarDays = ({
|
||||||
|
year,
|
||||||
|
monthIndex,
|
||||||
|
today,
|
||||||
|
selectedDateStr,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
}: GetCalendarDaysParams) => {
|
||||||
|
const selectedDate = new Date(selectedDateStr);
|
||||||
|
|
||||||
|
const firstDayOfMonth = new Date(year, monthIndex, 1);
|
||||||
|
const daysFromPrevMonth = (firstDayOfMonth.getDay() || 7) - 1;
|
||||||
|
|
||||||
|
const date = new Date(year, monthIndex, 1);
|
||||||
|
addDays(date, -daysFromPrevMonth);
|
||||||
|
const days: CalendarDay[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 42; i += 1) {
|
||||||
|
const number = date.getDate();
|
||||||
|
const isDisabled = (min && date < min) || (max && date > max);
|
||||||
|
const isSelected = daysAreEqual(date, selectedDate);
|
||||||
|
const isToday = daysAreEqual(date, today);
|
||||||
|
const string = dateToInputString(date);
|
||||||
|
const isCurrentMonth = date.getMonth() === monthIndex;
|
||||||
|
days.push({
|
||||||
|
number,
|
||||||
|
isDisabled,
|
||||||
|
isSelected,
|
||||||
|
isToday,
|
||||||
|
string,
|
||||||
|
isCurrentMonth,
|
||||||
|
});
|
||||||
|
addDays(date, 1);
|
||||||
|
}
|
||||||
|
return days;
|
||||||
|
};
|
1
front/src/components/ui/calendar/parts/index.ts
Normal file
1
front/src/components/ui/calendar/parts/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './calendar-days';
|
@ -0,0 +1,68 @@
|
|||||||
|
import { Checkbox } from '@components/ui';
|
||||||
|
import { Input } from '@components/ui/input';
|
||||||
|
import { WindmillConfig } from '@components/ux/windmill-form';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import styles from './style.module.scss';
|
||||||
|
import { WindmillTableRowProps } from './types';
|
||||||
|
|
||||||
|
export function WindmillTableRow({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
onSelect,
|
||||||
|
selected,
|
||||||
|
}: WindmillTableRowProps) {
|
||||||
|
const extractNumber = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
if (!value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const regex = /^[-+]?\d*\.?\d*$/;
|
||||||
|
return event.target.value.match(regex)?.[0] ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement>,
|
||||||
|
min: number,
|
||||||
|
max: number,
|
||||||
|
key: keyof WindmillConfig,
|
||||||
|
) => {
|
||||||
|
const num = extractNumber(event);
|
||||||
|
if (num === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const float = parseFloat(num);
|
||||||
|
if (Number.isNaN(float)) {
|
||||||
|
onChange({ ...value, [key]: num });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (float >= min && float <= max) {
|
||||||
|
onChange({ ...value, [key]: num });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.row}>
|
||||||
|
<Checkbox
|
||||||
|
label={{ className: styles.checkboxLabel }}
|
||||||
|
onChange={onSelect}
|
||||||
|
checked={selected}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
wrapper={{ className: styles.textInput }}
|
||||||
|
value={value.x}
|
||||||
|
onChange={(event) => handleChange(event, -90, 90, 'x')}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
wrapper={{ className: styles.textInput }}
|
||||||
|
value={value.y}
|
||||||
|
onChange={(event) => handleChange(event, -180, 180, 'y')}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
wrapper={{ className: styles.textInput }}
|
||||||
|
value={value.angle}
|
||||||
|
onChange={(event) => handleChange(event, 0, 360, 'angle')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { WindmillTableRow } from './component';
|
@ -0,0 +1,16 @@
|
|||||||
|
.row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkboxLabel {
|
||||||
|
width: 46px;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid var(--clr-border-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.textInput {
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: var(--clr-layer-200);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import { WindmillConfig } from '@components/ux/windmill-form';
|
||||||
|
|
||||||
|
export type WindmillTableRowProps = {
|
||||||
|
value: WindmillConfig;
|
||||||
|
onChange: (value: WindmillConfig) => void;
|
||||||
|
onSelect: () => void;
|
||||||
|
selected: boolean;
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user