[front]: gitignore fix
This commit is contained in:
parent
52eef8cfbf
commit
17ba979e41
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,7 +17,6 @@ eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
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