@delightui/components 0.1.104 → 0.1.106
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/README.md +104 -1
- package/dist/cjs/components/molecules/Modal/DemoModal.d.ts +8 -0
- package/dist/cjs/components/molecules/Modal/ModalContext/ModalContext.d.ts +41 -0
- package/dist/cjs/components/molecules/Modal/ModalContext/ModalContext.types.d.ts +87 -0
- package/dist/cjs/components/molecules/Modal/ModalContext/index.d.ts +3 -0
- package/dist/cjs/components/molecules/Modal/ModalContext/useModal.d.ts +34 -0
- package/dist/cjs/components/molecules/Modal/index.d.ts +2 -0
- package/dist/cjs/components/molecules/Popover/Popover.presenter.d.ts +26 -0
- package/dist/cjs/components/molecules/Select/Option/Option.types.d.ts +6 -0
- package/dist/cjs/components/molecules/Select/Select.Context.d.ts +1 -1
- package/dist/cjs/components/molecules/Select/Select.d.ts +5 -5
- package/dist/cjs/components/molecules/Select/Select.presenter.d.ts +1 -0
- package/dist/cjs/components/molecules/Select/Select.types.d.ts +5 -0
- package/dist/cjs/components/molecules/Select/index.d.ts +2 -9
- package/dist/cjs/components/molecules/index.d.ts +2 -0
- package/dist/cjs/components/utils/accessibilityUtils.d.ts +41 -0
- package/dist/cjs/components/utils/index.d.ts +2 -0
- package/dist/cjs/library.css +13 -0
- package/dist/cjs/library.js +2 -2
- package/dist/cjs/library.js.map +1 -1
- package/dist/esm/components/molecules/Modal/DemoModal.d.ts +8 -0
- package/dist/esm/components/molecules/Modal/ModalContext/ModalContext.d.ts +41 -0
- package/dist/esm/components/molecules/Modal/ModalContext/ModalContext.types.d.ts +87 -0
- package/dist/esm/components/molecules/Modal/ModalContext/index.d.ts +3 -0
- package/dist/esm/components/molecules/Modal/ModalContext/useModal.d.ts +34 -0
- package/dist/esm/components/molecules/Modal/index.d.ts +2 -0
- package/dist/esm/components/molecules/Popover/Popover.presenter.d.ts +26 -0
- package/dist/esm/components/molecules/Select/Option/Option.types.d.ts +6 -0
- package/dist/esm/components/molecules/Select/Select.Context.d.ts +1 -1
- package/dist/esm/components/molecules/Select/Select.d.ts +5 -5
- package/dist/esm/components/molecules/Select/Select.presenter.d.ts +1 -0
- package/dist/esm/components/molecules/Select/Select.types.d.ts +5 -0
- package/dist/esm/components/molecules/Select/index.d.ts +2 -9
- package/dist/esm/components/molecules/index.d.ts +2 -0
- package/dist/esm/components/utils/accessibilityUtils.d.ts +41 -0
- package/dist/esm/components/utils/index.d.ts +2 -0
- package/dist/esm/library.css +13 -0
- package/dist/esm/library.js +3 -3
- package/dist/esm/library.js.map +1 -1
- package/dist/index.d.ts +156 -12
- package/docs/README.md +264 -0
- package/docs/components/atoms/ActionImage.md +119 -0
- package/docs/components/atoms/Button.md +197 -0
- package/docs/components/atoms/Checkbox.md +299 -0
- package/docs/components/atoms/CheckboxItem.md +314 -0
- package/docs/components/atoms/Chip.md +380 -0
- package/docs/components/atoms/CustomToggle.md +270 -0
- package/docs/components/atoms/Icon.md +365 -0
- package/docs/components/atoms/IconButton.md +407 -0
- package/docs/components/atoms/Image.md +448 -0
- package/docs/components/atoms/Input.md +430 -0
- package/docs/components/atoms/ListItem.md +502 -0
- package/docs/components/atoms/Password.md +472 -0
- package/docs/components/atoms/RadioButton.md +614 -0
- package/docs/components/atoms/RadioButtonItem.md +588 -0
- package/docs/components/atoms/ResponsiveComponent.md +612 -0
- package/docs/components/atoms/SelectListItem.md +609 -0
- package/docs/components/atoms/Slider.md +605 -0
- package/docs/components/atoms/Spinner.md +605 -0
- package/docs/components/atoms/Text.md +463 -0
- package/docs/components/atoms/TextArea.md +670 -0
- package/docs/components/atoms/ToastNotification.md +668 -0
- package/docs/components/atoms/Toggle.md +737 -0
- package/docs/components/atoms/ToggleButton.md +751 -0
- package/docs/components/atoms/Tooltip.md +391 -0
- package/docs/components/molecules/Accordion.md +440 -0
- package/docs/components/molecules/AccordionGroup.md +547 -0
- package/docs/components/molecules/ActionCard.md +546 -0
- package/docs/components/molecules/Breadcrumb.md +403 -0
- package/docs/components/molecules/Breadcrumbs.md +485 -0
- package/docs/components/molecules/ButtonGroup.md +383 -0
- package/docs/components/molecules/Card.md +298 -0
- package/docs/components/molecules/ChipInput.md +646 -0
- package/docs/components/molecules/ContextMenu.md +768 -0
- package/docs/components/molecules/CustomTimeSelector.md +116 -0
- package/docs/components/molecules/DatePicker.md +516 -0
- package/docs/components/molecules/DateTimeSelector.md +166 -0
- package/docs/components/molecules/FormField.md +312 -0
- package/docs/components/molecules/Grid.md +577 -0
- package/docs/components/molecules/GridItem.md +834 -0
- package/docs/components/molecules/GridList.md +244 -0
- package/docs/components/molecules/List.md +485 -0
- package/docs/components/molecules/Modal.md +470 -0
- package/docs/components/molecules/ModalFooter.md +702 -0
- package/docs/components/molecules/ModalHeader.md +756 -0
- package/docs/components/molecules/ModalProvider.md +205 -0
- package/docs/components/molecules/Nav.md +530 -0
- package/docs/components/molecules/NavItem.md +572 -0
- package/docs/components/molecules/NavLink.md +499 -0
- package/docs/components/molecules/Option.md +521 -0
- package/docs/components/molecules/Pagination.md +592 -0
- package/docs/components/molecules/PaginationNumberField.md +722 -0
- package/docs/components/molecules/Popover.md +516 -0
- package/docs/components/molecules/ProgressBar.md +624 -0
- package/docs/components/molecules/RadioGroup.md +831 -0
- package/docs/components/molecules/RepeaterList.md +185 -0
- package/docs/components/molecules/Select.md +402 -0
- package/docs/components/molecules/SortableTrigger.md +82 -0
- package/docs/components/molecules/useModal.md +379 -0
- package/docs/components/organisms/Dropzone.md +346 -0
- package/docs/components/organisms/DropzoneClear.md +135 -0
- package/docs/components/organisms/DropzoneContent.md +216 -0
- package/docs/components/organisms/DropzoneFilename.md +191 -0
- package/docs/components/organisms/DropzoneSupportedFormats.md +184 -0
- package/docs/components/organisms/DropzoneTrigger.md +209 -0
- package/docs/components/organisms/Form.md +533 -0
- package/docs/components/organisms/SlideOutPanel.md +662 -0
- package/docs/components/organisms/TabContent.md +902 -0
- package/docs/components/organisms/TabItem.md +1091 -0
- package/docs/components/organisms/Table.md +611 -0
- package/docs/components/organisms/TableBody.md +679 -0
- package/docs/components/organisms/TableCell.md +482 -0
- package/docs/components/organisms/TableHeader.md +513 -0
- package/docs/components/organisms/TableHeaderCell.md +661 -0
- package/docs/components/organisms/TableRow.md +715 -0
- package/docs/components/organisms/Tabs.md +1330 -0
- package/docs/components/utils/ConditionalView.md +568 -0
- package/docs/components/utils/RenderStateView.md +726 -0
- package/docs/components/utils/WrapTextNodes.md +614 -0
- package/package.json +3 -2
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
# ChipInput
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
An input field component that allows users to enter multiple values as chips or tags. Automatically converts typed input into removable chip elements, providing an intuitive interface for managing lists of items like tags, categories, email addresses, or any collection of discrete values.
|
|
6
|
+
|
|
7
|
+
## Aliases
|
|
8
|
+
|
|
9
|
+
- ChipInput
|
|
10
|
+
- TagInput
|
|
11
|
+
- TokenInput
|
|
12
|
+
- MultiValueInput
|
|
13
|
+
- ChipField
|
|
14
|
+
|
|
15
|
+
## Props Breakdown
|
|
16
|
+
|
|
17
|
+
**Extends:** `ControlledFormComponentProps<string[]>`
|
|
18
|
+
|
|
19
|
+
| Prop | Type | Default | Required | Description |
|
|
20
|
+
|------|------|---------|----------|-------------|
|
|
21
|
+
| `value` | `string[]` | - | No | Current array of chip values |
|
|
22
|
+
| `onValueChange` | `(value: string[]) => void` | - | No | Callback fired when chip values change |
|
|
23
|
+
| `inputValue` | `string` | - | No | Current input field value |
|
|
24
|
+
| `onInputChange` | `(value: string) => void` | - | No | Callback fired when input value changes |
|
|
25
|
+
| `options` | `string[]` | - | No | Predefined options for autocomplete |
|
|
26
|
+
| `placeholder` | `string` | - | No | Placeholder text for the input field |
|
|
27
|
+
| `className` | `string` | - | No | Additional CSS class names |
|
|
28
|
+
| `onAddNewOption` | `(value: string) => void` | - | No | Callback fired when a new option is added |
|
|
29
|
+
|
|
30
|
+
## Examples
|
|
31
|
+
|
|
32
|
+
### Basic Usage
|
|
33
|
+
```tsx
|
|
34
|
+
import { ChipInput } from '@delightui/components';
|
|
35
|
+
|
|
36
|
+
function BasicExample() {
|
|
37
|
+
const [tags, setTags] = useState(['react', 'javascript']);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<ChipInput
|
|
41
|
+
value={tags}
|
|
42
|
+
onValueChange={setTags}
|
|
43
|
+
placeholder="Add tags..."
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Email Recipients
|
|
50
|
+
```tsx
|
|
51
|
+
function EmailRecipientsExample() {
|
|
52
|
+
const [recipients, setRecipients] = useState(['john@example.com']);
|
|
53
|
+
const [inputValue, setInputValue] = useState('');
|
|
54
|
+
|
|
55
|
+
const validateEmail = (email) => {
|
|
56
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleAddRecipient = (email) => {
|
|
60
|
+
if (validateEmail(email) && !recipients.includes(email)) {
|
|
61
|
+
setRecipients([...recipients, email]);
|
|
62
|
+
setInputValue('');
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div className="email-recipients">
|
|
68
|
+
<Text type="Heading6">Email Recipients</Text>
|
|
69
|
+
<ChipInput
|
|
70
|
+
value={recipients}
|
|
71
|
+
onValueChange={setRecipients}
|
|
72
|
+
inputValue={inputValue}
|
|
73
|
+
onInputChange={setInputValue}
|
|
74
|
+
placeholder="Enter email addresses..."
|
|
75
|
+
onAddNewOption={handleAddRecipient}
|
|
76
|
+
className="email-chip-input"
|
|
77
|
+
/>
|
|
78
|
+
<Text type="BodySmall">
|
|
79
|
+
{recipients.length} recipient{recipients.length !== 1 ? 's' : ''} added
|
|
80
|
+
</Text>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Skills Tags with Autocomplete
|
|
87
|
+
```tsx
|
|
88
|
+
function SkillsTagsExample() {
|
|
89
|
+
const [skills, setSkills] = useState(['React', 'TypeScript']);
|
|
90
|
+
const [inputValue, setInputValue] = useState('');
|
|
91
|
+
|
|
92
|
+
const availableSkills = [
|
|
93
|
+
'JavaScript', 'TypeScript', 'React', 'Vue.js', 'Angular',
|
|
94
|
+
'Node.js', 'Python', 'Java', 'C++', 'HTML', 'CSS',
|
|
95
|
+
'GraphQL', 'REST APIs', 'MongoDB', 'PostgreSQL'
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const handleAddSkill = (skill) => {
|
|
99
|
+
if (!skills.includes(skill)) {
|
|
100
|
+
setSkills([...skills, skill]);
|
|
101
|
+
}
|
|
102
|
+
setInputValue('');
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="skills-section">
|
|
107
|
+
<Text type="Heading6">Technical Skills</Text>
|
|
108
|
+
<ChipInput
|
|
109
|
+
value={skills}
|
|
110
|
+
onValueChange={setSkills}
|
|
111
|
+
inputValue={inputValue}
|
|
112
|
+
onInputChange={setInputValue}
|
|
113
|
+
options={availableSkills}
|
|
114
|
+
placeholder="Add your skills..."
|
|
115
|
+
onAddNewOption={handleAddSkill}
|
|
116
|
+
className="skills-chip-input"
|
|
117
|
+
/>
|
|
118
|
+
<Text type="BodySmall">
|
|
119
|
+
{skills.length} skill{skills.length !== 1 ? 's' : ''} selected
|
|
120
|
+
</Text>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Product Categories
|
|
127
|
+
```tsx
|
|
128
|
+
function ProductCategoriesExample() {
|
|
129
|
+
const [categories, setCategories] = useState(['Electronics']);
|
|
130
|
+
const [inputValue, setInputValue] = useState('');
|
|
131
|
+
|
|
132
|
+
const predefinedCategories = [
|
|
133
|
+
'Electronics', 'Clothing', 'Home & Garden', 'Sports',
|
|
134
|
+
'Books', 'Toys', 'Health', 'Beauty', 'Automotive'
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const handleCategoryChange = (newCategories) => {
|
|
138
|
+
// Limit to maximum 5 categories
|
|
139
|
+
if (newCategories.length <= 5) {
|
|
140
|
+
setCategories(newCategories);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<div className="product-categories">
|
|
146
|
+
<div className="category-header">
|
|
147
|
+
<Text type="Heading6">Product Categories</Text>
|
|
148
|
+
<Chip size="Small" style="B">
|
|
149
|
+
{categories.length}/5 selected
|
|
150
|
+
</Chip>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<ChipInput
|
|
154
|
+
value={categories}
|
|
155
|
+
onValueChange={handleCategoryChange}
|
|
156
|
+
inputValue={inputValue}
|
|
157
|
+
onInputChange={setInputValue}
|
|
158
|
+
options={predefinedCategories}
|
|
159
|
+
placeholder="Select categories..."
|
|
160
|
+
className="category-chip-input"
|
|
161
|
+
/>
|
|
162
|
+
|
|
163
|
+
<div className="category-suggestions">
|
|
164
|
+
<Text type="BodySmall">Suggested categories:</Text>
|
|
165
|
+
<div className="suggestion-chips">
|
|
166
|
+
<List
|
|
167
|
+
data={predefinedCategories
|
|
168
|
+
.filter(cat => !categories.includes(cat))
|
|
169
|
+
.slice(0, 3)
|
|
170
|
+
.map(category => ({ category }))}
|
|
171
|
+
component={({ category }) => (
|
|
172
|
+
<Chip
|
|
173
|
+
size="Small"
|
|
174
|
+
style="B"
|
|
175
|
+
onClick={() => handleCategoryChange([...categories, category])}
|
|
176
|
+
>
|
|
177
|
+
{category}
|
|
178
|
+
</Chip>
|
|
179
|
+
)}
|
|
180
|
+
keyExtractor={(item) => item.category}
|
|
181
|
+
/>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Search Filters
|
|
190
|
+
```tsx
|
|
191
|
+
function SearchFiltersExample() {
|
|
192
|
+
const [filters, setFilters] = useState([]);
|
|
193
|
+
const [inputValue, setInputValue] = useState('');
|
|
194
|
+
|
|
195
|
+
const availableFilters = [
|
|
196
|
+
'In Stock', 'On Sale', 'Free Shipping', 'New Arrivals',
|
|
197
|
+
'Top Rated', 'Premium', 'Eco Friendly', 'Local Seller'
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
const handleFilterToggle = (filter) => {
|
|
201
|
+
setFilters(prev =>
|
|
202
|
+
prev.includes(filter)
|
|
203
|
+
? prev.filter(f => f !== filter)
|
|
204
|
+
: [...prev, filter]
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div className="search-filters">
|
|
210
|
+
<div className="filter-header">
|
|
211
|
+
<Text type="Heading6">Search Filters</Text>
|
|
212
|
+
<Button
|
|
213
|
+
size="Small"
|
|
214
|
+
type="Ghost"
|
|
215
|
+
onClick={() => setFilters([])}
|
|
216
|
+
disabled={filters.length === 0}
|
|
217
|
+
>
|
|
218
|
+
Clear All
|
|
219
|
+
</Button>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<ChipInput
|
|
223
|
+
value={filters}
|
|
224
|
+
onValueChange={setFilters}
|
|
225
|
+
inputValue={inputValue}
|
|
226
|
+
onInputChange={setInputValue}
|
|
227
|
+
options={availableFilters}
|
|
228
|
+
placeholder="Add filters..."
|
|
229
|
+
className="filter-chip-input"
|
|
230
|
+
/>
|
|
231
|
+
|
|
232
|
+
<div className="filter-options">
|
|
233
|
+
<Text type="BodySmall">Quick filters:</Text>
|
|
234
|
+
<div className="quick-filters">
|
|
235
|
+
<List
|
|
236
|
+
data={availableFilters.slice(0, 4).map(filter => ({ filter }))}
|
|
237
|
+
component={({ filter }) => (
|
|
238
|
+
<Button
|
|
239
|
+
size="Small"
|
|
240
|
+
type={filters.includes(filter) ? "Filled" : "Outlined"}
|
|
241
|
+
onClick={() => handleFilterToggle(filter)}
|
|
242
|
+
>
|
|
243
|
+
{filter}
|
|
244
|
+
</Button>
|
|
245
|
+
)}
|
|
246
|
+
keyExtractor={(item) => item.filter}
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Team Members Assignment
|
|
256
|
+
```tsx
|
|
257
|
+
function TeamAssignmentExample() {
|
|
258
|
+
const [assignees, setAssignees] = useState(['john.doe']);
|
|
259
|
+
const [inputValue, setInputValue] = useState('');
|
|
260
|
+
|
|
261
|
+
const teamMembers = [
|
|
262
|
+
{ id: 'john.doe', name: 'John Doe', role: 'Developer' },
|
|
263
|
+
{ id: 'jane.smith', name: 'Jane Smith', role: 'Designer' },
|
|
264
|
+
{ id: 'mike.johnson', name: 'Mike Johnson', role: 'PM' },
|
|
265
|
+
{ id: 'sarah.wilson', name: 'Sarah Wilson', role: 'QA' }
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
const memberOptions = teamMembers.map(member => member.id);
|
|
269
|
+
|
|
270
|
+
const getMemberName = (id) => {
|
|
271
|
+
const member = teamMembers.find(m => m.id === id);
|
|
272
|
+
return member ? member.name : id;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const handleAssigneeChange = (newAssignees) => {
|
|
276
|
+
setAssignees(newAssignees);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<div className="team-assignment">
|
|
281
|
+
<div className="assignment-header">
|
|
282
|
+
<Text type="Heading6">Assign Team Members</Text>
|
|
283
|
+
<Chip size="Small" style="A">
|
|
284
|
+
{assignees.length} assigned
|
|
285
|
+
</Chip>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<ChipInput
|
|
289
|
+
value={assignees}
|
|
290
|
+
onValueChange={handleAssigneeChange}
|
|
291
|
+
inputValue={inputValue}
|
|
292
|
+
onInputChange={setInputValue}
|
|
293
|
+
options={memberOptions}
|
|
294
|
+
placeholder="Type to search team members..."
|
|
295
|
+
className="assignment-chip-input"
|
|
296
|
+
/>
|
|
297
|
+
|
|
298
|
+
<div className="team-list">
|
|
299
|
+
<Text type="BodySmall">Available team members:</Text>
|
|
300
|
+
{teamMembers
|
|
301
|
+
.filter(member => !assignees.includes(member.id))
|
|
302
|
+
.map(member => (
|
|
303
|
+
<div key={member.id} className="member-item">
|
|
304
|
+
<div className="member-info">
|
|
305
|
+
<Text type="BodyMedium">{member.name}</Text>
|
|
306
|
+
<Text type="BodySmall">{member.role}</Text>
|
|
307
|
+
</div>
|
|
308
|
+
<Button
|
|
309
|
+
size="Small"
|
|
310
|
+
type="Outlined"
|
|
311
|
+
onClick={() => setAssignees([...assignees, member.id])}
|
|
312
|
+
>
|
|
313
|
+
Assign
|
|
314
|
+
</Button>
|
|
315
|
+
</div>
|
|
316
|
+
))}
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Custom Validation
|
|
324
|
+
```tsx
|
|
325
|
+
function CustomValidationExample() {
|
|
326
|
+
const [urls, setUrls] = useState([]);
|
|
327
|
+
const [inputValue, setInputValue] = useState('');
|
|
328
|
+
const [errors, setErrors] = useState([]);
|
|
329
|
+
|
|
330
|
+
const validateURL = (url) => {
|
|
331
|
+
try {
|
|
332
|
+
new URL(url);
|
|
333
|
+
return true;
|
|
334
|
+
} catch {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const handleAddURL = (url) => {
|
|
340
|
+
if (!validateURL(url)) {
|
|
341
|
+
setErrors([...errors, `Invalid URL: ${url}`]);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (urls.includes(url)) {
|
|
346
|
+
setErrors([...errors, `URL already exists: ${url}`]);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
setUrls([...urls, url]);
|
|
351
|
+
setInputValue('');
|
|
352
|
+
setErrors(errors.filter(error => !error.includes(url)));
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const handleURLChange = (newUrls) => {
|
|
356
|
+
setUrls(newUrls);
|
|
357
|
+
// Clear related errors when URL is removed
|
|
358
|
+
setErrors(errors.filter(error =>
|
|
359
|
+
newUrls.some(url => error.includes(url))
|
|
360
|
+
));
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
return (
|
|
364
|
+
<div className="url-validation">
|
|
365
|
+
<Text type="Heading6">Website URLs</Text>
|
|
366
|
+
<ChipInput
|
|
367
|
+
value={urls}
|
|
368
|
+
onValueChange={handleURLChange}
|
|
369
|
+
inputValue={inputValue}
|
|
370
|
+
onInputChange={setInputValue}
|
|
371
|
+
placeholder="Enter website URLs..."
|
|
372
|
+
onAddNewOption={handleAddURL}
|
|
373
|
+
className="url-chip-input"
|
|
374
|
+
/>
|
|
375
|
+
|
|
376
|
+
{errors.length > 0 && (
|
|
377
|
+
<div className="error-messages">
|
|
378
|
+
{errors.map((error, index) => (
|
|
379
|
+
<div key={index} className="error-message">
|
|
380
|
+
<Icon icon="Error" size="Small" />
|
|
381
|
+
<Text type="BodySmall">{error}</Text>
|
|
382
|
+
</div>
|
|
383
|
+
))}
|
|
384
|
+
</div>
|
|
385
|
+
)}
|
|
386
|
+
|
|
387
|
+
<div className="url-stats">
|
|
388
|
+
<Text type="BodySmall">
|
|
389
|
+
{urls.length} valid URL{urls.length !== 1 ? 's' : ''} added
|
|
390
|
+
</Text>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Dynamic Options Loading
|
|
398
|
+
```tsx
|
|
399
|
+
function DynamicOptionsExample() {
|
|
400
|
+
const [selectedTags, setSelectedTags] = useState([]);
|
|
401
|
+
const [inputValue, setInputValue] = useState('');
|
|
402
|
+
const [options, setOptions] = useState([]);
|
|
403
|
+
const [loading, setLoading] = useState(false);
|
|
404
|
+
|
|
405
|
+
const searchTags = async (query) => {
|
|
406
|
+
if (query.length < 2) return;
|
|
407
|
+
|
|
408
|
+
setLoading(true);
|
|
409
|
+
try {
|
|
410
|
+
// Simulate API call
|
|
411
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
412
|
+
const mockResults = [
|
|
413
|
+
`${query}-tag1`, `${query}-tag2`, `${query}-tag3`,
|
|
414
|
+
`popular-${query}`, `trending-${query}`
|
|
415
|
+
];
|
|
416
|
+
setOptions(mockResults);
|
|
417
|
+
} finally {
|
|
418
|
+
setLoading(false);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
useEffect(() => {
|
|
423
|
+
searchTags(inputValue);
|
|
424
|
+
}, [inputValue]);
|
|
425
|
+
|
|
426
|
+
return (
|
|
427
|
+
<div className="dynamic-options">
|
|
428
|
+
<div className="tags-header">
|
|
429
|
+
<Text type="Heading6">Dynamic Tag Search</Text>
|
|
430
|
+
{loading && <Spinner size="Small" />}
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
<ChipInput
|
|
434
|
+
value={selectedTags}
|
|
435
|
+
onValueChange={setSelectedTags}
|
|
436
|
+
inputValue={inputValue}
|
|
437
|
+
onInputChange={setInputValue}
|
|
438
|
+
options={options}
|
|
439
|
+
placeholder="Type to search tags..."
|
|
440
|
+
className="dynamic-chip-input"
|
|
441
|
+
/>
|
|
442
|
+
|
|
443
|
+
{options.length > 0 && (
|
|
444
|
+
<div className="suggested-tags">
|
|
445
|
+
<Text type="BodySmall">Suggested tags:</Text>
|
|
446
|
+
<div className="tag-suggestions">
|
|
447
|
+
{options.slice(0, 5).map(tag => (
|
|
448
|
+
<Chip
|
|
449
|
+
key={tag}
|
|
450
|
+
size="Small"
|
|
451
|
+
style="B"
|
|
452
|
+
onClick={() => {
|
|
453
|
+
if (!selectedTags.includes(tag)) {
|
|
454
|
+
setSelectedTags([...selectedTags, tag]);
|
|
455
|
+
setInputValue('');
|
|
456
|
+
}
|
|
457
|
+
}}
|
|
458
|
+
>
|
|
459
|
+
{tag}
|
|
460
|
+
</Chip>
|
|
461
|
+
))}
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
)}
|
|
465
|
+
</div>
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Form Integration
|
|
471
|
+
```tsx
|
|
472
|
+
function FormIntegrationExample() {
|
|
473
|
+
const [formData, setFormData] = useState({
|
|
474
|
+
title: '',
|
|
475
|
+
description: '',
|
|
476
|
+
tags: ['frontend'],
|
|
477
|
+
skills: [],
|
|
478
|
+
collaborators: []
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
const handleSubmit = (e) => {
|
|
482
|
+
e.preventDefault();
|
|
483
|
+
console.log('Form submitted:', formData);
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const updateFormField = (field, value) => {
|
|
487
|
+
setFormData(prev => ({
|
|
488
|
+
...prev,
|
|
489
|
+
[field]: value
|
|
490
|
+
}));
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
return (
|
|
494
|
+
<Form onSubmit={handleSubmit} className="chip-input-form">
|
|
495
|
+
<FormField name="title" label="Project Title" required>
|
|
496
|
+
<Input
|
|
497
|
+
value={formData.title}
|
|
498
|
+
onValueChange={(value) => updateFormField('title', value)}
|
|
499
|
+
placeholder="Enter project title"
|
|
500
|
+
/>
|
|
501
|
+
</FormField>
|
|
502
|
+
|
|
503
|
+
<FormField name="description" label="Description" required>
|
|
504
|
+
<TextArea
|
|
505
|
+
value={formData.description}
|
|
506
|
+
onValueChange={(value) => updateFormField('description', value)}
|
|
507
|
+
placeholder="Describe your project"
|
|
508
|
+
rows={3}
|
|
509
|
+
/>
|
|
510
|
+
</FormField>
|
|
511
|
+
|
|
512
|
+
<FormField name="tags" label="Tags">
|
|
513
|
+
<ChipInput
|
|
514
|
+
value={formData.tags}
|
|
515
|
+
onValueChange={(value) => updateFormField('tags', value)}
|
|
516
|
+
placeholder="Add relevant tags..."
|
|
517
|
+
options={['frontend', 'backend', 'fullstack', 'mobile', 'design']}
|
|
518
|
+
/>
|
|
519
|
+
</FormField>
|
|
520
|
+
|
|
521
|
+
<FormField name="skills" label="Required Skills">
|
|
522
|
+
<ChipInput
|
|
523
|
+
value={formData.skills}
|
|
524
|
+
onValueChange={(value) => updateFormField('skills', value)}
|
|
525
|
+
placeholder="What skills are needed?"
|
|
526
|
+
options={['React', 'Node.js', 'Python', 'Design', 'DevOps']}
|
|
527
|
+
/>
|
|
528
|
+
</FormField>
|
|
529
|
+
|
|
530
|
+
<FormField name="collaborators" label="Collaborators">
|
|
531
|
+
<ChipInput
|
|
532
|
+
value={formData.collaborators}
|
|
533
|
+
onValueChange={(value) => updateFormField('collaborators', value)}
|
|
534
|
+
placeholder="Add collaborator emails..."
|
|
535
|
+
/>
|
|
536
|
+
</FormField>
|
|
537
|
+
|
|
538
|
+
<ButtonGroup>
|
|
539
|
+
<Button type="Filled" actionType="submit">
|
|
540
|
+
Create Project
|
|
541
|
+
</Button>
|
|
542
|
+
<Button type="Outlined" actionType="reset">
|
|
543
|
+
Clear Form
|
|
544
|
+
</Button>
|
|
545
|
+
</ButtonGroup>
|
|
546
|
+
</Form>
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Advanced Filtering Interface
|
|
552
|
+
```tsx
|
|
553
|
+
function AdvancedFilteringExample() {
|
|
554
|
+
const [filters, setFilters] = useState({
|
|
555
|
+
categories: [],
|
|
556
|
+
brands: [],
|
|
557
|
+
features: [],
|
|
558
|
+
priceRanges: []
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
const filterOptions = {
|
|
562
|
+
categories: ['Electronics', 'Clothing', 'Home', 'Sports'],
|
|
563
|
+
brands: ['Apple', 'Samsung', 'Nike', 'Adidas'],
|
|
564
|
+
features: ['Wireless', 'Waterproof', 'Bluetooth', 'Fast Charging'],
|
|
565
|
+
priceRanges: ['Under $50', '$50-$100', '$100-$200', 'Over $200']
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const updateFilter = (filterType, values) => {
|
|
569
|
+
setFilters(prev => ({
|
|
570
|
+
...prev,
|
|
571
|
+
[filterType]: values
|
|
572
|
+
}));
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const clearAllFilters = () => {
|
|
576
|
+
setFilters({
|
|
577
|
+
categories: [],
|
|
578
|
+
brands: [],
|
|
579
|
+
features: [],
|
|
580
|
+
priceRanges: []
|
|
581
|
+
});
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const totalFilters = Object.values(filters).flat().length;
|
|
585
|
+
|
|
586
|
+
return (
|
|
587
|
+
<div className="advanced-filtering">
|
|
588
|
+
<div className="filter-header">
|
|
589
|
+
<Text type="Heading5">Product Filters</Text>
|
|
590
|
+
<div className="filter-actions">
|
|
591
|
+
<Chip size="Small" style="A">
|
|
592
|
+
{totalFilters} filter{totalFilters !== 1 ? 's' : ''} active
|
|
593
|
+
</Chip>
|
|
594
|
+
<Button
|
|
595
|
+
size="Small"
|
|
596
|
+
type="Ghost"
|
|
597
|
+
onClick={clearAllFilters}
|
|
598
|
+
disabled={totalFilters === 0}
|
|
599
|
+
>
|
|
600
|
+
Clear All
|
|
601
|
+
</Button>
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
|
|
605
|
+
<div className="filter-sections">
|
|
606
|
+
{Object.entries(filterOptions).map(([filterType, options]) => (
|
|
607
|
+
<div key={filterType} className="filter-section">
|
|
608
|
+
<Text type="Heading6">
|
|
609
|
+
{filterType.charAt(0).toUpperCase() + filterType.slice(1)}
|
|
610
|
+
</Text>
|
|
611
|
+
<ChipInput
|
|
612
|
+
value={filters[filterType]}
|
|
613
|
+
onValueChange={(values) => updateFilter(filterType, values)}
|
|
614
|
+
options={options}
|
|
615
|
+
placeholder={`Select ${filterType}...`}
|
|
616
|
+
className="filter-chip-input"
|
|
617
|
+
/>
|
|
618
|
+
</div>
|
|
619
|
+
))}
|
|
620
|
+
</div>
|
|
621
|
+
|
|
622
|
+
<div className="filter-summary">
|
|
623
|
+
<Text type="BodyMedium">Active Filters:</Text>
|
|
624
|
+
{totalFilters > 0 ? (
|
|
625
|
+
<div className="active-filters">
|
|
626
|
+
{Object.entries(filters).map(([type, values]) =>
|
|
627
|
+
values.map(value => (
|
|
628
|
+
<Chip
|
|
629
|
+
key={`${type}-${value}`}
|
|
630
|
+
size="Small"
|
|
631
|
+
style="A"
|
|
632
|
+
onRemove={() => updateFilter(type, values.filter(v => v !== value))}
|
|
633
|
+
>
|
|
634
|
+
{value}
|
|
635
|
+
</Chip>
|
|
636
|
+
))
|
|
637
|
+
)}
|
|
638
|
+
</div>
|
|
639
|
+
) : (
|
|
640
|
+
<Text type="BodySmall">No filters applied</Text>
|
|
641
|
+
)}
|
|
642
|
+
</div>
|
|
643
|
+
</div>
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
```
|