@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,51 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
// При смене выбранного значения в select-компоненте может измениться ширина кнопки.
|
|
4
|
+
// Это приводит к пересчёту позиции popover, привязанного к её размерам и координатам.
|
|
5
|
+
// Во время анимированного закрытия popover это способно вызвать нежелательные визуальные артефакты.
|
|
6
|
+
|
|
7
|
+
// Чтобы запретить данное поведение, разработан хук useFroozeClosing, который фиксирует и применяет значения ширины и позиции открытого popover-окна перед событием закрытия.
|
|
8
|
+
|
|
9
|
+
export const useFroozeClosing = () => {
|
|
10
|
+
const popoverRef = useRef<HTMLDivElement>(null)
|
|
11
|
+
const stateRef = useRef<{ cb: null | (() => void) }>({ cb: null })
|
|
12
|
+
|
|
13
|
+
const froozePopoverPosition = useCallback(() => {
|
|
14
|
+
if (popoverRef.current && popoverRef.current.parentElement) {
|
|
15
|
+
const parent = popoverRef.current.parentElement
|
|
16
|
+
|
|
17
|
+
const width = parent.style.getPropertyValue('--radix-popper-anchor-width')
|
|
18
|
+
const position = parent.style.getPropertyValue('transform')
|
|
19
|
+
|
|
20
|
+
const observer = new MutationObserver(() => {
|
|
21
|
+
if (parent.style.transform !== position) {
|
|
22
|
+
parent.style.setProperty('transform', position)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
observer.observe(parent, {
|
|
27
|
+
attributes: true,
|
|
28
|
+
attributeFilter: ['style']
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
stateRef.current.cb = () => {
|
|
32
|
+
observer.disconnect()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
popoverRef.current.style.setProperty('--radix-popover-trigger-width', width)
|
|
36
|
+
}
|
|
37
|
+
}, [])
|
|
38
|
+
|
|
39
|
+
const handleAnimationEnd = useCallback(() => {
|
|
40
|
+
if (stateRef.current.cb) {
|
|
41
|
+
stateRef.current.cb()
|
|
42
|
+
stateRef.current.cb = null
|
|
43
|
+
}
|
|
44
|
+
}, [])
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
popoverRef,
|
|
48
|
+
handleAnimationEnd,
|
|
49
|
+
froozePopoverPosition
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface UseLoadingProps<T = unknown> {
|
|
4
|
+
onClick: (startLoad: () => void, param: T) => Promise<any>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const useLoading = <T = unknown>({ onClick }: UseLoadingProps<T>) => {
|
|
8
|
+
const [state, setState] = useState({ isLoading: false, isError: false })
|
|
9
|
+
|
|
10
|
+
const handleClick = (param: T) => {
|
|
11
|
+
if (!state.isLoading) {
|
|
12
|
+
onClick(() => setState({ isLoading: true, isError: false }), param)
|
|
13
|
+
.then(() => {
|
|
14
|
+
setState({
|
|
15
|
+
isLoading: false,
|
|
16
|
+
isError: false
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
.catch((e) => {
|
|
20
|
+
console.log(e)
|
|
21
|
+
|
|
22
|
+
setState({
|
|
23
|
+
isLoading: false,
|
|
24
|
+
isError: true
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
...state,
|
|
32
|
+
handleClick
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
export const useLocalStorage = <T>(token: string, defaultValue: T) => {
|
|
4
|
+
const [state, setState] = useState<T>(() => {
|
|
5
|
+
const initialValue = localStorage.getItem(token)
|
|
6
|
+
|
|
7
|
+
if (initialValue) {
|
|
8
|
+
return JSON.parse(initialValue)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return defaultValue
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
localStorage.setItem(token, JSON.stringify(state))
|
|
16
|
+
}, [state])
|
|
17
|
+
|
|
18
|
+
return [state, setState] as [T, React.Dispatch<React.SetStateAction<T>>]
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useRef } from 'react'
|
|
2
|
+
import { px } from '@companix/utils-browser'
|
|
3
|
+
|
|
4
|
+
export const usePopoverLeftValue = () => {
|
|
5
|
+
const popoverRef = useRef<HTMLDivElement>(null)
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
popoverRef,
|
|
9
|
+
getLeftValue: () => {
|
|
10
|
+
return popoverRef.current?.style.left ?? '0px'
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const useButtonWidth = () => {
|
|
16
|
+
const buttonRef = useRef<HTMLButtonElement>(null)
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
buttonRef,
|
|
20
|
+
getWidthValue: () => {
|
|
21
|
+
return px(buttonRef.current?.offsetWidth ?? 0)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
export const usePrevious = <T>(value: T) => {
|
|
4
|
+
const currentRef = useRef(value)
|
|
5
|
+
const previousRef = useRef<T>()
|
|
6
|
+
|
|
7
|
+
if (currentRef.current !== value) {
|
|
8
|
+
previousRef.current = currentRef.current
|
|
9
|
+
currentRef.current = value
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return previousRef.current
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const usePreviousChanged = <T>(value: T) => {
|
|
16
|
+
const currentRef = useRef(value)
|
|
17
|
+
const previousRef = useRef<T>()
|
|
18
|
+
|
|
19
|
+
if (currentRef.current !== value) {
|
|
20
|
+
previousRef.current = currentRef.current
|
|
21
|
+
currentRef.current = value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return previousRef.current
|
|
25
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
type ResizeText = [React.RefObject<HTMLTextAreaElement>, () => void]
|
|
4
|
+
|
|
5
|
+
const useResizeTextarea = (
|
|
6
|
+
onResize: ((el: HTMLTextAreaElement) => void) | undefined,
|
|
7
|
+
grow: boolean
|
|
8
|
+
): ResizeText => {
|
|
9
|
+
const elementRef = useRef<HTMLTextAreaElement>(null)
|
|
10
|
+
const currentScrollHeight = useRef<number>(undefined)
|
|
11
|
+
|
|
12
|
+
const resizeElement = useCallback(
|
|
13
|
+
(el: HTMLTextAreaElement) => {
|
|
14
|
+
if (grow && el.offsetParent) {
|
|
15
|
+
el.style.height = ''
|
|
16
|
+
el.style.height = `${el.scrollHeight}px`
|
|
17
|
+
|
|
18
|
+
if (el.scrollHeight !== currentScrollHeight.current && onResize) {
|
|
19
|
+
onResize(el)
|
|
20
|
+
currentScrollHeight.current = el.scrollHeight
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
[grow, onResize]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const resize = useCallback(() => {
|
|
28
|
+
const el = elementRef.current
|
|
29
|
+
|
|
30
|
+
if (!el) {
|
|
31
|
+
/* istanbul ignore next: нет возможности воспроизвести */
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
resizeElement(el)
|
|
36
|
+
}, [elementRef, resizeElement])
|
|
37
|
+
|
|
38
|
+
return [elementRef, resize]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { useResizeTextarea }
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
interface UseScrollListControllerReturn {
|
|
4
|
+
scrollBoxRef: React.RefObject<HTMLDivElement>
|
|
5
|
+
optionsWrapperRef: React.RefObject<HTMLDivElement>
|
|
6
|
+
scrollToElement: (index: number, center?: boolean) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const useScrollListController = (): UseScrollListControllerReturn => {
|
|
10
|
+
const scrollBoxRef = useRef<HTMLDivElement>(null)
|
|
11
|
+
const optionsWrapperRef = useRef<HTMLDivElement>(null)
|
|
12
|
+
|
|
13
|
+
const scrollToElement = useCallback(
|
|
14
|
+
(index: number, center = false) => {
|
|
15
|
+
const dropdown = scrollBoxRef.current
|
|
16
|
+
const optionsWrapper = optionsWrapperRef.current
|
|
17
|
+
|
|
18
|
+
if (!dropdown || !optionsWrapper || index < 0 || index > optionsWrapper.children.length) {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const item = optionsWrapper.children[index] as HTMLElement | null
|
|
23
|
+
/* istanbul ignore if: проверка для TS (ситуация, когда среди children нет элемента с index, маловероятна) */
|
|
24
|
+
if (!item) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const dropdownHeight = dropdown.offsetHeight
|
|
29
|
+
const scrollTop = dropdown.scrollTop
|
|
30
|
+
const itemTop = item.offsetTop
|
|
31
|
+
const itemHeight = item.offsetHeight
|
|
32
|
+
|
|
33
|
+
if (center) {
|
|
34
|
+
dropdown.scrollTop = itemTop - dropdownHeight / 2 + itemHeight / 2
|
|
35
|
+
} else if (itemTop + itemHeight > dropdownHeight + scrollTop) {
|
|
36
|
+
dropdown.scrollTop = itemTop - dropdownHeight + itemHeight
|
|
37
|
+
} else if (itemTop < scrollTop) {
|
|
38
|
+
dropdown.scrollTop = itemTop
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
[optionsWrapperRef, scrollBoxRef]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return { scrollToElement, scrollBoxRef, optionsWrapperRef }
|
|
45
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useRef, useState, useMemo, useCallback, useLayoutEffect } from 'react'
|
|
2
|
+
import { normalize, truncateNumber, getFloatDigits } from '@companix/utils-js'
|
|
3
|
+
|
|
4
|
+
export interface StepperInputOptions {
|
|
5
|
+
value: number
|
|
6
|
+
onChange: (value: number) => void
|
|
7
|
+
step: number
|
|
8
|
+
minValue?: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// TODO: если onChange будет меньше minValue произойдет ошибка
|
|
12
|
+
export const useStepperInput = ({ minValue, value, onChange, step }: StepperInputOptions) => {
|
|
13
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
14
|
+
|
|
15
|
+
const precision = useMemo(() => {
|
|
16
|
+
return getFloatDigits(step.toString())
|
|
17
|
+
}, [step])
|
|
18
|
+
|
|
19
|
+
const formatting = useCallback(
|
|
20
|
+
(value: number) => {
|
|
21
|
+
if (minValue && value < minValue) {
|
|
22
|
+
return minValue.toFixed(precision)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return truncateNumber(value, precision)
|
|
26
|
+
},
|
|
27
|
+
[minValue, precision]
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const [inputValue, setInputValue] = useState({
|
|
31
|
+
value: formatting(value),
|
|
32
|
+
cursor: 0
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const setChanges = (value: number, i: number = 0) => {
|
|
36
|
+
const nextValue = formatting(value)
|
|
37
|
+
|
|
38
|
+
setInputValue({ value: nextValue, cursor: (inputRef.current?.selectionStart || 0) + i })
|
|
39
|
+
onChange(+nextValue)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (+inputValue.value !== value) {
|
|
43
|
+
setInputValue({ value: formatting(value), cursor: 0 })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
useLayoutEffect(() => {
|
|
47
|
+
if (inputRef.current) {
|
|
48
|
+
inputRef.current.setSelectionRange(inputValue.cursor, inputValue.cursor)
|
|
49
|
+
inputRef.current.focus()
|
|
50
|
+
}
|
|
51
|
+
}, [inputValue])
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
inputRef,
|
|
55
|
+
value: inputValue.value,
|
|
56
|
+
increment: () => {
|
|
57
|
+
setChanges(normalize(value + step, precision))
|
|
58
|
+
},
|
|
59
|
+
decrement: () => {
|
|
60
|
+
setChanges(normalize(value - step, precision))
|
|
61
|
+
},
|
|
62
|
+
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
63
|
+
const value = e.currentTarget.value.trim()
|
|
64
|
+
|
|
65
|
+
if (value) {
|
|
66
|
+
if (isNaN(+value) || value.includes('e')) {
|
|
67
|
+
setChanges(+inputValue.value, -1)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (inputValue.value.includes('.') && inputValue.value.replace('.', '') === value) {
|
|
72
|
+
if (inputRef.current && inputRef.current.selectionStart) {
|
|
73
|
+
setChanges(+inputValue.value)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setChanges(+value)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
export const useLayoutAndUpdate = (callback: () => void, deps?: React.DependencyList | undefined) => {
|
|
4
|
+
const isRendered = useRef(false)
|
|
5
|
+
|
|
6
|
+
useLayoutEffect(() => {
|
|
7
|
+
isRendered.current = true
|
|
8
|
+
callback()
|
|
9
|
+
}, [])
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (isRendered.current) {
|
|
13
|
+
isRendered.current = false
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
callback()
|
|
18
|
+
}, deps)
|
|
19
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { CalendarProps } from '../DatePicker/Calendar'
|
|
2
|
+
import { addMonths, subMonths, useDayDisableCheker } from '../__libs/calendar'
|
|
3
|
+
import { DEFAULT_MAX_YEAR, DEFAULT_MIN_YEAR } from '../__utils/utils'
|
|
4
|
+
import { useCallback, useState } from 'react'
|
|
5
|
+
|
|
6
|
+
export interface UseCalendarDependencies
|
|
7
|
+
extends Pick<
|
|
8
|
+
CalendarProps,
|
|
9
|
+
'minDateTime' | 'maxDateTime' | 'shouldDisableDate' | 'disableFuture' | 'disablePast'
|
|
10
|
+
> {
|
|
11
|
+
disablePast?: boolean
|
|
12
|
+
disableFuture?: boolean
|
|
13
|
+
minDateTime?: Date
|
|
14
|
+
maxDateTime?: Date
|
|
15
|
+
value?: Date | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const useCalendar = ({
|
|
19
|
+
value,
|
|
20
|
+
disablePast,
|
|
21
|
+
disableFuture,
|
|
22
|
+
shouldDisableDate,
|
|
23
|
+
minDateTime,
|
|
24
|
+
maxDateTime
|
|
25
|
+
}: UseCalendarDependencies) => {
|
|
26
|
+
const [viewDate, setViewDate] = useState(value || new Date())
|
|
27
|
+
|
|
28
|
+
const setPrevMonth = useCallback(() => {
|
|
29
|
+
// onPrevMonth?.();
|
|
30
|
+
setViewDate(subMonths(viewDate, 1))
|
|
31
|
+
}, [viewDate])
|
|
32
|
+
const setNextMonth = useCallback(() => {
|
|
33
|
+
// onNextMonth?.();
|
|
34
|
+
setViewDate(addMonths(viewDate, 1))
|
|
35
|
+
}, [viewDate])
|
|
36
|
+
|
|
37
|
+
const isDayDisabled = useDayDisableCheker({
|
|
38
|
+
disableFuture,
|
|
39
|
+
disablePast,
|
|
40
|
+
shouldDisableDate,
|
|
41
|
+
minDateTime,
|
|
42
|
+
maxDateTime
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const isMonthDisabled = useCallback(
|
|
46
|
+
(month: number, year?: number): boolean => {
|
|
47
|
+
const now = new Date()
|
|
48
|
+
year = year || viewDate.getFullYear()
|
|
49
|
+
const minMonth = minDateTime ? minDateTime.getMonth() : 0
|
|
50
|
+
const maxMonth = maxDateTime ? maxDateTime.getMonth() : 11
|
|
51
|
+
const minYear = minDateTime?.getFullYear() || DEFAULT_MIN_YEAR
|
|
52
|
+
const maxYear = maxDateTime?.getFullYear() || DEFAULT_MAX_YEAR
|
|
53
|
+
|
|
54
|
+
let isDisabled =
|
|
55
|
+
year >= minYear && year <= maxYear
|
|
56
|
+
? (year === minYear && minMonth > month) || (year === maxYear && month > maxMonth)
|
|
57
|
+
: true
|
|
58
|
+
|
|
59
|
+
if (disableFuture) {
|
|
60
|
+
isDisabled =
|
|
61
|
+
isDisabled || (year === now.getFullYear() ? month > now.getMonth() : year > now.getFullYear())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (disablePast) {
|
|
65
|
+
isDisabled =
|
|
66
|
+
isDisabled || (year === now.getFullYear() ? month < now.getMonth() : year < now.getFullYear())
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return isDisabled
|
|
70
|
+
},
|
|
71
|
+
[disableFuture, disablePast, viewDate, minDateTime, maxDateTime]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const isYearDisabled = useCallback(
|
|
75
|
+
(year: number): boolean => {
|
|
76
|
+
const now = new Date()
|
|
77
|
+
const minYear = minDateTime?.getFullYear() || DEFAULT_MIN_YEAR
|
|
78
|
+
const maxYear = maxDateTime?.getFullYear() || DEFAULT_MAX_YEAR
|
|
79
|
+
|
|
80
|
+
let isDisabled = minYear > year || year > maxYear
|
|
81
|
+
|
|
82
|
+
if (disableFuture) {
|
|
83
|
+
isDisabled = isDisabled || year > now.getFullYear()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (disablePast) {
|
|
87
|
+
isDisabled = isDisabled || year < now.getFullYear()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return isDisabled
|
|
91
|
+
},
|
|
92
|
+
[disableFuture, disablePast, minDateTime, maxDateTime]
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
viewDate,
|
|
97
|
+
setViewDate,
|
|
98
|
+
setPrevMonth,
|
|
99
|
+
setNextMonth,
|
|
100
|
+
isDayDisabled,
|
|
101
|
+
isMonthDisabled,
|
|
102
|
+
isYearDisabled
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { DefaultMonths, getMonthMaxDay } from '../__utils/utils'
|
|
2
|
+
import { range } from '@companix/utils-js'
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
|
|
5
|
+
export const defaultMax = new Date(2050, 0, 1)
|
|
6
|
+
export const defaultMin = new Date(1925, 0, 1)
|
|
7
|
+
|
|
8
|
+
interface Options {
|
|
9
|
+
min?: Date
|
|
10
|
+
max?: Date
|
|
11
|
+
now: Date
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const getDate = (date: Date) => {
|
|
15
|
+
return {
|
|
16
|
+
day: date.getDate(),
|
|
17
|
+
month: date.getMonth(),
|
|
18
|
+
year: date.getFullYear()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const useCalendarOptions2 = ({ min = defaultMin, max = defaultMax, now }: Options) => {
|
|
23
|
+
const max_values = useMemo(() => getDate(max), [max])
|
|
24
|
+
const min_values = useMemo(() => getDate(min), [min])
|
|
25
|
+
const now_values = useMemo(() => getDate(now), [now])
|
|
26
|
+
|
|
27
|
+
const years = useMemo(() => {
|
|
28
|
+
return range(max_values.year, min_values.year).map((value) => ({
|
|
29
|
+
title: value.toString(),
|
|
30
|
+
value
|
|
31
|
+
}))
|
|
32
|
+
}, [max_values.year, min_values.year])
|
|
33
|
+
|
|
34
|
+
const months = useMemo(() => {
|
|
35
|
+
const options = DefaultMonths.map((name, index) => ({
|
|
36
|
+
title: name,
|
|
37
|
+
value: index
|
|
38
|
+
}))
|
|
39
|
+
|
|
40
|
+
if (min_values.month && now_values.year === min_values.year) {
|
|
41
|
+
const i = options.findIndex(({ value }) => value === min_values.month)
|
|
42
|
+
options.splice(0, i)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (max_values.month && now_values.year === max_values.year) {
|
|
46
|
+
const i = options.findIndex(({ value }) => value === max_values.month)
|
|
47
|
+
options.splice(i + 1, options.length)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return options
|
|
51
|
+
}, [now_values.year, min_values.year, min_values.month, max_values.year, max_values.month])
|
|
52
|
+
|
|
53
|
+
const days = useMemo(() => {
|
|
54
|
+
if (now_values.month === 0) {
|
|
55
|
+
return []
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const maxDays = getMonthMaxDay(now_values.month, now_values.year)
|
|
59
|
+
|
|
60
|
+
const options = Array.from({ length: maxDays }, (_, i) => ({
|
|
61
|
+
title: `${i + 1}`,
|
|
62
|
+
value: i + 1
|
|
63
|
+
}))
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
min_values.day &&
|
|
67
|
+
now_values.month === min_values.month &&
|
|
68
|
+
now_values.year === min_values.year
|
|
69
|
+
) {
|
|
70
|
+
const i = options.findIndex(({ value }) => value === min_values.day)
|
|
71
|
+
options.splice(0, i)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
max_values.day &&
|
|
76
|
+
now_values.month === max_values.month &&
|
|
77
|
+
now_values.year === max_values.year
|
|
78
|
+
) {
|
|
79
|
+
const i = options.findIndex(({ value }) => value === max_values.day)
|
|
80
|
+
options.splice(i + 1, options.length)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return options
|
|
84
|
+
}, [now_values.month, now_values.year, min, max])
|
|
85
|
+
|
|
86
|
+
return { years, months, days }
|
|
87
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { DefaultMonths, getMonthMaxDay } from '../__utils/utils'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
import { DateFormat } from '..'
|
|
4
|
+
import { range } from '@companix/utils-js'
|
|
5
|
+
|
|
6
|
+
export const defaultMax = { day: 31, month: 12, year: 2050 }
|
|
7
|
+
export const defaultMin = { day: 1, month: 1, year: 1900 }
|
|
8
|
+
|
|
9
|
+
interface Options {
|
|
10
|
+
min?: DateFormat
|
|
11
|
+
max?: DateFormat
|
|
12
|
+
now: DateFormat
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const useCalendarOptions = ({ min = defaultMin, max = defaultMax, now }: Options) => {
|
|
16
|
+
const years = useMemo(() => {
|
|
17
|
+
return range(max.year, min.year).map((value) => ({
|
|
18
|
+
title: value.toString(),
|
|
19
|
+
value
|
|
20
|
+
}))
|
|
21
|
+
}, [max.year, min.year])
|
|
22
|
+
|
|
23
|
+
const months = useMemo(() => {
|
|
24
|
+
const options = DefaultMonths.map((name, index) => ({
|
|
25
|
+
title: name,
|
|
26
|
+
value: index + 1
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
if (min.month && now.year === min.year) {
|
|
30
|
+
const i = options.findIndex(({ value }) => value === min.month)
|
|
31
|
+
options.splice(0, i)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (max.month && now.year === max.year) {
|
|
35
|
+
const i = options.findIndex(({ value }) => value === max.month)
|
|
36
|
+
options.splice(i + 1, options.length)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return options
|
|
40
|
+
}, [now.year, min.year, min.month, max.year, max.month])
|
|
41
|
+
|
|
42
|
+
const days = useMemo(() => {
|
|
43
|
+
if (now.month === 0) {
|
|
44
|
+
return []
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const maxDays = getMonthMaxDay(now.month, now.year)
|
|
48
|
+
|
|
49
|
+
const options = Array.from({ length: maxDays }, (_, i) => ({
|
|
50
|
+
title: `${i + 1}`,
|
|
51
|
+
value: i + 1
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
if (min.day && now.month === min.month && now.year === min.year) {
|
|
55
|
+
const i = options.findIndex(({ value }) => value === min.day)
|
|
56
|
+
options.splice(0, i)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (max.day && now.month === max.month && now.year === max.year) {
|
|
60
|
+
const i = options.findIndex(({ value }) => value === max.day)
|
|
61
|
+
options.splice(i + 1, options.length)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return options
|
|
65
|
+
}, [now.month, now.year, min, max])
|
|
66
|
+
|
|
67
|
+
return { years, months, days }
|
|
68
|
+
}
|