samlax12 commited on
Commit
eca996b
·
verified ·
1 Parent(s): 4fc8eae

Update src/components/Category/CategorySelector.tsx

Browse files
src/components/Category/CategorySelector.tsx CHANGED
@@ -1,96 +1,149 @@
1
- import React, { useState } from 'react';
2
- import { Category } from '../../types';
3
- import { useApp } from '../../contexts/AppContext';
4
- import CategoryBadge from './CategoryBadge';
5
-
6
- interface CategorySelectorProps {
7
- selectedCategory: string | Category;
8
- onChange: (categoryId: string) => void;
9
- className?: string;
10
- }
11
-
12
-
13
- const CategorySelector: React.FC<CategorySelectorProps> = ({
14
- selectedCategory,
15
- onChange,
16
- className = ''
17
- }) => {
18
- const { categories } = useApp();
19
- const [showDropdown, setShowDropdown] = useState(false);
20
-
21
- const selectedCategoryObj = categories.find(c => c._id === selectedCategory);
22
-
23
- const handleCategorySelect = (categoryId: string) => {
24
- onChange(categoryId);
25
- setShowDropdown(false);
26
- };
27
-
28
- return (
29
- <div className={`relative ${className}`}>
30
- <div
31
- className="flex items-center cursor-pointer"
32
- onClick={() => setShowDropdown(!showDropdown)}
33
- >
34
- {selectedCategoryObj ? (
35
- <CategoryBadge category={selectedCategoryObj} />
36
- ) : (
37
- <div className="ios-tag" style={{ backgroundColor: '#f0f0f0', color: '#666' }}>
38
- 选择分类
39
- </div>
40
- )}
41
- <svg
42
- xmlns="http://www.w3.org/2000/svg"
43
- width="16"
44
- height="16"
45
- viewBox="0 0 24 24"
46
- fill="none"
47
- stroke="currentColor"
48
- strokeWidth="2"
49
- strokeLinecap="round"
50
- strokeLinejoin="round"
51
- className="ml-1"
52
- >
53
- <polyline points="6 9 12 15 18 9"></polyline>
54
- </svg>
55
- </div>
56
-
57
- {showDropdown && (
58
- <div className="absolute z-10 mt-1 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5">
59
- <div className="py-1" role="menu" aria-orientation="vertical">
60
- {categories.map((category) => (
61
- <div
62
- key={category._id}
63
- className="px-4 py-2 text-sm hover:bg-gray-100 cursor-pointer flex items-center"
64
- onClick={() => handleCategorySelect(category._id)}
65
- >
66
- <div
67
- className="w-3 h-3 rounded-full mr-2"
68
- style={{ backgroundColor: category.color }}
69
- ></div>
70
- {category.name}
71
- {selectedCategory === category._id && (
72
- <svg
73
- xmlns="http://www.w3.org/2000/svg"
74
- width="16"
75
- height="16"
76
- viewBox="0 0 24 24"
77
- fill="none"
78
- stroke="currentColor"
79
- strokeWidth="2"
80
- strokeLinecap="round"
81
- strokeLinejoin="round"
82
- className="ml-auto"
83
- >
84
- <polyline points="20 6 9 17 4 12"></polyline>
85
- </svg>
86
- )}
87
- </div>
88
- ))}
89
- </div>
90
- </div>
91
- )}
92
- </div>
93
- );
94
- };
95
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  export default CategorySelector;
 
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { Category } from '../../types';
3
+ import { useApp } from '../../contexts/AppContext';
4
+ import CategoryBadge from './CategoryBadge';
5
+ import ReactDOM from 'react-dom';
6
+
7
+ interface CategorySelectorProps {
8
+ selectedCategory: string | Category;
9
+ onChange: (categoryId: string) => void;
10
+ className?: string;
11
+ }
12
+
13
+ const CategorySelector: React.FC<CategorySelectorProps> = ({
14
+ selectedCategory,
15
+ onChange,
16
+ className = ''
17
+ }) => {
18
+ const { categories } = useApp();
19
+ const [showDropdown, setShowDropdown] = useState(false);
20
+ const selectorRef = useRef<HTMLDivElement>(null);
21
+
22
+ const selectedCategoryObj = categories.find(c =>
23
+ typeof selectedCategory === 'string'
24
+ ? c._id === selectedCategory
25
+ : c._id === selectedCategory._id
26
+ );
27
+
28
+ // 点击外部关闭下拉菜单
29
+ useEffect(() => {
30
+ const handleClickOutside = (event: MouseEvent) => {
31
+ if (selectorRef.current && !selectorRef.current.contains(event.target as Node)) {
32
+ setShowDropdown(false);
33
+ }
34
+ };
35
+
36
+ if (showDropdown) {
37
+ document.addEventListener('mousedown', handleClickOutside);
38
+ }
39
+
40
+ return () => {
41
+ document.removeEventListener('mousedown', handleClickOutside);
42
+ };
43
+ }, [showDropdown]);
44
+
45
+ // 切换下拉菜单显示状态
46
+ const toggleDropdown = () => {
47
+ setShowDropdown(!showDropdown);
48
+ };
49
+
50
+ const handleCategorySelect = (categoryId: string) => {
51
+ onChange(categoryId);
52
+ setShowDropdown(false);
53
+ };
54
+
55
+ // 使用Portal将下拉菜单渲染到body
56
+ const renderDropdown = () => {
57
+ if (!showDropdown || !selectorRef.current) return null;
58
+
59
+ const rect = selectorRef.current.getBoundingClientRect();
60
+
61
+ return ReactDOM.createPortal(
62
+ <div
63
+ style={{
64
+ position: 'absolute',
65
+ zIndex: 9999,
66
+ width: '200px',
67
+ maxHeight: '300px',
68
+ overflowY: 'auto',
69
+ top: `${rect.bottom + window.scrollY}px`,
70
+ left: `${rect.left + window.scrollX}px`,
71
+ backgroundColor: 'white',
72
+ borderRadius: '0.375rem',
73
+ boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
74
+ border: '1px solid rgba(0, 0, 0, 0.05)'
75
+ }}
76
+ >
77
+ <div className="py-1" role="menu" aria-orientation="vertical">
78
+ {categories.map((category) => (
79
+ <div
80
+ key={category._id}
81
+ className="px-4 py-2 text-sm hover:bg-gray-100 cursor-pointer flex items-center"
82
+ onClick={() => handleCategorySelect(category._id)}
83
+ >
84
+ <div
85
+ className="w-3 h-3 rounded-full mr-2"
86
+ style={{ backgroundColor: category.color }}
87
+ ></div>
88
+ {category.name}
89
+ {(typeof selectedCategory === 'string' ?
90
+ selectedCategory === category._id :
91
+ selectedCategory._id === category._id) && (
92
+ <svg
93
+ xmlns="http://www.w3.org/2000/svg"
94
+ width="16"
95
+ height="16"
96
+ viewBox="0 0 24 24"
97
+ fill="none"
98
+ stroke="currentColor"
99
+ strokeWidth="2"
100
+ strokeLinecap="round"
101
+ strokeLinejoin="round"
102
+ className="ml-auto"
103
+ >
104
+ <polyline points="20 6 9 17 4 12"></polyline>
105
+ </svg>
106
+ )}
107
+ </div>
108
+ ))}
109
+ </div>
110
+ </div>,
111
+ document.body
112
+ );
113
+ };
114
+
115
+ return (
116
+ <div ref={selectorRef} className={`relative ${className}`}>
117
+ <div
118
+ className="flex items-center cursor-pointer"
119
+ onClick={toggleDropdown}
120
+ >
121
+ {selectedCategoryObj ? (
122
+ <CategoryBadge category={selectedCategoryObj} />
123
+ ) : (
124
+ <div className="ios-tag" style={{ backgroundColor: '#f0f0f0', color: '#666' }}>
125
+ 选择分类
126
+ </div>
127
+ )}
128
+ <svg
129
+ xmlns="http://www.w3.org/2000/svg"
130
+ width="16"
131
+ height="16"
132
+ viewBox="0 0 24 24"
133
+ fill="none"
134
+ stroke="currentColor"
135
+ strokeWidth="2"
136
+ strokeLinecap="round"
137
+ strokeLinejoin="round"
138
+ className="ml-1"
139
+ >
140
+ <polyline points="6 9 12 15 18 9"></polyline>
141
+ </svg>
142
+ </div>
143
+
144
+ {renderDropdown()}
145
+ </div>
146
+ );
147
+ };
148
+
149
  export default CategorySelector;