@pnkx-lib/ui 1.9.369 → 1.9.371

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.
@@ -1,4 +1,4 @@
1
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import React__default, { useRef, useState, useEffect, useLayoutEffect, useMemo, forwardRef, useCallback } from 'react';
3
3
  import { Input as Input$1 } from 'antd';
4
4
  import { g as get } from '../chunks/get-DPccfEM4.js';
@@ -1228,6 +1228,51 @@ function NumericFormat(props) {
1228
1228
  return React__default.createElement(NumberFormatBase, Object.assign({}, numericFormatProps));
1229
1229
  }
1230
1230
 
1231
+ const AllowClearIcon = ({
1232
+ size = 6,
1233
+ color = "currentColor",
1234
+ className,
1235
+ style,
1236
+ onClick,
1237
+ "aria-label": ariaLabel,
1238
+ title,
1239
+ ...props
1240
+ }) => /* @__PURE__ */ jsx(
1241
+ "div",
1242
+ {
1243
+ className,
1244
+ style: {
1245
+ width: size,
1246
+ height: size,
1247
+ cursor: onClick ? "pointer" : "default",
1248
+ ...style
1249
+ },
1250
+ onClick,
1251
+ role: onClick ? "button" : void 0,
1252
+ "aria-label": ariaLabel,
1253
+ title,
1254
+ ...props,
1255
+ children: /* @__PURE__ */ jsx(
1256
+ "svg",
1257
+ {
1258
+ xmlns: "http://www.w3.org/2000/svg",
1259
+ width: "100%",
1260
+ height: "100%",
1261
+ fill: "none",
1262
+ children: /* @__PURE__ */ jsx(
1263
+ "path",
1264
+ {
1265
+ fill: color || "#116DFF",
1266
+ fillRule: "evenodd",
1267
+ d: "M5.2 0 3 2.2.8 0 0 .8 2.2 3 0 5.3l.8.7L3 3.8 5.2 6l.8-.7L3.8 3 6 .8 5.2 0Z",
1268
+ clipRule: "evenodd"
1269
+ }
1270
+ )
1271
+ }
1272
+ )
1273
+ }
1274
+ );
1275
+
1231
1276
  const Input = (props) => {
1232
1277
  //! State
1233
1278
  const {
@@ -1246,6 +1291,7 @@ const Input = (props) => {
1246
1291
  classNameLabel,
1247
1292
  inputPassword,
1248
1293
  toUpperCaseValue,
1294
+ allowClear,
1249
1295
  ...restProps
1250
1296
  } = props;
1251
1297
  const { name, value, onChange, onBlur } = field || {};
@@ -1404,6 +1450,9 @@ const Input = (props) => {
1404
1450
  suffix: iconEndInput,
1405
1451
  className: twMerge("pnkx-default-input", classNameInput),
1406
1452
  status: (isTouched || isSubmitted) && errorMessage ? "error" : "",
1453
+ allowClear: allowClear ? {
1454
+ clearIcon: /* @__PURE__ */ jsx("div", { className: "allowClearIcon", children: /* @__PURE__ */ jsx(AllowClearIcon, { color: "#116DFF" }) })
1455
+ } : false,
1407
1456
  ...restProps
1408
1457
  }
1409
1458
  ),
@@ -0,0 +1,163 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useCallback } from 'react';
3
+ import { InputNumber } from 'antd';
4
+ import { g as get } from '../chunks/get-DPccfEM4.js';
5
+ import { ErrorMessage } from '../ui/ErrorMessage.js';
6
+ import { Label } from '../ui/Label.js';
7
+ import { t as twMerge } from '../chunks/bundle-mjs-BME7zF0Z.js';
8
+
9
+ const InputRangePicker = (props) => {
10
+ //! State
11
+ const {
12
+ label,
13
+ required,
14
+ field,
15
+ formState,
16
+ afterOnChange,
17
+ classNameContainer,
18
+ classNameInput,
19
+ classNameInputWrapper,
20
+ classNameLabel,
21
+ thousandSeparator = true,
22
+ placeholderFrom = "Từ",
23
+ placeholderTo = "Đến",
24
+ ...restProps
25
+ } = props;
26
+ const { name, value, onChange, onBlur } = field || {};
27
+ const { touchedFields, errors, isSubmitted } = formState || {};
28
+ const isTouched = get(touchedFields, name);
29
+ const errorMessage = get(errors, name)?.message;
30
+ const rangeValue = value;
31
+ const fromValue = rangeValue?.[0] ?? null;
32
+ const toValue = rangeValue?.[1] ?? null;
33
+ //! Function
34
+ const handleFromChange = useCallback(
35
+ (val) => {
36
+ const newValue = [
37
+ val,
38
+ toValue
39
+ ];
40
+ onChange?.(newValue);
41
+ afterOnChange?.(newValue);
42
+ },
43
+ [onChange, afterOnChange, toValue]
44
+ );
45
+ const handleToChange = useCallback(
46
+ (val) => {
47
+ const newValue = [
48
+ fromValue,
49
+ val
50
+ ];
51
+ onChange?.(newValue);
52
+ afterOnChange?.(newValue);
53
+ },
54
+ [onChange, afterOnChange, fromValue]
55
+ );
56
+ const handleBlur = useCallback(() => {
57
+ onBlur?.();
58
+ }, [onBlur]);
59
+ //! Render
60
+ const renderLabel = () => {
61
+ if (!label) return null;
62
+ return /* @__PURE__ */ jsx(
63
+ Label,
64
+ {
65
+ label,
66
+ required,
67
+ classNameLabel
68
+ }
69
+ );
70
+ };
71
+ const renderInput = () => {
72
+ return /* @__PURE__ */ jsxs(
73
+ "div",
74
+ {
75
+ className: twMerge(
76
+ "relative flex items-center border border-gray-300 rounded-lg bg-white hover:border-blue-400 focus-within:border-blue-500 focus-within:ring-1 focus-within:ring-blue-500 transition-colors",
77
+ (isTouched || isSubmitted) && errorMessage && "border-red-500 focus-within:border-red-500 focus-within:ring-red-500",
78
+ classNameInputWrapper
79
+ ),
80
+ children: [
81
+ /* @__PURE__ */ jsx(
82
+ InputNumber,
83
+ {
84
+ name: `${name}_from`,
85
+ value: fromValue,
86
+ onChange: handleFromChange,
87
+ onBlur: handleBlur,
88
+ placeholder: placeholderFrom,
89
+ className: twMerge("flex-1", classNameInput),
90
+ style: {
91
+ border: "none",
92
+ boxShadow: "none",
93
+ background: "transparent"
94
+ },
95
+ formatter: thousandSeparator ? (value2) => `${value2}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : void 0,
96
+ parser: thousandSeparator ? (value2) => value2.replace(/\$\s?|(,*)/g, "") : void 0,
97
+ controls: false,
98
+ ...restProps
99
+ }
100
+ ),
101
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center px-2 text-gray-400", children: /* @__PURE__ */ jsx(
102
+ "svg",
103
+ {
104
+ width: "20",
105
+ height: "20",
106
+ viewBox: "0 0 24 24",
107
+ fill: "none",
108
+ xmlns: "http://www.w3.org/2000/svg",
109
+ children: /* @__PURE__ */ jsx(
110
+ "path",
111
+ {
112
+ d: "M5 12H18M13 6L18.2929 11.2929C18.6834 11.6834 18.6834 12.3166 18.2929 12.7071L13 18",
113
+ stroke: "black",
114
+ "stroke-width": "1.5",
115
+ "stroke-linecap": "round"
116
+ }
117
+ )
118
+ }
119
+ ) }),
120
+ /* @__PURE__ */ jsx(
121
+ InputNumber,
122
+ {
123
+ name: `${name}_to`,
124
+ value: toValue,
125
+ onChange: handleToChange,
126
+ onBlur: handleBlur,
127
+ placeholder: placeholderTo,
128
+ className: twMerge("flex-1 ", classNameInput),
129
+ style: {
130
+ border: "none",
131
+ boxShadow: "none",
132
+ background: "transparent"
133
+ },
134
+ formatter: thousandSeparator ? (value2) => `${value2}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : void 0,
135
+ parser: thousandSeparator ? (value2) => value2.replace(/\$\s?|(,*)/g, "") : void 0,
136
+ controls: false,
137
+ max: 9999999999,
138
+ ...restProps
139
+ }
140
+ )
141
+ ]
142
+ }
143
+ );
144
+ };
145
+ const renderErrorMessage = () => {
146
+ if (!errorMessage) return null;
147
+ return /* @__PURE__ */ jsx(
148
+ ErrorMessage,
149
+ {
150
+ errorMessage,
151
+ isTouched,
152
+ isSubmitted
153
+ }
154
+ );
155
+ };
156
+ return /* @__PURE__ */ jsxs("div", { className: twMerge("w-full", classNameContainer), children: [
157
+ renderLabel(),
158
+ renderInput(),
159
+ renderErrorMessage()
160
+ ] });
161
+ };
162
+
163
+ export { InputRangePicker };
@@ -14,3 +14,4 @@ export { SliderRange } from './SliderRanger.js';
14
14
  export { TimeRangePicker } from './TimeRangePicker.js';
