@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.
Files changed (124) hide show
  1. package/.eslintrc +54 -0
  2. package/declaration.d.ts +4 -0
  3. package/index.html +12 -0
  4. package/package.json +66 -0
  5. package/playground/App.tsx +166 -0
  6. package/playground/Example.tsx +14 -0
  7. package/playground/Test.tsx +44 -0
  8. package/playground/animation-test-1/index.scss +20 -0
  9. package/playground/animation-test-1/index.tsx +17 -0
  10. package/playground/animation-test-2/index.scss +62 -0
  11. package/playground/animation-test-2/index.tsx +32 -0
  12. package/playground/bootstrap.tsx +19 -0
  13. package/playground/buttons/index.tsx +132 -0
  14. package/playground/checkbox/index.tsx +64 -0
  15. package/playground/date-input/index.tsx +45 -0
  16. package/playground/date-picker/index.tsx +41 -0
  17. package/playground/dialog/index.tsx +92 -0
  18. package/playground/dialog-alert/index.tsx +47 -0
  19. package/playground/drawer/index.tsx +55 -0
  20. package/playground/index.css +33 -0
  21. package/playground/index.scss +270 -0
  22. package/playground/input/index.tsx +112 -0
  23. package/playground/number-inputs/index.tsx +50 -0
  24. package/playground/popovers/index.tsx +70 -0
  25. package/playground/radio-group/index.tsx +69 -0
  26. package/playground/select/index.tsx +72 -0
  27. package/playground/select-tags/index.tsx +36 -0
  28. package/playground/styles.scss +2 -0
  29. package/playground/switch/index.tsx +44 -0
  30. package/playground/tabs/index.tsx +16 -0
  31. package/playground/test.scss +0 -0
  32. package/playground/text-area/index.tsx +17 -0
  33. package/playground/text-input/index.tsx +12 -0
  34. package/playground/toaster/index.tsx +156 -0
  35. package/playground/tooltip/index.tsx +26 -0
  36. package/src/Button/Button.scss +128 -0
  37. package/src/Button/index.tsx +72 -0
  38. package/src/ButtonGroup/ButtonGroup.scss +18 -0
  39. package/src/ButtonGroup/index.tsx +20 -0
  40. package/src/Checkbox/Checkbox.scss +115 -0
  41. package/src/Checkbox/index.tsx +46 -0
  42. package/src/Countdown/index.tsx +54 -0
  43. package/src/DateInput/DateInput.scss +11 -0
  44. package/src/DateInput/index.tsx +96 -0
  45. package/src/DatePicker/Calendar.scss +125 -0
  46. package/src/DatePicker/Calendar.tsx +157 -0
  47. package/src/DatePicker/CalendarHeader.tsx +139 -0
  48. package/src/DatePicker/DatePicker.scss +0 -0
  49. package/src/DatePicker/index.tsx +177 -0
  50. package/src/Dialog/Dialog.scss +25 -0
  51. package/src/Dialog/Popup.scss +55 -0
  52. package/src/Dialog/index.tsx +31 -0
  53. package/src/DialogAlert/Alert.scss +52 -0
  54. package/src/DialogAlert/Alert.tsx +78 -0
  55. package/src/DialogAlert/Viewport.tsx +52 -0
  56. package/src/DialogAlert/index.tsx +37 -0
  57. package/src/Drawer/Drawer.scss +112 -0
  58. package/src/Drawer/index.tsx +46 -0
  59. package/src/File/index.tsx +60 -0
  60. package/src/Form/Form.scss +70 -0
  61. package/src/Form/Input.scss +24 -0
  62. package/src/Form/index.tsx +131 -0
  63. package/src/Icon/icon.scss +18 -0
  64. package/src/Icon/index.tsx +43 -0
  65. package/src/LoadButton/index.tsx +17 -0
  66. package/src/NumberInput/index.tsx +74 -0
  67. package/src/OptionItem/Option.scss +89 -0
  68. package/src/OptionItem/OptionItem.tsx +49 -0
  69. package/src/OptionItem/OptionsList.tsx +26 -0
  70. package/src/Popover/Popover.scss +80 -0
  71. package/src/Popover/index.tsx +117 -0
  72. package/src/Radio/Radio.scss +148 -0
  73. package/src/Radio/index.tsx +68 -0
  74. package/src/Scrollable/ImitateScroll.tsx +141 -0
  75. package/src/Scrollable/Scrollable.scss +50 -0
  76. package/src/Scrollable/index.tsx +141 -0
  77. package/src/Select/Select.scss +80 -0
  78. package/src/Select/SelectInput.tsx +131 -0
  79. package/src/Select/index.tsx +134 -0
  80. package/src/SelectTags/SelectTags.scss +66 -0
  81. package/src/SelectTags/index.tsx +192 -0
  82. package/src/Spinner/Spinner.scss +14 -0
  83. package/src/Spinner/index.tsx +19 -0
  84. package/src/Stepper/StepperInput.scss +35 -0
  85. package/src/Stepper/index.tsx +76 -0
  86. package/src/Switch/Switch.scss +102 -0
  87. package/src/Switch/index.tsx +49 -0
  88. package/src/Tabs/Tabs.scss +58 -0
  89. package/src/Tabs/index.tsx +89 -0
  90. package/src/TextArea/TextArea.scss +34 -0
  91. package/src/TextArea/index.tsx +51 -0
  92. package/src/Toaster/RemoveListener.tsx +11 -0
  93. package/src/Toaster/Toast.tsx +69 -0
  94. package/src/Toaster/Toaster.scss +151 -0
  95. package/src/Toaster/Viewport.tsx +117 -0
  96. package/src/Toaster/index.tsx +52 -0
  97. package/src/Tooltip/Tooltip.scss +28 -0
  98. package/src/Tooltip/index.tsx +33 -0
  99. package/src/__hooks/use-frooze-closing.ts +51 -0
  100. package/src/__hooks/use-loading.ts +34 -0
  101. package/src/__hooks/use-local-storage.ts +19 -0
  102. package/src/__hooks/use-popover-position.ts +24 -0
  103. package/src/__hooks/use-previos.ts +25 -0
  104. package/src/__hooks/use-resize.ts +41 -0
  105. package/src/__hooks/use-scrollbox.ts +45 -0
  106. package/src/__hooks/use-stepper-input.ts +82 -0
  107. package/src/__hooks/use-update.ts +19 -0
  108. package/src/__hooks/useCalendar.ts +104 -0
  109. package/src/__hooks/useCalendarOptions-copy.ts +87 -0
  110. package/src/__hooks/useCalendarOptions.ts +68 -0
  111. package/src/__libs/calendar.ts +175 -0
  112. package/src/__utils/utils.ts +137 -0
  113. package/src/css.scss +120 -0
  114. package/src/index.scss +22 -0
  115. package/src/index.ts +36 -0
  116. package/src/mixins.scss +99 -0
  117. package/src/theme.scss +103 -0
  118. package/src/types.ts +14 -0
  119. package/tailwind.config.js +91 -0
  120. package/themes/classic/animations.scss +179 -0
  121. package/themes/classic/classic.scss +493 -0
  122. package/tsconfig.json +27 -0
  123. package/vite.build.ts +35 -0
  124. package/vite.config.ts +33 -0
