@fe-free/core 2.7.0 → 2.8.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @fe-free/core
2
2
 
3
+ ## 2.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - feat: className
8
+
9
+ ### Patch Changes
10
+
11
+ - @fe-free/tool@2.8.0
12
+
13
+ ## 2.7.1
14
+
15
+ ### Patch Changes
16
+
17
+ - feat: record
18
+ - @fe-free/tool@2.7.1
19
+
3
20
  ## 2.7.0
4
21
 
5
22
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/core",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "",
5
5
  "main": "./src/index.ts",
6
6
  "author": "",
@@ -41,7 +41,7 @@
41
41
  "remark-gfm": "^4.0.1",
42
42
  "vanilla-jsoneditor": "^0.23.1",
43
43
  "zustand": "^4.5.4",
44
- "@fe-free/tool": "2.7.0"
44
+ "@fe-free/tool": "2.8.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@ant-design/pro-components": "2.8.9",
@@ -22,7 +22,7 @@ CRUDOfList 组件。(简洁的列表形态的 CRUD 组件)
22
22
  decorators: [
23
23
  (Story) => {
24
24
  return (
25
- <div className="c-border h-[500px] w-[300px] overflow-y-auto">
25
+ <div className="fec-border h-[500px] w-[300px] overflow-y-auto">
26
26
  <Story />
27
27
  </div>
28
28
  );
@@ -27,11 +27,11 @@ interface EditorMentionProps {
27
27
  }
28
28
 
29
29
  const emptyArr = [];
30
- const tagClassName = 'cl-editor-mention-tag';
30
+ const tagClassName = 'fec-editor-mention-tag';
31
31
  const defaultPrefix = ['/'];
32
32
 
33
33
  function defaultRenderItem({ item }) {
34
- return <div className="py-1 text-sm pl-4 pr-2">{item.label}</div>;
34
+ return <div className="py-1 pl-4 pr-2 text-sm">{item.label}</div>;
35
35
  }
36
36
 
37
37
  function defaultRenderTagHTML({ item }) {
@@ -104,16 +104,16 @@ function useDropdown({ items, renderItem, onSelect, editorRef }) {
104
104
  <div
105
105
  // 阻止下拉菜单点击事件冒泡
106
106
  onClick={(e) => e.stopPropagation()}
107
- className="shadow-md rounded p-1 z-10 max-h-[250px] overflow-y-auto bg-white absolute max-w-[250px]"
107
+ className="absolute z-10 max-h-[250px] max-w-[250px] overflow-y-auto rounded bg-white p-1 shadow-md"
108
108
  style={{
109
109
  top: `${position.top}px`,
110
110
  left: `${position.left}px`,
111
111
  }}
112
112
  >
113
- {items.length === 0 && <div className="text-desc text-sm p-2">暂无数据</div>}
113
+ {items.length === 0 && <div className="p-2 text-sm text-desc">暂无数据</div>}
114
114
  {items.map((group, i) => (
115
115
  <div key={group.label}>
116
- <div className="text-desc text-sm p-2">{group.label}</div>
116
+ <div className="p-2 text-sm text-desc">{group.label}</div>
117
117
  <div>
118
118
  {group.options.map((item) => (
119
119
  <div
@@ -122,7 +122,7 @@ function useDropdown({ items, renderItem, onSelect, editorRef }) {
122
122
  e.preventDefault(); // 防止失去焦点
123
123
  handleSelect(item);
124
124
  }}
125
- className="cursor-pointer hover:bg-gray-100 rounded"
125
+ className="cursor-pointer rounded hover:bg-gray-100"
126
126
  >
127
127
  {renderItem({ item, index: i })}
128
128
  </div>
@@ -200,7 +200,7 @@ function useChange({ content, editorRef, onChange }) {
200
200
  const node = (
201
201
  <div
202
202
  ref={ref}
203
- className="absolute top-[-10px] left-[-10px] w-[1px] h-[1px] overflow-auto"
203
+ className="absolute left-[-10px] top-[-10px] h-[1px] w-[1px] overflow-auto"
204
204
  contentEditable="true"
205
205
  />
206
206
  );
@@ -324,7 +324,7 @@ const EditorMention = ({
324
324
  const isEmpty = !content || content === '<br>';
325
325
 
326
326
  return (
327
- <div className="c-editor-mention relative h-full flex">
327
+ <div className="c-editor-mention relative flex h-full">
328
328
  <div
329
329
  ref={editorRef}
330
330
  contentEditable="true"
@@ -332,7 +332,7 @@ const EditorMention = ({
332
332
  onInput={handleInput}
333
333
  onKeyDown={handleKeyDown}
334
334
  className={classNames(
335
- 'flex-1 w-full p-2 c-border rounded outline-none overflow-y-auto focus:border-primary',
335
+ 'fec-border w-full flex-1 overflow-y-auto rounded p-2 outline-none focus:border-primary',
336
336
  resizeHeight && 'resize-y',
337
337
  )}
338
338
  style={{
@@ -341,7 +341,7 @@ const EditorMention = ({
341
341
  />
342
342
  {placeholder && isEmpty && (
343
343
  <div
344
- className="absolute top-0 left-0 text-gray-500 p-2 cursor-text"
344
+ className="absolute left-0 top-0 cursor-text p-2 text-gray-500"
345
345
  onClick={() => {
346
346
  editorRef.current?.focus();
347
347
  }}
@@ -244,11 +244,15 @@ export const ProFormRecordComponent: Story = {
244
244
  name="record1"
245
245
  label="record1"
246
246
  fieldProps={{
247
- defaultItems: [
247
+ labels: [
248
248
  { key: 'username', label: '用户名' },
249
249
  { key: 'password', label: '密码' },
250
250
  ],
251
251
  }}
252
+ initialValue={{
253
+ username: '',
254
+ password: '',
255
+ }}
252
256
  />
253
257
  <ProFormRecord name="record2" label="record2" />
254
258
  </ProFormBase>
@@ -262,11 +266,15 @@ export const ProFormRecordArrayComponent: Story = {
262
266
  name="recordArray1"
263
267
  label="recordArray1"
264
268
  fieldProps={{
265
- defaultItems: [
269
+ labels: [
266
270
  { key: 'username', label: '用户名' },
267
271
  { key: 'password', label: '密码' },
268
272
  ],
269
273
  }}
274
+ initialValue={[
275
+ { key: 'username', value: undefined },
276
+ { key: 'password', value: undefined },
277
+ ]}
270
278
  />
271
279
  <ProFormRecordArray name="recordArray2" label="recordArray2" />
272
280
  </ProFormBase>
@@ -1,5 +1,6 @@
1
1
  import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
2
2
  import { Button } from 'antd';
3
+ import classNames from 'classnames';
3
4
 
4
5
  interface ProFormListHelperProps<T> {
5
6
  value?: T[];
@@ -12,7 +13,10 @@ interface ProFormListHelperProps<T> {
12
13
  }) => React.ReactNode;
13
14
  addText?: string;
14
15
  getAdd: () => T;
16
+ disabledAdd?: boolean;
17
+ disabledDelete?: boolean;
15
18
  readOnly?: boolean;
19
+ className?: string;
16
20
  }
17
21
 
18
22
  const emptyArr = [];
@@ -20,7 +24,7 @@ function ProFormListHelper<T = any>(props: ProFormListHelperProps<T>) {
20
24
  const options = props.value || emptyArr;
21
25
 
22
26
  return (
23
- <div className="flex flex-col gap-2">
27
+ <div className={classNames('flex flex-col gap-2', props.className)}>
24
28
  <div className="flex flex-col gap-2">
25
29
  {options.map((item, index) => {
26
30
  return (
@@ -37,7 +41,7 @@ function ProFormListHelper<T = any>(props: ProFormListHelperProps<T>) {
37
41
  },
38
42
  })}
39
43
  </div>
40
- {!props.readOnly && (
44
+ {!props.readOnly && !props.disabledDelete && (
41
45
  <Button
42
46
  icon={<DeleteOutlined />}
43
47
  type="text"
@@ -52,7 +56,7 @@ function ProFormListHelper<T = any>(props: ProFormListHelperProps<T>) {
52
56
  );
53
57
  })}
54
58
  </div>
55
- {!props.readOnly && (
59
+ {!props.readOnly && !props.disabledAdd && (
56
60
  <div className="flex justify-center">
57
61
  <Button
58
62
  size="small"
@@ -7,7 +7,22 @@ function ProFormRecord(props: ProFormItemProps<RecordProps>) {
7
7
  const { fieldProps, ...rest } = props;
8
8
 
9
9
  return (
10
- <ProForm.Item {...rest}>
10
+ <ProForm.Item
11
+ {...rest}
12
+ rules={[
13
+ ...(props.rules || []),
14
+ {
15
+ validator: async (_, value) => {
16
+ const values = Object.values(value || {});
17
+ if (values.length && values.filter(Boolean).length !== values.length) {
18
+ return Promise.reject('选项不能为空');
19
+ }
20
+
21
+ return Promise.resolve();
22
+ },
23
+ },
24
+ ]}
25
+ >
11
26
  <Record {...(fieldProps as RecordProps)} />
12
27
  </ProForm.Item>
13
28
  );
@@ -17,7 +32,22 @@ function ProFormRecordArray(props: ProFormItemProps<RecordArrayProps>) {
17
32
  const { fieldProps, ...rest } = props;
18
33
 
19
34
  return (
20
- <ProForm.Item {...rest}>
35
+ <ProForm.Item
36
+ {...rest}
37
+ rules={[
38
+ ...(props.rules || []),
39
+ {
40
+ validator: async (_, value) => {
41
+ console.log('value', value);
42
+ if (value?.some((item) => item.value === undefined || item.key === undefined)) {
43
+ return Promise.reject('选项不能为空');
44
+ }
45
+
46
+ return Promise.resolve();
47
+ },
48
+ },
49
+ ]}
50
+ >
21
51
  <RecordArray {...(fieldProps as RecordArrayProps)} />
22
52
  </ProForm.Item>
23
53
  );
@@ -1,6 +1,7 @@
1
- import { Button, Divider, Input } from 'antd';
1
+ import { Divider, Input } from 'antd';
2
2
  import classNames from 'classnames';
3
- import { Fragment, useCallback, useMemo, useState } from 'react';
3
+ import { useCallback, useMemo } from 'react';
4
+ import { ProFormListHelper } from '../form';
4
5
 
5
6
  function Item({
6
7
  value,
@@ -8,7 +9,7 @@ function Item({
8
9
  onChange,
9
10
  }: {
10
11
  value?: string;
11
- label: string;
12
+ label?: string;
12
13
  onChange: (value: string) => void;
13
14
  }) {
14
15
  return (
@@ -31,20 +32,20 @@ function Item({
31
32
  interface RecordItem {
32
33
  value?: { key?: string; value?: string };
33
34
  onChange: (value: { key?: string; value?: string }) => void;
34
- item: { key?: string; label?: string; placeholder?: string };
35
+ label?: string;
35
36
  }
36
37
 
37
38
  function RecordItem(props: RecordItem) {
38
- const { value, onChange, item } = props;
39
+ const { value, onChange, label } = props;
39
40
 
40
- // 如果提供了 key
41
- if (item.key) {
41
+ // 如果提供了 label
42
+ if (label) {
42
43
  return (
43
44
  <Item
44
45
  value={value?.value}
45
- label={item.label || item.key}
46
+ label={label || value?.key}
46
47
  onChange={(v) => {
47
- onChange({ key: item.key, value: v });
48
+ onChange({ key: value?.key, value: v });
48
49
  }}
49
50
  />
50
51
  );
@@ -73,45 +74,42 @@ function RecordItem(props: RecordItem) {
73
74
  interface RecordArrayProps {
74
75
  value?: { key?: string; value?: string }[];
75
76
  onChange: (value: { key?: string; value?: string }[]) => void;
76
- defaultItems?: { key: string; label: string }[];
77
+ labels?: { key?: string; label?: string }[];
77
78
  className?: string;
78
79
  }
79
80
 
80
81
  function RecordArray(props: RecordArrayProps) {
81
- const { value, onChange, defaultItems, className } = props;
82
- const [items, setItems] = useState<{ name?: string; label?: string }[]>(defaultItems || [{}]);
82
+ const { value, onChange, labels, className } = props;
83
+
84
+ const isFixed = !!labels?.length;
83
85
 
84
86
  return (
85
- <div className={classNames('c-bg flex flex-col gap-2 rounded p-2', className)}>
86
- {(items || [{}])?.map((item, index) => {
87
- const v = value?.[index] || {};
87
+ <ProFormListHelper
88
+ className={classNames('fec-bg rounded p-2', className)}
89
+ value={value}
90
+ onChange={onChange}
91
+ getAdd={() => ({
92
+ key: undefined,
93
+ value: undefined,
94
+ })}
95
+ disabledDelete={isFixed}
96
+ disabledAdd={isFixed}
97
+ >
98
+ {({ item, index, onItemChange }) => {
88
99
  return (
89
- <Fragment key={index}>
100
+ <>
90
101
  <RecordItem
91
- item={item}
92
- value={v}
102
+ value={item}
103
+ label={labels?.[index]?.label}
93
104
  onChange={(v) => {
94
- const newValues = [...(value || [])];
95
- newValues[index] = v || {};
96
- onChange(newValues);
105
+ onItemChange(v);
97
106
  }}
98
107
  />
99
- {!defaultItems && <Divider className="my-2" />}
100
- </Fragment>
108
+ {!isFixed && <Divider className="mb-2" />}
109
+ </>
101
110
  );
102
- })}
103
- {!defaultItems && (
104
- <div className="flex flex-col items-center">
105
- <Button
106
- onClick={() => {
107
- setItems((v) => [...v, {}]);
108
- }}
109
- >
110
- 添加
111
- </Button>
112
- </div>
113
- )}
114
- </div>
111
+ }}
112
+ </ProFormListHelper>
115
113
  );
116
114
  }
117
115
 
@@ -121,8 +119,9 @@ interface RecordProps extends Omit<RecordArrayProps, 'value' | 'onChange'> {
121
119
  }
122
120
 
123
121
  function Record(props: RecordProps) {
124
- const { value, onChange, defaultItems, className } = props;
122
+ const { value, onChange, ...rest } = props;
125
123
 
124
+ // obj 和 arr 的转换
126
125
  const newValue = useMemo(() => {
127
126
  return Object.keys(value || {}).map((key) => ({
128
127
  key,
@@ -130,13 +129,13 @@ function Record(props: RecordProps) {
130
129
  }));
131
130
  }, [value]);
132
131
 
132
+ // obj 和 arr 的转换
133
133
  const newOnChange = useCallback(
134
134
  (newV: { key?: string; value?: string }[]) => {
135
135
  const newValues = {};
136
- newV.forEach((item) => {
137
- if (item.key) {
138
- newValues[item.key] = item.value;
139
- }
136
+ newV.forEach((item, index) => {
137
+ // 确保有值
138
+ newValues[item.key || index] = item.value;
140
139
  });
141
140
 
142
141
  onChange(newValues);
@@ -144,14 +143,7 @@ function Record(props: RecordProps) {
144
143
  [onChange],
145
144
  );
146
145
 
147
- return (
148
- <RecordArray
149
- value={newValue}
150
- onChange={newOnChange}
151
- defaultItems={defaultItems}
152
- className={className}
153
- />
154
- );
146
+ return <RecordArray value={newValue} onChange={newOnChange} {...rest} />;
155
147
  }
156
148
 
157
149
  export { Record, RecordArray };
package/src/style.scss CHANGED
@@ -8,31 +8,31 @@
8
8
  /* stylelint-disable-next-line at-rule-no-unknown */
9
9
  @tailwind utilities;
10
10
 
11
- .c-border {
11
+ .fec-border {
12
12
  border: 1px solid #f0f0f0;
13
13
  }
14
14
 
15
- .c-border-bottom {
15
+ .fec-border-bottom {
16
16
  border-bottom: 1px solid #f0f0f0;
17
17
  }
18
18
 
19
- .c-border-top {
19
+ .fec-border-top {
20
20
  border-top: 1px solid #f0f0f0;
21
21
  }
22
22
 
23
- .c-border-left {
23
+ .fec-border-left {
24
24
  border-left: 1px solid #f0f0f0;
25
25
  }
26
26
 
27
- .c-border-right {
27
+ .fec-border-right {
28
28
  border-right: 1px solid #f0f0f0;
29
29
  }
30
30
 
31
- .c-bg {
31
+ .fec-bg {
32
32
  background-color: #f3f5f6;
33
33
  }
34
34
 
35
- .c-bg-hover {
35
+ .fec-bg-hover {
36
36
  background-color: #f3f5f6;
37
37
 
38
38
  &:hover {
@@ -13,12 +13,12 @@ export const Default = () => {
13
13
  <div className="text-desc">text-desc</div>
14
14
  </div>
15
15
  <div>
16
- <div className="c-border-bottom">c-border-bottom</div>
17
- <div className="c-border-top">c-border-top</div>
18
- <div className="c-border-left">c-border-left</div>
19
- <div className="c-border-right">c-border-right</div>
20
- <div className="c-bg">c-bg</div>
21
- <div className="c-bg-hover">c-bg-hover</div>
16
+ <div className="fec-border-bottom">fec-border-bottom</div>
17
+ <div className="fec-border-top">fec-border-top</div>
18
+ <div className="fec-border-left">fec-border-left</div>
19
+ <div className="fec-border-right">fec-border-right</div>
20
+ <div className="fec-bg">fec-bg</div>
21
+ <div className="fec-bg-hover">fec-bg-hover</div>
22
22
  </div>
23
23
  </div>
24
24
  );
@@ -8,7 +8,7 @@ const meta: Meta<typeof FileTree> = {
8
8
  decorators: [
9
9
  (Story) => {
10
10
  return (
11
- <div className="c-border w-[200px] overflow-y-auto">
11
+ <div className="fec-border w-[200px] overflow-y-auto">
12
12
  <Story />
13
13
  </div>
14
14
  );
@@ -183,7 +183,7 @@ function FileTree<D extends DataNode>(props: FileTreeProps<D>) {
183
183
  treeProps={{
184
184
  titleRender,
185
185
  ...props.treeProps,
186
- className: classNames('cl-file-tree', props.treeProps?.className),
186
+ className: classNames('fec-file-tree', props.treeProps?.className),
187
187
  }}
188
188
  />
189
189
  );
@@ -1,5 +1,5 @@
1
- .cl-tree {
2
- &.cl-tree-no-wrap {
1
+ .fec-tree {
2
+ &.fec-tree-no-wrap {
3
3
  .ant-tree-title {
4
4
  white-space: nowrap;
5
5
  }
@@ -10,13 +10,13 @@
10
10
  }
11
11
  }
12
12
 
13
- .cl-file-tree {
13
+ .fec-file-tree {
14
14
  .ant-tree-node-content-wrapper {
15
15
  overflow: hidden;
16
16
  padding-inline-start: 0 !important;
17
17
  }
18
18
 
19
- &.cl-tree-all-leaf {
19
+ &.fec-tree-all-leaf {
20
20
  .ant-tree-switcher {
21
21
  display: none;
22
22
  }
@@ -7,7 +7,7 @@ const meta: Meta<typeof Tree> = {
7
7
  tags: ['autodocs'],
8
8
  decorators: [
9
9
  (Story) => (
10
- <div className="c-border w-[300px]">
10
+ <div className="fec-border w-[300px]">
11
11
  <Story />
12
12
  </div>
13
13
  ),
package/src/tree/tree.tsx CHANGED
@@ -157,9 +157,9 @@ function Tree<T extends DataNode>(props: TreeProps<T>) {
157
157
  {...treeProps}
158
158
  treeData={newTreeData}
159
159
  className={classNames(
160
- 'cl-tree cl-tree-no-wrap',
160
+ 'fec-tree fec-tree-no-wrap',
161
161
  {
162
- 'cl-tree-all-leaf': isAllLeaf,
162
+ 'fec-tree-all-leaf': isAllLeaf,
163
163
  },
164
164
  treeProps?.className,
165
165
  )}