15
15
  export { TimePicker } from './TimePicker.js';
16
16
  export { Cascader } from './CascaderField.js';
17
+ export { InputRangePicker } from './InputRangePicker.js';
package/es/index.js CHANGED
@@ -79,5 +79,6 @@ export { SliderRange } from './fields/SliderRanger.js';
79
79
  export { TimeRangePicker } from './fields/TimeRangePicker.js';
80
80
  export { TimePicker } from './fields/TimePicker.js';
81
81
  export { Cascader } from './fields/CascaderField.js';
82
+ export { InputRangePicker } from './fields/InputRangePicker.js';
82
83
  export { useToast } from './hooks/useToast.js';
83
84
  export { useMessage } from './hooks/useMessage.js';
@@ -0,0 +1,127 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { Select, Table, Spin } from 'antd';
3
+ import { useState, useCallback } from 'react';
4
+
5
+ function SelectTable({
6
+ columns,
7
+ dataSource = [],
8
+ selectedKeys = [],
9
+ onSelectionChange,
10
+ onLoadMore,
11
+ onSearch,
12
+ hasMore = false,
13
+ loading = false,
14
+ rowKey = "key",
15
+ tableProps = {},
16
+ infiniteScroll = false,
17
+ loadMoreThreshold = 50,
18
+ searchPlaceholder = "Tìm kiếm...",
19
+ ...selectProps
20
+ }) {
21
+ const [open, setOpen] = useState(false);
22
+ const [internalDataSource] = useState(dataSource);
23
+ const [internalLoading, setInternalLoading] = useState(false);
24
+ const [searchValue, setSearchValue] = useState("");
25
+ const [isClearing, setIsClearing] = useState(false);
26
+ const currentDataSource = dataSource.length > 0 ? dataSource : internalDataSource;
27
+ const loadMoreData = useCallback(async () => {
28
+ if (internalLoading || loading || !hasMore || !onLoadMore) return;
29
+ setInternalLoading(true);
30
+ try {
31
+ await onLoadMore();
32
+ } catch (error) {
33
+ console.error("Error loading more data:", error);
34
+ } finally {
35
+ setInternalLoading(false);
36
+ }
37
+ }, [onLoadMore, internalLoading, loading, hasMore]);
38
+ const handleScroll = useCallback(
39
+ (e) => {
40
+ if (!infiniteScroll) return;
41
+ const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
42
+ if (scrollHeight - scrollTop <= clientHeight + loadMoreThreshold) {
43
+ loadMoreData();
44
+ }
45
+ },
46
+ [infiniteScroll, loadMoreThreshold, loadMoreData]
47
+ );
48
+ const onSelectChange = (newSelectedRowKeys, newSelectedRows) => {
49
+ if (onSelectionChange) {
50
+ onSelectionChange(newSelectedRowKeys, newSelectedRows);
51
+ }
52
+ };
53
+ const rowSelection = {
54
+ selectedRowKeys: selectedKeys,
55
+ onChange: onSelectChange
56
+ };
57
+ const getDisplayValue = useCallback(() => {
58
+ return [];
59
+ }, []);
60
+ const handleSearch = (value) => {
61
+ setSearchValue(value);
62
+ if (onSearch) {
63
+ onSearch(value);
64
+ }
65
+ };
66
+ const handleClear = () => {
67
+ setIsClearing(true);
68
+ setSearchValue("");
69
+ if (onSearch) {
70
+ onSearch("");
71
+ }
72
+ setTimeout(() => {
73
+ setOpen(true);
74
+ setIsClearing(false);
75
+ }, 10);
76
+ };
77
+ const handleDropdownVisibleChange = (visible) => {
78
+ if (isClearing && !visible) {
79
+ return;
80
+ }
81
+ setOpen(visible);
82
+ };
83
+ const dropdownRender = () => /* @__PURE__ */ jsx("div", { className: "select-table-dropdown", children: /* @__PURE__ */ jsxs("div", { className: "overflow-x-hidden w-full", children: [
84
+ /* @__PURE__ */ jsx(
85
+ Table,
86
+ {
87
+ className: "select-table-custom",
88
+ rowSelection,
89
+ columns,
90
+ dataSource: currentDataSource,
91
+ rowKey,
92
+ size: tableProps.size || "small",
93
+ pagination: false,
94
+ scroll: { y: 500 },
95
+ style: { overflowX: "hidden" },
96
+ loading: false,
97
+ onScroll: handleScroll,
98
+ ...tableProps
99
+ }
100
+ ),
101
+ (internalLoading || loading) && /* @__PURE__ */ jsxs("div", { className: "text-center py-2 border-t border-gray-200", children: [
102
+ /* @__PURE__ */ jsx(Spin, { size: "small" }),
103
+ " Đang tải thêm..."
104
+ ] })
105
+ ] }) });
106
+ return /* @__PURE__ */ jsx(
107
+ Select,
108
+ {
109
+ ...selectProps,
110
+ open,
111
+ onDropdownVisibleChange: handleDropdownVisibleChange,
112
+ dropdownRender,
113
+ value: getDisplayValue(),
114
+ mode: "multiple",
115
+ showSearch: true,
116
+ onSearch: handleSearch,
117
+ onClear: handleClear,
118
+ placeholder: searchPlaceholder,
119
+ maxTagCount: 0,
120
+ showArrow: true,
121
+ filterOption: false,
122
+ searchValue
123
+ }
124
+ );
125
+ }
126
+
127
+ export { SelectTable as default };