@@ -0,0 +1,68 @@
1
+ import { useId } from 'react'
2
+ import * as RadioPrimitive from '@radix-ui/react-radio-group'
3
+ import { attr } from '@companix/utils-browser'
4
+
5
+ interface RadioOption<T> {
6
+ value: T
7
+ label: React.ReactNode
8
+ }
9
+
10
+ interface RadioGroupProps<T> {
11
+ options: RadioOption<T>[]
12
+ onChange: (event: T) => void
13
+ value: T | null
14
+ disabled?: boolean
15
+ required?: boolean
16
+ size?: 'sm' | 'md'
17
+ }
18
+
19
+ export const RadioGroup = <T extends string>(props: RadioGroupProps<T>) => {
20
+ const { options, value, onChange, disabled, required, size } = props
21
+
22
+ return (
23
+ <RadioPrimitive.Root
24
+ className="radio-group"
25
+ disabled={disabled}
26
+ data-required={attr(required && !value)}
27
+ data-v={value}
28
+ value={value}
29
+ onValueChange={(e) => onChange(e as T)}
30
+ >
31
+ {options.map((option, i) => (
32
+ <Radio
33
+ key={`radio-${option.value}-${i}`}
34
+ {...option}
35
+ size={size}
36
+ disabled={disabled}
37
+ required={required && !value}
38
+ />
39
+ ))}
40
+ </RadioPrimitive.Root>
41
+ )
42
+ }
43
+
44
+ interface RadioProps extends RadioOption<string> {
45
+ size?: 'sm' | 'md'
46
+ disabled?: boolean
47
+ required?: boolean
48
+ }
49
+
50
+ export const Radio = ({ value, label, size = 'md', disabled, required }: RadioProps) => {
51
+ const id = useId()
52
+
53
+ return (
54
+ <span
55
+ className="radio"
56
+ data-disabled={attr(disabled)}
57
+ data-size={size}
58
+ data-required={attr(required)}
59
+ >
60
+ <RadioPrimitive.Item className="radio-box" value={value} disabled={disabled} id={id}>
61
+ <RadioPrimitive.Indicator className="radio-mark" />
62
+ </RadioPrimitive.Item>
63
+ <label className="radio-label" htmlFor={id} data-disabled={attr(disabled)}>
64
+ {label}
65
+ </label>
66
+ </span>
67
+ )
68
+ }
@@ -0,0 +1,141 @@
1
+ import cn from 'classnames'
2
+
3
+ import { useRef, useLayoutEffect, useMemo, useCallback, useEffect } from 'react'
4
+ import { getContainers, pc, px } from '@companix/utils-browser'
5
+
6
+ interface Props {
7
+ children: React.ReactNode
8
+ thumbClassName: string
9
+ thumbColor?: string
10
+ trackWidth?: number
11
+ thumbMargin?: number
12
+ scrollableClassName?: string
13
+ }
14
+
15
+ // Отслеживание изменений scrollHeight:
16
+ // https://stackoverflow.com/questions/44428370/detect-scrollheight-change-with-mutationobserver
17
+
18
+ const ImitateScroll = ({
19
+ children,
20
+ thumbClassName,
21
+ scrollableClassName,
22
+ thumbMargin = 0,
23
+ trackWidth = 20
24
+ }: Props) => {
25
+ const scrollThumbRef = useRef<HTMLDivElement>(null)
26
+ const scrollableRef = useRef<HTMLDivElement>(null)
27
+
28
+ const data = useMemo(() => {
29
+ return { positons: { top: 0, y: 0 }, scrollRatio: 0 }
30
+ }, [])
31
+
32
+ const listeners = useMemo(() => {
33
+ return {
34
+ start() {
35
+ document.addEventListener('mousemove', handleMouseMove)
36
+ document.addEventListener('mouseup', handleMouseUp)
37
+ },
38
+ clear() {
39
+ document.removeEventListener('mousemove', handleMouseMove)
40
+ document.removeEventListener('mouseup', handleMouseUp)
41
+ }
42
+ }
43
+ }, [])
44
+
45
+ useEffect(() => {
46
+ const observer = new ResizeObserver(setThumbHeight)
47
+
48
+ if (scrollableRef.current) {
49
+ getContainers(scrollableRef.current, (element) => {
50
+ observer.observe(element)
51
+ })
52
+ }
53
+
54
+ return () => {
55
+ if (scrollableRef.current) {
56
+ getContainers(scrollableRef.current, (element) => {
57
+ observer.unobserve(element)
58
+ })
59
+ }
60
+ }
61
+ }, [])
62
+
63
+ useLayoutEffect(() => {
64
+ setThumbHeight()
65
+ }, [])
66
+
67
+ const setThumbHeight = useCallback(() => {
68
+ const { current: container } = scrollableRef
69
+ const { current: thumb } = scrollThumbRef
70
+
71
+ if (thumb && container) {
72
+ const scrollRatio = container.clientHeight / container.scrollHeight
73
+
74
+ thumb.style.height = pc(scrollRatio * 100)
75
+ thumb.style.display = scrollRatio === 1 ? 'none' : 'block'
76
+
77
+ data.scrollRatio = scrollRatio
78
+ }
79
+ }, [])
80
+
81
+ const handleMouseMove = useCallback(({ clientY }: MouseEvent) => {
82
+ const { current: container } = scrollableRef
83
+
84
+ if (container) {
85
+ const dy = clientY - data.positons.y
86
+ container.scrollTop = data.positons.top + dy / data.scrollRatio
87
+ }
88
+ }, [])
89
+
90
+ const handleMouseUp = useCallback(() => {
91
+ listeners.clear()
92
+ }, [])
93
+
94
+ const handleMouseDown = useCallback(({ clientY }: React.MouseEvent) => {
95
+ const { current: container } = scrollableRef
96
+
97
+ if (container) {
98
+ data.positons = {
99
+ top: container.scrollTop,
100
+ y: clientY
101
+ }
102
+ }
103
+
104
+ listeners.start()
105
+ }, [])
106
+
107
+ const handleScroll = () => {
108
+ requestAnimationFrame(() => {
109
+ const { current: container } = scrollableRef
110
+ const { current: thumb } = scrollThumbRef
111
+
112
+ if (thumb && container) {
113
+ thumb.style.top = pc((container.scrollTop * 100) / container.scrollHeight)
114
+ }
115
+ })
116
+ }
117
+
118
+ return (
119
+ <div className="relative h-full overflow-hidden">
120
+ <div
121
+ className={cn('hidden-scroll h-full overflow-y-scroll', scrollableClassName)}
122
+ onScroll={handleScroll}
123
+ ref={scrollableRef}
124
+ >
125
+ {children}
126
+ </div>
127
+ <div
128
+ className="absolute right-0 top-0 box-border h-full"
129
+ style={{ width: px(trackWidth), padding: px(thumbMargin) }}
130
+ >
131
+ <div
132
+ onMouseDown={handleMouseDown}
133
+ className={`${thumbClassName} relative w-full rounded-full`}
134
+ ref={scrollThumbRef}
135
+ />
136
+ </div>
137
+ </div>
138
+ )
139
+ }
140
+
141
+ export { ImitateScroll }
@@ -0,0 +1,50 @@
1
+ .scrollable::-webkit-scrollbar {
2
+ width: var(--scrollbar-width);
3
+ height: var(--scrollbar-width);
4
+ }
5
+
6
+ .scrollable::-webkit-scrollbar-thumb {
7
+ background-clip: padding-box;
8
+ border: var(--thumb-padding) solid transparent;
9
+ border-radius: 999px;
10
+ background-color: var(--thumb-color);
11
+ }
12
+
13
+ .scrollable::-webkit-scrollbar-corner {
14
+ background-color: transparent;
15
+ }
16
+
17
+ .scrollable-hover-interaction::-webkit-scrollbar-thumb,
18
+ .scrollable-hover-interaction::-webkit-scrollbar-track {
19
+ visibility: hidden;
20
+ }
21
+ .scrollable-hover-interaction:hover::-webkit-scrollbar-thumb,
22
+ .scrollable-hover-interaction:hover::-webkit-scrollbar-track {
23
+ visibility: visible;
24
+ }
25
+
26
+ .scrollable-border-position::-webkit-scrollbar-thumb {
27
+ border-radius: 0px;
28
+
29
+ border-right-width: 0px;
30
+ border-bottom-width: 0px;
31
+
32
+ border-left-width: calc(var(--thumb-padding) * 2);
33
+ border-top-width: calc(var(--thumb-padding) * 2);
34
+ }
35
+
36
+ .hidden-scroll::-webkit-scrollbar {
37
+ display: none;
38
+ }
39
+
40
+ .hidden-scroll::-webkit-scrollbar-thumb {
41
+ display: none;
42
+ }
43
+
44
+ .hidden-scroll::-webkit-scrollbar-track {
45
+ display: none;
46
+ }
47
+
48
+ .hidden-scroll::-webkit-scrollbar-corner {
49
+ display: none;
50
+ }
@@ -0,0 +1,141 @@
1
+ import './Scrollable.scss'
2
+
3
+ import classNames from 'classnames'
4
+ import { forwardRef } from 'react'
5
+ import { px, varToStyle } from '@companix/utils-browser'
6
+
7
+ interface OuterImplementation {
8
+ implementation: 'outer'
9
+ shadowPadding?: number
10
+ scrollbarWidth: number
11
+ noneCorrect?: boolean
12
+ }
13
+
14
+ interface EdgeImplementation {
15
+ implementation: 'edge'
16
+ scrollbarWidth: number
17
+ padding: number
18
+ }
19
+
20
+ interface InnerImplementation {
21
+ implementation: 'inner'
22
+ padding: number
23
+ }
24
+
25
+ interface ScrollableProps {
26
+ heightAuto?: boolean
27
+ scrollX?: boolean
28
+ scrollY?: boolean
29
+ interactionKind?: 'static' | 'hover'
30
+ thumbPos?: 'center' | 'border'
31
+ thumbPadding?: number
32
+ thumbColor?: string
33
+ onWheel?: React.WheelEventHandler<HTMLDivElement>
34
+ onScroll?: React.UIEventHandler<HTMLDivElement>
35
+ maxHeight?: number
36
+ children: React.ReactNode
37
+ style?: React.CSSProperties
38
+ className?: string
39
+ }
40
+
41
+ type Implementation = InnerImplementation | OuterImplementation | EdgeImplementation
42
+
43
+ const Scrollable = forwardRef<HTMLDivElement, ScrollableProps & Implementation>((props, ref) => {
44
+ let {
45
+ interactionKind = 'static',
46
+ thumbPos = 'center',
47
+ thumbPadding = 4,
48
+ heightAuto,
49
+ scrollX,
50
+ className,
51
+ scrollY,
52
+ onWheel,
53
+ maxHeight,
54
+ onScroll,
55
+ thumbColor,
56
+ children
57
+ } = props
58
+
59
+ const style: React.CSSProperties = (() => {
60
+ if (props.implementation === 'edge') {
61
+ thumbPadding = 0
62
+
63
+ const { padding, scrollbarWidth } = props
64
+
65
+ return {
66
+ ...varToStyle({ '--scrollbar-width': px(scrollbarWidth) }),
67
+ padding: `0px ${padding - scrollbarWidth}px 0px ${padding}px`
68
+ }
69
+ }
70
+
71
+ if (props.implementation === 'outer') {
72
+ const { shadowPadding = 0, noneCorrect, scrollbarWidth } = props
73
+
74
+ if ((window as any).IS_MOBILE) {
75
+ return {
76
+ padding: shadowPadding,
77
+ margin: -shadowPadding
78
+ }
79
+ }
80
+
81
+ return {
82
+ ...varToStyle({ '--scrollbar-width': px(scrollbarWidth) }),
83
+ padding: shadowPadding,
84
+ margin: -shadowPadding,
85
+ marginRight: noneCorrect ? undefined : `calc(-${scrollbarWidth}px - ${shadowPadding}px)`,
86
+ marginBottom: 0
87
+ }
88
+ }
89
+
90
+ if (props.implementation === 'inner') {
91
+ const { padding } = props
92
+
93
+ if ((window as any).IS_MOBILE) {
94
+ return {
95
+ [scrollY ? 'paddingLeft' : 'paddingTop']: padding,
96
+ [scrollY ? 'paddingRight' : 'paddingBottom']: padding
97
+ }
98
+ }
99
+
100
+ return {
101
+ [scrollY ? 'paddingLeft' : 'paddingTop']: padding,
102
+ ...varToStyle({ '--scrollbar-width': px(padding) })
103
+ }
104
+ }
105
+
106
+ return {}
107
+ })()
108
+
109
+ return (
110
+ <div
111
+ ref={ref}
112
+ onWheel={onWheel}
113
+ onScroll={onScroll}
114
+ onMouseDown={(e) => {
115
+ e.preventDefault()
116
+ }}
117
+ style={{
118
+ ...style,
119
+ ...props.style,
120
+ ...{ maxHeight: maxHeight ? px(maxHeight) : undefined },
121
+ ...varToStyle({ '--thumb-padding': px(thumbPadding) }),
122
+ ...varToStyle({ '--thumb-color': thumbColor ?? '#c1c2c8bd' })
123
+ }}
124
+ className={classNames(
125
+ !(window as any).IS_MOBILE && 'scrollable',
126
+ className,
127
+ heightAuto ? '' : 'h-full',
128
+ {
129
+ 'overflow-y-scroll': scrollY,
130
+ 'overflow-x-scroll': scrollX,
131
+ 'scrollable-hover-interaction': interactionKind === 'hover',
132
+ 'scrollable-border-position': thumbPos === 'border'
133
+ }
134
+ )}
135
+ >
136
+ {children}
137
+ </div>
138
+ )
139
+ })
140
+
141
+ export { Scrollable }
@@ -0,0 +1,80 @@
1
+ @use '../mixins.scss';
2
+
3
+ .select {
4
+ cursor: pointer;
5
+ max-width: 100%;
6
+
7
+ &-layout {
8
+ display: flex;
9
+ align-items: center;
10
+ width: 100%;
11
+ min-height: inherit;
12
+ }
13
+
14
+ &-placeholder {
15
+ user-select: none;
16
+
17
+ @include mixins.use-styles(form, placeholder);
18
+ }
19
+
20
+ &-content {
21
+ position: relative;
22
+ display: flex;
23
+ flex: 1;
24
+ align-items: center;
25
+ align-self: stretch;
26
+ overflow: hidden;
27
+
28
+ &:first-child {
29
+ padding-left: var(--form_space, 0);
30
+ }
31
+
32
+ &-text {
33
+ overflow: hidden;
34
+ text-overflow: ellipsis;
35
+ white-space: nowrap;
36
+ }
37
+ }
38
+
39
+ &-element {
40
+ height: 100%;
41
+ display: flex;
42
+ align-items: center;
43
+ }
44
+
45
+ &-close-button {
46
+ pointer-events: auto;
47
+ cursor: pointer;
48
+ padding: 0px var(--form_space, 0);
49
+ height: 100%;
50
+ outline: none;
51
+ color: var(--select_clean_color);
52
+
53
+ &:hover {
54
+ color: var(--select_clean_hover_color);
55
+ }
56
+ }
57
+
58
+ &-expand {
59
+ margin-right: var(--form_space, 0);
60
+
61
+ &:first-child {
62
+ margin-left: var(--form_space, 0);
63
+ }
64
+ }
65
+ }
66
+
67
+ // expand icon
68
+
69
+ .expand-icon {
70
+ color: var(--select_expand_color);
71
+ transition: transform 0.2s;
72
+ }
73
+
74
+ .form {
75
+ &[data-state='open'] {
76
+ .expand-icon {
77
+ transform: rotate(180deg);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,131 @@
1
+ import cn from 'classnames'
2
+
3
+ import { attr } from '@companix/utils-browser'
4
+ import { forwardRef, useCallback, useRef } from 'react'
5
+ import { Icon } from '../Icon'
6
+ import { faChevronDown, faClose } from '@fortawesome/free-solid-svg-icons'
7
+ import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
8
+
9
+ export interface SelectFormProps extends React.HTMLAttributes<HTMLDivElement> {
10
+ required?: boolean
11
+ disabled?: boolean
12
+ className?: string
13
+ leftElement?: React.ReactNode
14
+ placeholder?: string
15
+ value?: string | number
16
+ size?: 'sm' | 'md' | 'lg'
17
+ fill?: boolean
18
+ clearButton?: boolean
19
+ clearButtonIcon?: boolean
20
+ onClear?: (event: React.MouseEvent<HTMLButtonElement>) => void
21
+ }
22
+
23
+ export const SelectInput = forwardRef<HTMLDivElement, SelectFormProps>(
24
+ (
25
+ {
26
+ required,
27
+ size,
28
+ fill,
29
+ leftElement,
30
+ className,
31
+ value,
32
+ clearButton,
33
+ placeholder,
34
+ clearButtonIcon,
35
+ disabled,
36
+ onClear,
37
+ ...containerProps
38
+ },
39
+ ref
40
+ ) => {
41
+ const selectInputRef = useRef<HTMLInputElement>(null)
42
+
43
+ // https://vkui.io/components/custom-select/
44
+ const passClickAndFocusToInputOnClick = useCallback(
45
+ (e: React.MouseEvent<HTMLDivElement>) => {
46
+ if (!selectInputRef.current || !document) {
47
+ return
48
+ }
49
+
50
+ const clickTargetIsNotAnInput = e.target !== selectInputRef.current
51
+ if (clickTargetIsNotAnInput) {
52
+ selectInputRef.current.click()
53
+
54
+ const inputIsNotFocused = document.activeElement !== selectInputRef.current
55
+ if (inputIsNotFocused) {
56
+ selectInputRef.current.focus()
57
+ }
58
+ }
59
+ },
60
+ [selectInputRef]
61
+ )
62
+
63
+ const preventInputBlurWhenClickInsideFocusedSelectArea = (e: React.MouseEvent<HTMLDivElement>) => {
64
+ // Так как инпут больше не оборачивается пустым лэйблом, то клик внутри обертки,
65
+ // но вне инпута (например по иконке дропдауна), будет убирать фокус с инпута.
66
+ // Чтобы в такой ситуации отключить blur инпута мы превентим mousedown событие обёртки
67
+ const isInputFocused = document && document.activeElement === selectInputRef.current
68
+ if (isInputFocused) {
69
+ e.preventDefault()
70
+ }
71
+ }
72
+
73
+ const onClick = (event: React.MouseEvent<HTMLDivElement>) => {
74
+ if (disabled) return
75
+ passClickAndFocusToInputOnClick(event)
76
+ }
77
+
78
+ const onMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
79
+ preventInputBlurWhenClickInsideFocusedSelectArea(event)
80
+ }
81
+
82
+ const handleClear = (event: React.MouseEvent<HTMLButtonElement>) => {
83
+ selectInputRef.current && selectInputRef.current.focus()
84
+ onClear?.(event)
85
+ }
86
+
87
+ return (
88
+ <div
89
+ ref={ref}
90
+ className={cn('form select', className)}
91
+ data-size={size ?? 'md'}
92
+ data-fill={attr(fill)}
93
+ data-required={attr(required)}
94
+ data-disabled={attr(disabled)}
95
+ onMouseDown={onMouseDown}
96
+ {...containerProps}
97
+ onClick={onClick}
98
+ >
99
+ <div className="select-layout form-input">
100
+ {leftElement && <div className="select-element">{leftElement}</div>}
101
+ <div className="select-content">
102
+ <div className="select-content-text" aria-disabled={disabled}>
103
+ {!value && <span className="select-placeholder">{placeholder}</span>}
104
+ {value}
105
+ </div>
106
+ </div>
107
+ <div className="select-element">
108
+ {clearButton && value && (
109
+ <button className="select-close-button" onClick={handleClear}>
110
+ {clearButtonIcon ?? <Icon className="select-close-icon" icon={faClose} size="xxxs" />}
111
+ </button>
112
+ )}
113
+ <Icon className="expand-icon select-expand" icon={faChevronDown} size="xxxs" />
114
+ </div>
115
+ </div>
116
+ <VisuallyHidden asChild>
117
+ <input
118
+ ref={selectInputRef}
119
+ autoComplete="off"
120
+ autoCapitalize="none"
121
+ autoCorrect="off"
122
+ spellCheck="false"
123
+ aria-autocomplete="none"
124
+ onClick={containerProps.onClick}
125
+ readOnly
126
+ />
127
+ </VisuallyHidden>
128
+ </div>
129
+ )
130
+ }
131
+ )