@teamturing/react-kit 2.19.13 → 2.19.15

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.
@@ -23,6 +23,7 @@ const Dialog = ({
23
23
  isOpen,
24
24
  onDismiss,
25
25
  size = 'm',
26
+ initialFocusRef,
26
27
  sx
27
28
  }, ref) => {
28
29
  const handleDismiss = useCallback(() => onDismiss?.(), [onDismiss]);
@@ -45,7 +46,7 @@ const Dialog = ({
45
46
  }, [handleDismiss]);
46
47
  useFocusTrap({
47
48
  containerRef: dialogRef,
48
- initialFocusRef: closeButtonRef,
49
+ initialFocusRef: initialFocusRef || closeButtonRef,
49
50
  disabled: !isOpen
50
51
  });
51
52
  useEffect(() => {
@@ -1,7 +1,7 @@
1
1
  import { forwardRef, createContext, isValidElement, cloneElement } from 'react';
2
2
  import useRelocation from '../../hook/useRelocation.js';
3
3
  import Checkbox from '../Checkbox/index.js';
4
- import OverlaySelectInput from '../OverlaySelectInput/index.js';
4
+ import SearchSelectInput from '../SearchSelectInput/index.js';
5
5
  import Select from '../Select/index.js';
6
6
  import TextInput from '../TextInput/index.js';
7
7
  import View from '../View/index.js';
@@ -28,7 +28,7 @@ const FormControl = ({
28
28
  successMessage: FormControlSuccessMessage
29
29
  }
30
30
  });
31
- const inputComponentCandidates = [TextInput, Select, OverlaySelectInput, Checkbox, ...additionalInputComponentCandidates];
31
+ const inputComponentCandidates = [TextInput, Select, SearchSelectInput, Checkbox, ...additionalInputComponentCandidates];
32
32
  const InputComponent = restComponents.find(component => inputComponentCandidates.some(candidate => /*#__PURE__*/isValidElement(component) && component.type === candidate));
33
33
  const isHorizontalLayoutNeeded = /*#__PURE__*/isValidElement(InputComponent) && InputComponent.type === Checkbox;
34
34
  return /*#__PURE__*/jsxRuntimeExports.jsx(FormControlContext.Provider, {
@@ -15,7 +15,9 @@ const OverlayPopper = ({
15
15
  renderOverlay,
16
16
  placement = 'bottom-start',
17
17
  focusZoneSettings,
18
- focusTrapSettings
18
+ focusTrapSettings,
19
+ onOpen,
20
+ onClose
19
21
  }) => {
20
22
  const {
21
23
  refs,
@@ -36,11 +38,23 @@ const OverlayPopper = ({
36
38
  } = useToggleHandler({
37
39
  initialState: false
38
40
  });
39
- const handleDismiss = () => {
41
+ const handleOverlayToggle = () => {
42
+ if (!isOpen) onOpen?.();else onClose?.();
43
+ toggleOverlay();
44
+ };
45
+ const handleOverlayOpen = () => {
46
+ onOpen?.();
47
+ openOverlay();
48
+ };
49
+ const handleOverlayClose = () => {
50
+ onClose?.();
40
51
  closeOverlay();
41
52
  };
53
+ const handleDismiss = () => {
54
+ handleOverlayClose();
55
+ };
42
56
  const defaultPopperProps = {
43
- onClick: toggleOverlay,
57
+ onClick: handleOverlayToggle,
44
58
  tabIndex: 0,
45
59
  ...{
46
60
  ref: refs.setReference
@@ -50,7 +64,7 @@ const OverlayPopper = ({
50
64
  ...defaultPopperProps
51
65
  }, {
52
66
  isOpen,
53
- openOverlay
67
+ openOverlay: handleOverlayOpen
54
68
  }) : Children.map(propChildren, child => /*#__PURE__*/cloneElement(child, {
55
69
  ...defaultPopperProps
56
70
  }));
@@ -76,7 +90,7 @@ const OverlayPopper = ({
76
90
  onDismiss: handleDismiss
77
91
  }, {
78
92
  isOpen,
79
- closeOverlay
93
+ closeOverlay: handleOverlayClose
80
94
  }, {
81
95
  elements
82
96
  })]
@@ -21,6 +21,7 @@ const Pagination = ({
21
21
  onPreviousClick = noop,
22
22
  onNextClick = noop,
23
23
  renderPage = (page, i) => /*#__PURE__*/jsxRuntimeExports.jsx(PaginationPage, {
24
+ type: 'button',
24
25
  onClick: () => onPageClick(page, i),
25
26
  selected: i === currentPageIndex,
26
27
  children: page.label
@@ -62,6 +63,7 @@ const Pagination = ({
62
63
  sx: sx,
63
64
  children: [renderPreviousPageDirection({
64
65
  previousPageDirectionProps: {
66
+ type: 'button',
65
67
  onClick: () => onPreviousClick(currentPageIndex),
66
68
  disabled: currentPageIndex === 0
67
69
  }
@@ -99,6 +101,7 @@ const Pagination = ({
99
101
  originalIndex
100
102
  }) => renderPaginationPage(page, originalIndex))], renderNextPageDirection({
101
103
  nextPageDirectionProps: {
104
+ type: 'button',
102
105
  onClick: () => onNextClick(currentPageIndex),
103
106
  disabled: currentPageIndex === totalPageCount - 1
104
107
  }
@@ -42,6 +42,7 @@ const Pill = ({
42
42
  hasRemoveButton: !isNullable(propOnRemove),
43
43
  disabled: disabled,
44
44
  ...props,
45
+ type: 'button',
45
46
  children: [typeof LeadingVisual !== 'string' && reactIsExports.isValidElementType(LeadingVisual) ? /*#__PURE__*/jsxRuntimeExports.jsx(LeadingVisual, {}) : LeadingVisual, /*#__PURE__*/jsxRuntimeExports.jsx("span", {
46
47
  title: children?.toString(),
47
48
  children: children
@@ -1,41 +1,141 @@
1
- import { forwardRef } from 'react';
1
+ import { forwardRef, useRef } from 'react';
2
2
  import SvgChevronDown from '../../packages/icons/esm/ChevronDown.js';
3
+ import SvgSearch from '../../packages/icons/esm/Search.js';
3
4
  import { forcePixelValue } from '../../packages/utils/esm/forcePixelValue.js';
4
5
  import { isFunction } from '../../packages/utils/esm/isFunction.js';
5
6
  import { isNullable } from '../../packages/utils/esm/isNullable.js';
6
7
  import { noop } from '../../packages/utils/esm/noop.js';
8
+ import { scrollIntoView } from '../../packages/utils/esm/scrollIntoView.js';
7
9
  import { r as reactIsExports } from '../../node_modules/react-is/index.js';
8
- import styled, { css } from 'styled-components';
10
+ import styled, { css, useTheme } from 'styled-components';
9
11
  import useProvidedOrCreatedRef from '../../hook/useProvidedOrCreatedRef.js';
12
+ import HorizontalDivider from '../HorizontalDivider/index.js';
10
13
  import Overlay from '../Overlay/index.js';
11
14
  import OverlayPopper from '../OverlayPopper/index.js';
15
+ import Space from '../Space/index.js';
12
16
  import StyledIcon from '../StyledIcon/index.js';
17
+ import TextInput from '../TextInput/index.js';
13
18
  import View from '../View/index.js';
14
19
  import { j as jsxRuntimeExports } from '../../node_modules/react/jsx-runtime.js';
15
20
 
16
- const OverlaySelectInput = ({
21
+ const SearchSelectInput = ({
17
22
  validationStatus,
18
23
  leadingVisual: LeadingVisual,
19
24
  children,
25
+ onChange,
26
+ focusTrapSettings,
27
+ focusZoneSettings,
28
+ onOpen,
29
+ onClose,
30
+ searchInputProps,
31
+ value,
32
+ renderValue = value => value?.toString(),
20
33
  ...props
21
34
  }, ref) => {
22
- const inputRef = useProvidedOrCreatedRef(ref);
35
+ const theme = useTheme();
36
+ const hasLeadingVisual = !isNullable(LeadingVisual);
37
+ const valueInputRef = useProvidedOrCreatedRef(ref);
38
+ const labelInputRef = useRef(null);
23
39
  const focusInput = () => {
24
- inputRef.current?.focus();
40
+ labelInputRef.current?.focus();
25
41
  };
26
42
  const {
27
- disabled
43
+ id,
44
+ disabled,
45
+ placeholder
28
46
  } = props;
47
+ const handleSelect = item => {
48
+ if (labelInputRef.current && valueInputRef.current) {
49
+ /**
50
+ * ! valueInput의 native onChange를 trigger하려고 했으나 작동하지 않음.
51
+ * ! 일단 Custom onChange를 만들어서 해결.
52
+ */
53
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
54
+ nativeInputValueSetter?.call(valueInputRef.current, item.value.toString());
55
+ onChange?.(item);
56
+ }
57
+ };
58
+ const listContainerRef = useRef(null);
59
+ const searchInputRef = useRef(null);
60
+ const activeDescendantRef = useRef();
29
61
  return /*#__PURE__*/jsxRuntimeExports.jsx(OverlayPopper, {
30
- renderOverlay: (overlayProps, _, {
62
+ focusTrapSettings: {
63
+ initialFocusRef: searchInputRef,
64
+ restoreFocusOnCleanUp: true,
65
+ ...focusTrapSettings
66
+ },
67
+ focusZoneSettings: {
68
+ containerRef: listContainerRef,
69
+ activeDescendantFocus: searchInputRef,
70
+ focusOutBehavior: 'stop',
71
+ onActiveDescendantChanged: (current, previous) => {
72
+ activeDescendantRef.current = current;
73
+ if (current && listContainerRef.current) {
74
+ current.style.backgroundColor = theme.colors['bg/selected/subtle'];
75
+ scrollIntoView({
76
+ childrenRef: current,
77
+ scrollContainerRef: listContainerRef.current,
78
+ options: {
79
+ behavior: 'auto',
80
+ direction: 'vertical',
81
+ offset: 0
82
+ }
83
+ });
84
+ }
85
+ if (previous && current !== previous) {
86
+ previous.style.backgroundColor = '';
87
+ }
88
+ },
89
+ focusableElementFilter: elem => {
90
+ return elem instanceof HTMLElement;
91
+ },
92
+ ...focusZoneSettings
93
+ },
94
+ onOpen: onOpen,
95
+ onClose: onClose,
96
+ renderOverlay: (overlayProps, overlayHandler, {
31
97
  elements
32
- }) => /*#__PURE__*/jsxRuntimeExports.jsx(Overlay, {
98
+ }) => /*#__PURE__*/jsxRuntimeExports.jsxs(Overlay, {
33
99
  ...overlayProps,
100
+ maxHeight: 200,
101
+ sx: {
102
+ display: 'flex',
103
+ flexDirection: 'column'
104
+ },
34
105
  style: {
35
106
  ...overlayProps.style,
36
107
  width: elements?.reference?.getBoundingClientRect().width
37
108
  },
38
- children: children
109
+ children: [/*#__PURE__*/jsxRuntimeExports.jsx(Space, {
110
+ p: 2,
111
+ sx: {
112
+ flexGrow: 0,
113
+ flexShrink: 0,
114
+ flexBasis: 'auto'
115
+ },
116
+ children: /*#__PURE__*/jsxRuntimeExports.jsx(TextInput, {
117
+ ref: searchInputRef,
118
+ leadingVisual: SvgSearch,
119
+ onKeyDown: e => {
120
+ if (e.code === 'Enter' && activeDescendantRef.current) {
121
+ const activeDescendantEvent = new KeyboardEvent(e.type, e.nativeEvent);
122
+ activeDescendantRef.current?.dispatchEvent(activeDescendantEvent);
123
+ }
124
+ },
125
+ ...searchInputProps
126
+ })
127
+ }), /*#__PURE__*/jsxRuntimeExports.jsx(HorizontalDivider, {}), /*#__PURE__*/jsxRuntimeExports.jsx(View, {
128
+ ref: listContainerRef,
129
+ sx: {
130
+ flexGrow: 1,
131
+ flexShrink: 1,
132
+ flexBasis: 'auto',
133
+ overflowY: 'auto'
134
+ },
135
+ children: children?.({
136
+ handleSelect
137
+ }, overlayHandler)
138
+ })]
39
139
  }),
40
140
  children: (popperProps, {
41
141
  openOverlay
@@ -44,13 +144,14 @@ const OverlaySelectInput = ({
44
144
  tabIndex: disabled ? -1 : 0,
45
145
  disabled: disabled,
46
146
  onClick: focusInput,
47
- hasLeadingVisual: !isNullable(LeadingVisual),
147
+ hasLeadingVisual: hasLeadingVisual,
48
148
  validationStatus: validationStatus,
49
149
  onKeyDown: e => {
50
150
  if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
51
151
  e.preventDefault();
52
152
  openOverlay();
53
153
  }
154
+ e.stopPropagation();
54
155
  },
55
156
  children: [/*#__PURE__*/jsxRuntimeExports.jsx(View, {
56
157
  sx: {
@@ -66,20 +167,42 @@ const OverlaySelectInput = ({
66
167
  }
67
168
  },
68
169
  children: typeof LeadingVisual !== 'string' && reactIsExports.isValidElementType(LeadingVisual) ? /*#__PURE__*/jsxRuntimeExports.jsx(LeadingVisual, {}) : LeadingVisual
69
- }), /*#__PURE__*/jsxRuntimeExports.jsx(BaseInput, {
70
- ref: e => {
71
- isFunction(ref) ? ref(e) : null;
72
- inputRef.current = e;
170
+ }), /*#__PURE__*/jsxRuntimeExports.jsxs(View, {
171
+ sx: {
172
+ display: 'flex',
173
+ alignItems: 'center',
174
+ paddingTop: 3,
175
+ paddingRight: 10,
176
+ paddingBottom: 3,
177
+ paddingLeft: hasLeadingVisual ? 2 : 4,
178
+ whiteSpace: 'pre',
179
+ textOverflow: 'ellipsis',
180
+ width: '100%'
73
181
  },
74
- value: '',
75
- onChange: noop,
76
- ...props,
77
- autoComplete: 'off',
78
- tabIndex: -1,
79
182
  onClick: e => {
80
183
  popperProps.onClick?.(e);
81
- props.onClick?.(e);
82
- }
184
+ },
185
+ children: [!isNullable(renderValue(value)) ? /*#__PURE__*/jsxRuntimeExports.jsx(View, {
186
+ sx: {
187
+ flex: '0 1 auto',
188
+ maxWidth: '100%',
189
+ textOverflow: 'ellipsis',
190
+ whiteSpace: 'pre',
191
+ overflow: 'hidden'
192
+ },
193
+ children: renderValue(value)
194
+ }) : null, /*#__PURE__*/jsxRuntimeExports.jsx(BaseInput, {
195
+ id: id,
196
+ ref: labelInputRef,
197
+ readOnly: true,
198
+ onChange: noop,
199
+ autoComplete: 'off',
200
+ tabIndex: -1,
201
+ onClick: e => {
202
+ props.onClick?.(e);
203
+ },
204
+ placeholder: !isNullable(renderValue(value)) ? '' : placeholder
205
+ })]
83
206
  }), /*#__PURE__*/jsxRuntimeExports.jsx(StyledIcon, {
84
207
  sx: {
85
208
  position: 'absolute',
@@ -91,6 +214,13 @@ const OverlaySelectInput = ({
91
214
  icon: SvgChevronDown,
92
215
  color: disabled ? 'icon/disabled' : 'icon/neutral/bolder',
93
216
  size: 16
217
+ }), /*#__PURE__*/jsxRuntimeExports.jsx(BaseInput, {
218
+ ref: e => {
219
+ isFunction(ref) ? ref(e) : null;
220
+ valueInputRef.current = e;
221
+ },
222
+ type: 'hidden',
223
+ defaultValue: value
94
224
  })]
95
225
  })
96
226
  });
@@ -112,6 +242,8 @@ const TextInputWrapper = styled.div`
112
242
  cursor: default;
113
243
  input {
114
244
  cursor: default;
245
+
246
+ flex: 1;
115
247
  }
116
248
  display: inline-flex;
117
249
  align-items: center;
@@ -193,9 +325,6 @@ const TextInputWrapper = styled.div`
193
325
 
194
326
  ${props => props.hasLeadingVisual && css`
195
327
  padding-left: ${forcePixelValue(props.theme.space[4])};
196
- input {
197
- padding-left: ${forcePixelValue(props.theme.space[2])};
198
- }
199
328
  `}
200
329
 
201
330
  transition: color 100ms, background-color 100ms;
@@ -211,31 +340,19 @@ const UnstyledInput = styled.input`
211
340
  border-radius: inherit;
212
341
  color: inherit;
213
342
  transition: inherit;
343
+ width: 100%;
214
344
 
215
345
  border: 0;
346
+ padding: 0;
216
347
  background-color: transparent;
217
- width: 100%;
218
348
  &:focus {
219
349
  outline: 0;
220
350
  }
221
351
  `;
222
352
  const BaseInput = styled(UnstyledInput)`
223
- padding-top: ${({
224
- theme
225
- }) => forcePixelValue(theme.space[3])};
226
- padding-right: ${({
227
- theme
228
- }) => forcePixelValue(theme.space[10])};
229
- padding-bottom: ${({
230
- theme
231
- }) => forcePixelValue(theme.space[3])};
232
- padding-left: ${({
233
- theme
234
- }) => forcePixelValue(theme.space[4])};
235
-
236
353
  white-space: pre;
237
354
  text-overflow: ellipsis;
238
355
  `;
239
- var OverlaySelectInput$1 = /*#__PURE__*/forwardRef(OverlaySelectInput);
356
+ var SearchSelectInput$1 = /*#__PURE__*/forwardRef(SearchSelectInput);
240
357
 
241
- export { OverlaySelectInput$1 as default };
358
+ export { SearchSelectInput$1 as default };
package/esm/index.js CHANGED
@@ -22,9 +22,9 @@ export { default as ItemList } from './core/ItemList/index.js';
22
22
  export { default as MotionView } from './core/MotionView/index.js';
23
23
  export { default as Overlay } from './core/Overlay/index.js';
24
24
  export { default as OverlayPopper } from './core/OverlayPopper/index.js';
25
- export { default as OverlaySelectInput } from './core/OverlaySelectInput/index.js';
26
25
  export { default as Pagination } from './core/Pagination/index.js';
27
26
  export { default as Pill } from './core/Pill/index.js';
27
+ export { default as SearchSelectInput } from './core/SearchSelectInput/index.js';
28
28
  export { default as Select } from './core/Select/index.js';
29
29
  export { default as Space } from './core/Space/index.js';
30
30
  export { default as Spinner } from './core/Spinner/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamturing/react-kit",
3
- "version": "2.19.13",
3
+ "version": "2.19.15",
4
4
  "description": "React components, hooks for create teamturing web application",
5
5
  "author": "Sungchang Park <psch300@gmail.com> (https://github.com/psch300)",
6
6
  "homepage": "https://github.com/weareteamturing/bombe#readme",
@@ -62,5 +62,5 @@
62
62
  "react-is": "^18.2.0",
63
63
  "styled-system": "^5.1.5"
64
64
  },
65
- "gitHead": "0f7b13b7bf46b81369e159ad9e89aa7f17ddc7c0"
65
+ "gitHead": "ee34d126fa668d99e7cf21ed73b8a38f32d77add"
66
66
  }
@@ -1,25 +0,0 @@
1
- import { ElementType, InputHTMLAttributes, ReactNode } from 'react';
2
- type Props = {
3
- /**
4
- * TODO asdf
5
- */
6
- validationStatus?: 'error' | 'success' | undefined;
7
- /**
8
- * 입력 창 앞에 보여질 시각적 요소를 정의합니다. Icon, Text, Image 등이 될 수 있습니다.
9
- */
10
- leadingVisual?: ElementType | ReactNode;
11
- } & InputHTMLAttributes<HTMLInputElement>;
12
- declare const _default: import("react").ForwardRefExoticComponent<{
13
- /**
14
- * TODO asdf
15
- */
16
- validationStatus?: "error" | "success" | undefined;
17
- /**
18
- * 입력 창 앞에 보여질 시각적 요소를 정의합니다. Icon, Text, Image 등이 될 수 있습니다.
19
- */
20
- leadingVisual?: string | number | boolean | import("react").ComponentClass<any, any> | import("react").FunctionComponent<any> | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | import("react").ReactPortal | null | undefined;
21
- } & InputHTMLAttributes<HTMLInputElement> & {
22
- children?: ReactNode;
23
- } & import("react").RefAttributes<HTMLInputElement>>;
24
- export default _default;
25
- export type { Props as OverlaySelectInputProps };