@companix/uikit 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +54 -0
- package/declaration.d.ts +4 -0
- package/index.html +12 -0
- package/package.json +66 -0
- package/playground/App.tsx +166 -0
- package/playground/Example.tsx +14 -0
- package/playground/Test.tsx +44 -0
- package/playground/animation-test-1/index.scss +20 -0
- package/playground/animation-test-1/index.tsx +17 -0
- package/playground/animation-test-2/index.scss +62 -0
- package/playground/animation-test-2/index.tsx +32 -0
- package/playground/bootstrap.tsx +19 -0
- package/playground/buttons/index.tsx +132 -0
- package/playground/checkbox/index.tsx +64 -0
- package/playground/date-input/index.tsx +45 -0
- package/playground/date-picker/index.tsx +41 -0
- package/playground/dialog/index.tsx +92 -0
- package/playground/dialog-alert/index.tsx +47 -0
- package/playground/drawer/index.tsx +55 -0
- package/playground/index.css +33 -0
- package/playground/index.scss +270 -0
- package/playground/input/index.tsx +112 -0
- package/playground/number-inputs/index.tsx +50 -0
- package/playground/popovers/index.tsx +70 -0
- package/playground/radio-group/index.tsx +69 -0
- package/playground/select/index.tsx +72 -0
- package/playground/select-tags/index.tsx +36 -0
- package/playground/styles.scss +2 -0
- package/playground/switch/index.tsx +44 -0
- package/playground/tabs/index.tsx +16 -0
- package/playground/test.scss +0 -0
- package/playground/text-area/index.tsx +17 -0
- package/playground/text-input/index.tsx +12 -0
- package/playground/toaster/index.tsx +156 -0
- package/playground/tooltip/index.tsx +26 -0
- package/src/Button/Button.scss +128 -0
- package/src/Button/index.tsx +72 -0
- package/src/ButtonGroup/ButtonGroup.scss +18 -0
- package/src/ButtonGroup/index.tsx +20 -0
- package/src/Checkbox/Checkbox.scss +115 -0
- package/src/Checkbox/index.tsx +46 -0
- package/src/Countdown/index.tsx +54 -0
- package/src/DateInput/DateInput.scss +11 -0
- package/src/DateInput/index.tsx +96 -0
- package/src/DatePicker/Calendar.scss +125 -0
- package/src/DatePicker/Calendar.tsx +157 -0
- package/src/DatePicker/CalendarHeader.tsx +139 -0
- package/src/DatePicker/DatePicker.scss +0 -0
- package/src/DatePicker/index.tsx +177 -0
- package/src/Dialog/Dialog.scss +25 -0
- package/src/Dialog/Popup.scss +55 -0
- package/src/Dialog/index.tsx +31 -0
- package/src/DialogAlert/Alert.scss +52 -0
- package/src/DialogAlert/Alert.tsx +78 -0
- package/src/DialogAlert/Viewport.tsx +52 -0
- package/src/DialogAlert/index.tsx +37 -0
- package/src/Drawer/Drawer.scss +112 -0
- package/src/Drawer/index.tsx +46 -0
- package/src/File/index.tsx +60 -0
- package/src/Form/Form.scss +70 -0
- package/src/Form/Input.scss +24 -0
- package/src/Form/index.tsx +131 -0
- package/src/Icon/icon.scss +18 -0
- package/src/Icon/index.tsx +43 -0
- package/src/LoadButton/index.tsx +17 -0
- package/src/NumberInput/index.tsx +74 -0
- package/src/OptionItem/Option.scss +89 -0
- package/src/OptionItem/OptionItem.tsx +49 -0
- package/src/OptionItem/OptionsList.tsx +26 -0
- package/src/Popover/Popover.scss +80 -0
- package/src/Popover/index.tsx +117 -0
- package/src/Radio/Radio.scss +148 -0
- package/src/Radio/index.tsx +68 -0
- package/src/Scrollable/ImitateScroll.tsx +141 -0
- package/src/Scrollable/Scrollable.scss +50 -0
- package/src/Scrollable/index.tsx +141 -0
- package/src/Select/Select.scss +80 -0
- package/src/Select/SelectInput.tsx +131 -0
- package/src/Select/index.tsx +134 -0
- package/src/SelectTags/SelectTags.scss +66 -0
- package/src/SelectTags/index.tsx +192 -0
- package/src/Spinner/Spinner.scss +14 -0
- package/src/Spinner/index.tsx +19 -0
- package/src/Stepper/StepperInput.scss +35 -0
- package/src/Stepper/index.tsx +76 -0
- package/src/Switch/Switch.scss +102 -0
- package/src/Switch/index.tsx +49 -0
- package/src/Tabs/Tabs.scss +58 -0
- package/src/Tabs/index.tsx +89 -0
- package/src/TextArea/TextArea.scss +34 -0
- package/src/TextArea/index.tsx +51 -0
- package/src/Toaster/RemoveListener.tsx +11 -0
- package/src/Toaster/Toast.tsx +69 -0
- package/src/Toaster/Toaster.scss +151 -0
- package/src/Toaster/Viewport.tsx +117 -0
- package/src/Toaster/index.tsx +52 -0
- package/src/Tooltip/Tooltip.scss +28 -0
- package/src/Tooltip/index.tsx +33 -0
- package/src/__hooks/use-frooze-closing.ts +51 -0
- package/src/__hooks/use-loading.ts +34 -0
- package/src/__hooks/use-local-storage.ts +19 -0
- package/src/__hooks/use-popover-position.ts +24 -0
- package/src/__hooks/use-previos.ts +25 -0
- package/src/__hooks/use-resize.ts +41 -0
- package/src/__hooks/use-scrollbox.ts +45 -0
- package/src/__hooks/use-stepper-input.ts +82 -0
- package/src/__hooks/use-update.ts +19 -0
- package/src/__hooks/useCalendar.ts +104 -0
- package/src/__hooks/useCalendarOptions-copy.ts +87 -0
- package/src/__hooks/useCalendarOptions.ts +68 -0
- package/src/__libs/calendar.ts +175 -0
- package/src/__utils/utils.ts +137 -0
- package/src/css.scss +120 -0
- package/src/index.scss +22 -0
- package/src/index.ts +36 -0
- package/src/mixins.scss +99 -0
- package/src/theme.scss +103 -0
- package/src/types.ts +14 -0
- package/tailwind.config.js +91 -0
- package/themes/classic/animations.scss +179 -0
- package/themes/classic/classic.scss +493 -0
- package/tsconfig.json +27 -0
- package/vite.build.ts +35 -0
- package/vite.config.ts +33 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
@use '../mixins.scss';
|
|
2
|
+
|
|
3
|
+
.calendar {
|
|
4
|
+
@include mixins.use-styles(calendar);
|
|
5
|
+
|
|
6
|
+
&-header {
|
|
7
|
+
position: relative;
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
|
|
12
|
+
@include mixins.use-styles(calendar, header);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
&-navigation {
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
position: absolute;
|
|
20
|
+
outline: none;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
|
|
23
|
+
@include mixins.use-styles(calendar, navigation);
|
|
24
|
+
@include mixins.use-size(calendar, navigation, size);
|
|
25
|
+
|
|
26
|
+
&[data-side='left'] {
|
|
27
|
+
left: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&[data-side='right'] {
|
|
31
|
+
right: 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&:hover {
|
|
35
|
+
@include mixins.use-styles(calendar, navigation, hover);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&:active {
|
|
39
|
+
@include mixins.use-styles(calendar, navigation, active);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
svg {
|
|
43
|
+
@include mixins.use-size(calendar, navigation, icon, size);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&-pickers {
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
gap: 4px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&-names {
|
|
54
|
+
display: flex;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&-name {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
justify-content: center;
|
|
61
|
+
|
|
62
|
+
@include mixins.use-styles(calendar, name);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
&-days {
|
|
66
|
+
display: grid;
|
|
67
|
+
grid-template-columns: repeat(7, 1fr);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&-day {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: center;
|
|
74
|
+
position: relative;
|
|
75
|
+
|
|
76
|
+
@include mixins.use-styles(calendar, day);
|
|
77
|
+
|
|
78
|
+
&:not([data-disabled]) {
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
|
|
81
|
+
&:hover {
|
|
82
|
+
@include mixins.use-styles(calendar, day, hover);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
&[data-active] {
|
|
86
|
+
@include mixins.use-styles(calendar, day, active);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
&[data-selected] {
|
|
90
|
+
@include mixins.use-styles(calendar, day, selected);
|
|
91
|
+
|
|
92
|
+
.calendar-day-number::after {
|
|
93
|
+
background-color: #ffffff;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
&[data-void] {
|
|
98
|
+
background-color: unset;
|
|
99
|
+
cursor: default;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
&[data-disabled] {
|
|
104
|
+
@include mixins.use-styles(calendar, day, disabled);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
&[data-today] {
|
|
108
|
+
.calendar-day-number {
|
|
109
|
+
position: relative;
|
|
110
|
+
|
|
111
|
+
&::after {
|
|
112
|
+
position: absolute;
|
|
113
|
+
height: 2px;
|
|
114
|
+
border-radius: 10px;
|
|
115
|
+
content: '';
|
|
116
|
+
display: block;
|
|
117
|
+
width: 100%;
|
|
118
|
+
bottom: 0px;
|
|
119
|
+
right: 0;
|
|
120
|
+
background-color: #529ef4;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createVoids,
|
|
3
|
+
dateToFormat,
|
|
4
|
+
getDayIndex,
|
|
5
|
+
getFirstDay,
|
|
6
|
+
getMonthMaxDay,
|
|
7
|
+
weeks
|
|
8
|
+
} from '../__utils/utils'
|
|
9
|
+
import { CalendarHeader } from './CalendarHeader'
|
|
10
|
+
import { useCalendar } from '../__hooks/useCalendar'
|
|
11
|
+
import { attr } from '@companix/utils-browser'
|
|
12
|
+
import { isSameDate } from '../__libs/calendar'
|
|
13
|
+
import { useLayoutEffect } from 'react'
|
|
14
|
+
|
|
15
|
+
export interface CalendarProps {
|
|
16
|
+
/**
|
|
17
|
+
* Текущая выбранная дата.
|
|
18
|
+
*/
|
|
19
|
+
value?: Date | null
|
|
20
|
+
/**
|
|
21
|
+
* Запрещает выбор даты в прошлом.
|
|
22
|
+
* Применяется, если не заданы `shouldDisableDate` и `disableFuture`.
|
|
23
|
+
*/
|
|
24
|
+
disablePast?: boolean
|
|
25
|
+
/**
|
|
26
|
+
* Запрещает выбор даты в будущем.
|
|
27
|
+
* Применяется, если не задано `shouldDisableDate`.
|
|
28
|
+
*/
|
|
29
|
+
disableFuture?: boolean
|
|
30
|
+
/**
|
|
31
|
+
* Включает выбор времени.
|
|
32
|
+
*/
|
|
33
|
+
enableTime?: boolean
|
|
34
|
+
/**
|
|
35
|
+
* Отключает селекторы выбора месяца/года.
|
|
36
|
+
*/
|
|
37
|
+
disablePickers?: boolean
|
|
38
|
+
/**
|
|
39
|
+
* Показывать дни соседних месяцев.
|
|
40
|
+
*/
|
|
41
|
+
showNeighboringMonth?: boolean
|
|
42
|
+
/**
|
|
43
|
+
* Обработчик изменения выбранной даты.
|
|
44
|
+
*/
|
|
45
|
+
onChange?: (value: Date) => void
|
|
46
|
+
/**
|
|
47
|
+
* Функция для проверки запрета выбора даты.
|
|
48
|
+
*/
|
|
49
|
+
shouldDisableDate?: (value: Date) => boolean
|
|
50
|
+
/**
|
|
51
|
+
* Дата отображаемого месяца.
|
|
52
|
+
* При использовании обновление даты должно происходить вне компонента.
|
|
53
|
+
*/
|
|
54
|
+
viewDate?: Date
|
|
55
|
+
/**
|
|
56
|
+
* Минимальные дата и время, которые можно выбрать.
|
|
57
|
+
* Применяется, если не заданы `shouldDisableDate` и `disablePast`/`disableFuture`.
|
|
58
|
+
*/
|
|
59
|
+
minDateTime?: Date
|
|
60
|
+
/**
|
|
61
|
+
* Максимальные дата и время, которые можно выбрать.
|
|
62
|
+
* Применяется, если не заданы `shouldDisableDate` и `disablePast`/`disableFuture`.
|
|
63
|
+
*/
|
|
64
|
+
maxDateTime?: Date
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const Calendar = ({ disablePickers, value, onChange, ...props }: CalendarProps) => {
|
|
68
|
+
const {
|
|
69
|
+
viewDate,
|
|
70
|
+
setViewDate,
|
|
71
|
+
setNextMonth,
|
|
72
|
+
setPrevMonth,
|
|
73
|
+
isMonthDisabled,
|
|
74
|
+
isYearDisabled,
|
|
75
|
+
isDayDisabled
|
|
76
|
+
} = useCalendar(props)
|
|
77
|
+
|
|
78
|
+
useLayoutEffect(() => {
|
|
79
|
+
if (value) {
|
|
80
|
+
setViewDate(value)
|
|
81
|
+
}
|
|
82
|
+
}, [value])
|
|
83
|
+
|
|
84
|
+
const date = dateToFormat(viewDate)
|
|
85
|
+
|
|
86
|
+
const monthIndex = viewDate.getMonth()
|
|
87
|
+
const year = viewDate.getFullYear()
|
|
88
|
+
const now = new Date()
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="calendar">
|
|
92
|
+
<CalendarHeader
|
|
93
|
+
viewDate={viewDate}
|
|
94
|
+
onChange={setViewDate}
|
|
95
|
+
onNextMonth={setNextMonth}
|
|
96
|
+
onPrevMonth={setPrevMonth}
|
|
97
|
+
disablePickers={disablePickers}
|
|
98
|
+
isMonthDisabled={isMonthDisabled}
|
|
99
|
+
isYearDisabled={isYearDisabled}
|
|
100
|
+
/>
|
|
101
|
+
<div className="calendar-names">
|
|
102
|
+
{weeks.map((name, i) => (
|
|
103
|
+
<div className="calendar-name" key={`week-name-${i}`}>
|
|
104
|
+
{name}
|
|
105
|
+
</div>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
<div className="calendar-days">
|
|
109
|
+
{createVoids(getDayIndex(getFirstDay(date.month, date.year))).map((n, i) => (
|
|
110
|
+
<div className="calendar-day" data-void key={`void-${n}-${i}`} />
|
|
111
|
+
))}
|
|
112
|
+
{createVoids(getMonthMaxDay(date.month, date.year)).map((n, i) => {
|
|
113
|
+
const date = new Date(year, monthIndex, i + 1)
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<CalendarDay
|
|
117
|
+
day={i + 1}
|
|
118
|
+
key={`date-${n}-${year}-${monthIndex}-${i}`}
|
|
119
|
+
disabled={isDayDisabled(date)}
|
|
120
|
+
selected={Boolean(value && isSameDate(value, date))}
|
|
121
|
+
today={isSameDate(date, now)}
|
|
122
|
+
onSelect={() => onChange?.(date)}
|
|
123
|
+
/>
|
|
124
|
+
)
|
|
125
|
+
})}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface CalendarDayProps {
|
|
132
|
+
day: number
|
|
133
|
+
disabled?: boolean
|
|
134
|
+
selected?: boolean
|
|
135
|
+
today?: boolean
|
|
136
|
+
onSelect?: () => void
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const CalendarDay = ({ day, disabled, selected, today, onSelect }: CalendarDayProps) => {
|
|
140
|
+
const handleClick = () => {
|
|
141
|
+
if (!disabled) {
|
|
142
|
+
onSelect?.()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<div
|
|
148
|
+
className="calendar-day"
|
|
149
|
+
data-disabled={attr(disabled)}
|
|
150
|
+
data-selected={attr(selected)}
|
|
151
|
+
data-today={attr(today)}
|
|
152
|
+
onClick={handleClick}
|
|
153
|
+
>
|
|
154
|
+
<span className="calendar-day-number">{day}</span>
|
|
155
|
+
</div>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react'
|
|
2
|
+
import { Option, Select } from '..'
|
|
3
|
+
import { DEFAULT_MAX_YEAR, DEFAULT_MIN_YEAR, getMonths, getYears } from '../__utils/utils'
|
|
4
|
+
import { Icon } from '../Icon'
|
|
5
|
+
import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons'
|
|
6
|
+
import { setMonth, setYear } from '../__libs/calendar'
|
|
7
|
+
|
|
8
|
+
export interface CalendarHeaderProps {
|
|
9
|
+
/**
|
|
10
|
+
* Отображаемая дата.
|
|
11
|
+
*/
|
|
12
|
+
viewDate: Date
|
|
13
|
+
/**
|
|
14
|
+
* Отключает селекторы выбора месяца/года.
|
|
15
|
+
*/
|
|
16
|
+
disablePickers?: boolean
|
|
17
|
+
/**
|
|
18
|
+
* Функция для проверки блокировки месяца.
|
|
19
|
+
*/
|
|
20
|
+
isMonthDisabled?: (monthNumber: number, year?: number) => boolean
|
|
21
|
+
/**
|
|
22
|
+
* Функция для проверки блокировки года.
|
|
23
|
+
*/
|
|
24
|
+
isYearDisabled?: (yearNumber: number) => boolean
|
|
25
|
+
/**
|
|
26
|
+
* Обработчик изменения отображаемой даты.
|
|
27
|
+
*/
|
|
28
|
+
onChange: (viewDate: Date) => void
|
|
29
|
+
/**
|
|
30
|
+
* Нажатие на кнопку переключения на следующий месяц.
|
|
31
|
+
*/
|
|
32
|
+
onNextMonth?: () => void
|
|
33
|
+
/**
|
|
34
|
+
* Нажатие на кнопку переключения на предыдущий месяц.
|
|
35
|
+
*/
|
|
36
|
+
onPrevMonth?: () => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const CalendarHeader = ({
|
|
40
|
+
viewDate,
|
|
41
|
+
onChange,
|
|
42
|
+
isMonthDisabled,
|
|
43
|
+
isYearDisabled,
|
|
44
|
+
onNextMonth,
|
|
45
|
+
onPrevMonth
|
|
46
|
+
}: CalendarHeaderProps) => {
|
|
47
|
+
const currentYear = viewDate.getFullYear()
|
|
48
|
+
const currentMonth = viewDate.getMonth()
|
|
49
|
+
const locale = 'ru'
|
|
50
|
+
|
|
51
|
+
// handlers
|
|
52
|
+
|
|
53
|
+
const onMonthsChange = useCallback(
|
|
54
|
+
(newValue: number) => {
|
|
55
|
+
onChange(setMonth(viewDate, newValue))
|
|
56
|
+
},
|
|
57
|
+
[onChange, viewDate]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const onYearChange = useCallback(
|
|
61
|
+
(newValue: number) => {
|
|
62
|
+
onChange(setYear(viewDate, newValue))
|
|
63
|
+
},
|
|
64
|
+
[onChange, viewDate]
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
// options
|
|
68
|
+
|
|
69
|
+
const months = useMemo((): Option<number>[] => {
|
|
70
|
+
return getMonths(locale).map((option) => ({
|
|
71
|
+
...option,
|
|
72
|
+
className: 'capitalize',
|
|
73
|
+
disabled: isMonthDisabled && isMonthDisabled(option.value)
|
|
74
|
+
}))
|
|
75
|
+
}, [locale, isMonthDisabled])
|
|
76
|
+
|
|
77
|
+
const years = useMemo((): Option<number>[] => {
|
|
78
|
+
return getYears(currentYear, 100).map((option) => ({
|
|
79
|
+
...option,
|
|
80
|
+
disabled: isYearDisabled && isYearDisabled(option.value)
|
|
81
|
+
}))
|
|
82
|
+
}, [currentYear, isYearDisabled])
|
|
83
|
+
|
|
84
|
+
// disable
|
|
85
|
+
|
|
86
|
+
let nextMonthHidden = currentMonth === 11 && currentYear === DEFAULT_MAX_YEAR
|
|
87
|
+
|
|
88
|
+
if (isMonthDisabled && !nextMonthHidden) {
|
|
89
|
+
nextMonthHidden = isMonthDisabled(
|
|
90
|
+
currentMonth === 11 ? 0 : currentMonth + 1,
|
|
91
|
+
currentMonth === 11 ? Math.min(currentYear + 1, DEFAULT_MAX_YEAR) : currentYear
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let prevMonthHidden = currentMonth === 0 && currentYear === DEFAULT_MIN_YEAR
|
|
96
|
+
|
|
97
|
+
if (isMonthDisabled && !prevMonthHidden) {
|
|
98
|
+
prevMonthHidden = isMonthDisabled(
|
|
99
|
+
currentMonth === 0 ? 11 : currentMonth - 1,
|
|
100
|
+
currentMonth === 0 ? Math.max(currentYear - 1, DEFAULT_MIN_YEAR) : currentYear
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div className="calendar-header">
|
|
106
|
+
{!prevMonthHidden && (
|
|
107
|
+
<button className="calendar-navigation" data-side="left" onClick={onPrevMonth}>
|
|
108
|
+
<Icon icon={faChevronLeft} />
|
|
109
|
+
</button>
|
|
110
|
+
)}
|
|
111
|
+
<div className="calendar-pickers">
|
|
112
|
+
<Select
|
|
113
|
+
fill
|
|
114
|
+
options={years}
|
|
115
|
+
size="sm"
|
|
116
|
+
value={currentYear}
|
|
117
|
+
minimalOptions
|
|
118
|
+
matchTarget="min-width"
|
|
119
|
+
onChange={(value) => onYearChange(value || 0)}
|
|
120
|
+
/>
|
|
121
|
+
<Select
|
|
122
|
+
fill
|
|
123
|
+
options={months}
|
|
124
|
+
size="sm"
|
|
125
|
+
className="capitalize"
|
|
126
|
+
value={currentMonth}
|
|
127
|
+
minimalOptions
|
|
128
|
+
matchTarget="min-width"
|
|
129
|
+
onChange={(value) => onMonthsChange(value || 0)}
|
|
130
|
+
/>
|
|
131
|
+
</div>
|
|
132
|
+
{!nextMonthHidden && (
|
|
133
|
+
<button className="calendar-navigation" data-side="right" onClick={onNextMonth}>
|
|
134
|
+
<Icon icon={faChevronRight} />
|
|
135
|
+
</button>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Popover } from '..'
|
|
2
|
+
import { Form, FormProps } from '../Form'
|
|
3
|
+
import { faChevronDown, faClose } from '@fortawesome/free-solid-svg-icons'
|
|
4
|
+
import { Icon } from '../Icon'
|
|
5
|
+
import { useRef, useState } from 'react'
|
|
6
|
+
import { Calendar, CalendarProps } from './Calendar'
|
|
7
|
+
import { useDayDisableCheker } from '../__libs/calendar'
|
|
8
|
+
import { formatTime, getNum } from '@companix/utils-js'
|
|
9
|
+
|
|
10
|
+
interface DatePickerProps
|
|
11
|
+
extends Omit<CalendarProps, 'onChange'>,
|
|
12
|
+
Omit<FormProps, 'value' | 'onChange' | 'rightElement'> {
|
|
13
|
+
onChange?: (value: Date | null) => void
|
|
14
|
+
placeholder?: string
|
|
15
|
+
clearButton?: boolean
|
|
16
|
+
clearButtonIcon?: boolean
|
|
17
|
+
children?: React.ReactNode
|
|
18
|
+
minimalOptions?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const createInputValue = (value: Date | null, char: string = '-'): string => {
|
|
22
|
+
if (value) {
|
|
23
|
+
const day = formatTime(value.getDate())
|
|
24
|
+
const month = formatTime(value.getMonth() + 1)
|
|
25
|
+
const year = value.getFullYear()
|
|
26
|
+
|
|
27
|
+
return [day, month, year].join(char)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return ''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const inputToDate = (input: string, char: string = '-'): Date | null => {
|
|
34
|
+
const values = input.split(char)
|
|
35
|
+
|
|
36
|
+
if (values.length === 3) {
|
|
37
|
+
const [day, month, year] = [getNum(values[0]), getNum(values[1]), getNum(values[2])]
|
|
38
|
+
|
|
39
|
+
if (day && month && year) {
|
|
40
|
+
const date = new Date(year, month - 1, day)
|
|
41
|
+
|
|
42
|
+
if (date.getFullYear() === year && date.getDate() === day && date.getMonth() === month - 1) {
|
|
43
|
+
return date
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const DatePicker = (props: DatePickerProps) => {
|
|
52
|
+
const {
|
|
53
|
+
minimalOptions,
|
|
54
|
+
clearButton,
|
|
55
|
+
clearButtonIcon,
|
|
56
|
+
children,
|
|
57
|
+
disabled,
|
|
58
|
+
// calendar props
|
|
59
|
+
value,
|
|
60
|
+
|
|
61
|
+
enableTime,
|
|
62
|
+
disablePickers,
|
|
63
|
+
showNeighboringMonth,
|
|
64
|
+
onChange,
|
|
65
|
+
shouldDisableDate,
|
|
66
|
+
viewDate,
|
|
67
|
+
disablePast,
|
|
68
|
+
disableFuture,
|
|
69
|
+
minDateTime,
|
|
70
|
+
maxDateTime,
|
|
71
|
+
// input props
|
|
72
|
+
...inputProps
|
|
73
|
+
} = props
|
|
74
|
+
|
|
75
|
+
const [inputValue, setInputValue] = useState(() => {
|
|
76
|
+
return createInputValue(value ?? null)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const popoverRef = useRef<HTMLDivElement>(null)
|
|
80
|
+
|
|
81
|
+
const isDayDisabled = useDayDisableCheker({
|
|
82
|
+
disableFuture,
|
|
83
|
+
disablePast,
|
|
84
|
+
shouldDisableDate,
|
|
85
|
+
minDateTime,
|
|
86
|
+
maxDateTime
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const handleRootClick = (event: React.MouseEvent) => {
|
|
90
|
+
if (disabled) return
|
|
91
|
+
|
|
92
|
+
// Предотвращаем закрытие Popover при клике на форму
|
|
93
|
+
if (popoverRef.current && popoverRef.current.getAttribute('data-state') === 'open') {
|
|
94
|
+
event.preventDefault()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const handleClear = (e: React.MouseEvent) => {
|
|
99
|
+
e.stopPropagation()
|
|
100
|
+
|
|
101
|
+
setInputValue('')
|
|
102
|
+
onChange?.(null)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const handleInputChange = (value: string) => {
|
|
106
|
+
setInputValue(value)
|
|
107
|
+
|
|
108
|
+
const date = inputToDate(value)
|
|
109
|
+
|
|
110
|
+
if (date && !isDayDisabled(date)) {
|
|
111
|
+
onChange?.(date)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const handleInputBlur = () => {
|
|
116
|
+
const date = inputToDate(inputValue)
|
|
117
|
+
|
|
118
|
+
if (!date || isDayDisabled(date)) {
|
|
119
|
+
setInputValue(createInputValue(value ?? null))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const handleCalendarChange = (value: Date) => {
|
|
124
|
+
onChange?.(value)
|
|
125
|
+
setInputValue(createInputValue(value))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<Popover
|
|
130
|
+
minimal
|
|
131
|
+
ref={popoverRef}
|
|
132
|
+
sideOffset={0}
|
|
133
|
+
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
134
|
+
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
135
|
+
disabled={disabled}
|
|
136
|
+
content={() => (
|
|
137
|
+
<Calendar
|
|
138
|
+
value={value}
|
|
139
|
+
disablePast={disablePast}
|
|
140
|
+
disableFuture={disableFuture}
|
|
141
|
+
enableTime={enableTime}
|
|
142
|
+
disablePickers={disablePickers}
|
|
143
|
+
showNeighboringMonth={showNeighboringMonth}
|
|
144
|
+
onChange={handleCalendarChange}
|
|
145
|
+
shouldDisableDate={shouldDisableDate}
|
|
146
|
+
viewDate={viewDate}
|
|
147
|
+
minDateTime={minDateTime}
|
|
148
|
+
maxDateTime={maxDateTime}
|
|
149
|
+
/>
|
|
150
|
+
)}
|
|
151
|
+
>
|
|
152
|
+
<Form
|
|
153
|
+
{...inputProps}
|
|
154
|
+
value={inputValue}
|
|
155
|
+
disabled={disabled}
|
|
156
|
+
onClick={handleRootClick}
|
|
157
|
+
onValueChange={handleInputChange}
|
|
158
|
+
onBlur={handleInputBlur}
|
|
159
|
+
mask={'99-99-9999'}
|
|
160
|
+
rightElement={
|
|
161
|
+
<>
|
|
162
|
+
{clearButton && isInputDefined(inputValue) && (
|
|
163
|
+
<button className="select-close-button" onClick={handleClear}>
|
|
164
|
+
{clearButtonIcon ?? <Icon className="select-close-icon" icon={faClose} size="xxxs" />}
|
|
165
|
+
</button>
|
|
166
|
+
)}
|
|
167
|
+
<Icon className="expand-icon select-expand" icon={faChevronDown} size="xxxs" />
|
|
168
|
+
</>
|
|
169
|
+
}
|
|
170
|
+
/>
|
|
171
|
+
</Popover>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const isInputDefined = (value: string) => {
|
|
176
|
+
return value.trim().replaceAll('-', '').replaceAll('_', '')
|
|
177
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
@use '../mixins.scss';
|
|
2
|
+
|
|
3
|
+
.dialog {
|
|
4
|
+
&-container {
|
|
5
|
+
@include mixins.use-styles(dialog, container);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
&-overlay {
|
|
9
|
+
@include mixins.use-styles(dialog, overlay);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@include mixins.use-styles(dialog);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.dialog-container {
|
|
16
|
+
@each $size, $v in (xxs, xs, s, m, l, xl, xxl, full) {
|
|
17
|
+
&[data-size='#{$size}'] {
|
|
18
|
+
@include mixins.use-styles(dialog, size, $size, container);
|
|
19
|
+
|
|
20
|
+
.dialog {
|
|
21
|
+
@include mixins.use-styles(dialog, size, $size);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
@use '../mixins.scss';
|
|
2
|
+
|
|
3
|
+
.popup {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
max-height: 100%;
|
|
7
|
+
z-index: 1000;
|
|
8
|
+
outline: none;
|
|
9
|
+
pointer-events: auto;
|
|
10
|
+
position: relative;
|
|
11
|
+
width: 100%;
|
|
12
|
+
|
|
13
|
+
@include mixins.use-styles(popup);
|
|
14
|
+
|
|
15
|
+
&-overlay {
|
|
16
|
+
inset: 0;
|
|
17
|
+
overflow: auto;
|
|
18
|
+
position: fixed;
|
|
19
|
+
-webkit-user-select: none;
|
|
20
|
+
-moz-user-select: none;
|
|
21
|
+
-ms-user-select: none;
|
|
22
|
+
user-select: none;
|
|
23
|
+
outline: none;
|
|
24
|
+
z-index: 9999;
|
|
25
|
+
|
|
26
|
+
@include mixins.use-styles(popup, overlay);
|
|
27
|
+
|
|
28
|
+
&[data-state='open'] {
|
|
29
|
+
@include mixins.use-styles(popup, overlay, in);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&[data-state='closed'] {
|
|
33
|
+
@include mixins.use-styles(popup, overlay, out);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&-container {
|
|
38
|
+
position: fixed;
|
|
39
|
+
inset: 0;
|
|
40
|
+
z-index: 9999;
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
pointer-events: none !important;
|
|
45
|
+
outline: none;
|
|
46
|
+
|
|
47
|
+
&[data-state='open'] {
|
|
48
|
+
@include mixins.use-styles(popup, in);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&[data-state='closed'] {
|
|
52
|
+
@include mixins.use-styles(popup, out);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import cn from 'classnames'
|
|
2
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
3
|
+
import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
|
|
4
|
+
|
|
5
|
+
export interface DialogProps {
|
|
6
|
+
open: boolean
|
|
7
|
+
onOpenChange: (value: boolean) => void
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
size?: 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl' | 'full'
|
|
10
|
+
className?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const Dialog = ({ size = 's', open, onOpenChange, children, className }: DialogProps) => {
|
|
14
|
+
return (
|
|
15
|
+
<DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>
|
|
16
|
+
<DialogPrimitive.Portal>
|
|
17
|
+
<DialogPrimitive.Overlay className="popup-overlay dialog-overlay" />
|
|
18
|
+
<DialogPrimitive.Content className="popup-container dialog-container" data-size={size}>
|
|
19
|
+
<div className={cn('popup dialog', className)}>
|
|
20
|
+
<VisuallyHidden>
|
|
21
|
+
<DialogPrimitive.Title />
|
|
22
|
+
</VisuallyHidden>
|
|
23
|
+
{children}
|
|
24
|
+
</div>
|
|
25
|
+
</DialogPrimitive.Content>
|
|
26
|
+
</DialogPrimitive.Portal>
|
|
27
|
+
</DialogPrimitive.Root>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Dialog.Close = DialogPrimitive.Close
|