@aiquants/select-box 0.1.1 → 0.2.1

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 CHANGED
@@ -1,375 +1,154 @@
1
- # @aiquants/directory-tree
1
+ # @aiquants/select-box
2
2
 
3
- A high-performance React directory tree component with virtualization, file selection, and theming support.
3
+ A high-performance React select box component with virtual scrolling, fuzzy search, and multi-selection support.
4
4
 
5
5
  ## Features
6
6
 
7
- - **🚀 High Performance**: Built with [@aiquants/virtualscroll](../virtualscroll) to handle large directory structures efficiently with O(log n) operations
8
- - **🌀 Ultrafast Scrolling**: Inherits adaptive tap scroll circle from VirtualScroll for navigating massive datasets
9
- - **📁 File Selection**: Interactive file selection with visual feedback and multiple selection modes
10
- - **🎨 Theming**: Customizable line colors with external theme control support
11
- - **♿ Accessibility**: Full keyboard navigation and screen reader support
12
- - **📱 Responsive**: Optimized for both desktop and mobile interfaces
13
- - **🔧 TypeScript**: Complete TypeScript support with comprehensive type definitions
14
- - **💾 State Persistence**: Automatic localStorage persistence for expansion states
15
- - **🎯 Flexible Selection**: Support for none, single, or multiple selection modes
7
+ - **Virtual Scrolling**: Built with `@aiquants/virtualscroll` to handle 1,000+ options smoothly
8
+ - **Fuzzy Search**: Built with `@aiquants/fuzzy-search` using Web Workers for non-blocking search
9
+ - **Single & Multi Select**: Unified API for both modes via the `multiple` prop
10
+ - **Clearable**: Optional clear button for single-select mode
11
+ - **Resizable Dropdown**: Drag the bottom-right handle to resize (100px - 600px)
12
+ - **Keyboard Navigation**: Backspace to remove last tag, external click to close
13
+ - **TypeScript**: Complete type definitions included
16
14
 
17
15
  ## Installation
18
16
 
19
17
  ```bash
20
- npm install @aiquants/directory-tree
21
- # or
22
- yarn add @aiquants/directory-tree
23
- # or
24
- pnpm add @aiquants/directory-tree
18
+ pnpm add @aiquants/select-box
25
19
  ```
26
20
 
27
21
  ## Peer Dependencies
28
22
 
29
- This package requires the following peer dependencies:
30
-
31
23
  ```bash
32
- npm install react react-dom @aiquants/virtualscroll tailwind-variants tailwind-merge
24
+ pnpm add react react-dom
33
25
  ```
34
26
 
35
27
  ## Quick Start
36
28
 
37
29
  ```tsx
38
- import React from 'react';
39
- import { DirectoryTree, useDirectoryTreeState } from '@aiquants/directory-tree';
40
- import { useTheme } from './hooks/useTheme'; // Your theme hook
41
- import type { DirectoryEntry } from '@aiquants/directory-tree';
42
-
43
- const sampleData: DirectoryEntry[] = [
44
- {
45
- name: 'src',
46
- absolutePath: '/src',
47
- relativePath: 'src',
48
- children: [
49
- {
50
- name: 'components',
51
- absolutePath: '/src/components',
52
- relativePath: 'src/components',
53
- children: [
54
- {
55
- name: 'App.tsx',
56
- absolutePath: '/src/components/App.tsx',
57
- relativePath: 'src/components/App.tsx',
58
- children: null
59
- }
60
- ]
61
- }
62
- ]
63
- }
64
- ];
65
-
66
- export default function App() {
67
- const { theme } = useTheme();
68
- const {
69
- toggle,
70
- isExpanded,
71
- expandMultiple,
72
- collapseMultiple,
73
- isPending
74
- } = useDirectoryTreeState({
75
- storageKey: 'my-directory-tree'
76
- });
77
-
78
- // Calculate line color based on theme
79
- const lineColor = theme === "dark" ? "#4A5568" : "#A0AEC0";
80
-
81
- const handleFileSelect = (absolutePath: string, relativePath: string) => {
82
- console.log(`File selected: ${absolutePath} (${relativePath})`);
83
- };
30
+ import { useState, useMemo } from "react"
31
+ import { SelectBox, type Option } from "@aiquants/select-box"
32
+ // Required: Vite environments need explicit Worker URLs
33
+ import indexWorkerUrl from "@aiquants/fuzzy-search/worker/indexWorker?url"
34
+ import levenshteinWorkerUrl from "@aiquants/fuzzy-search/worker/levenshteinWorker?url"
35
+
36
+ const options: Option[] = [
37
+ { label: "Apple", value: "apple" },
38
+ { label: "Banana", value: "banana" },
39
+ { label: "Cherry", value: "cherry" },
40
+ ]
41
+
42
+ function SingleSelect() {
43
+ const [value, setValue] = useState<Option | null>(null)
44
+ const workerUrls = useMemo(() => ({
45
+ indexWorker: indexWorkerUrl,
46
+ levenshteinWorker: levenshteinWorkerUrl,
47
+ }), [])
84
48
 
85
49
  return (
86
- <div className="h-96 w-full border rounded-lg">
87
- <DirectoryTree
88
- entries={sampleData}
89
- expansion={{
90
- toggle,
91
- isExpanded,
92
- expandMultiple,
93
- collapseMultiple,
94
- isPending
95
- }}
96
- selection={{
97
- onFileSelect: handleFileSelect,
98
- selectedPath: null
99
- }}
100
- visual={{
101
- lineColor,
102
- className: "h-full"
103
- }}
104
- />
105
- </div>
106
- );
50
+ <SelectBox
51
+ options={options}
52
+ value={value}
53
+ onChange={(val) => setValue(val as Option | null)}
54
+ placeholder="Select a fruit..."
55
+ workerUrls={workerUrls}
56
+ />
57
+ )
107
58
  }
108
- ```
109
-
110
- ## API Reference
111
-
112
- ### DirectoryTree Component
113
-
114
- The main component for rendering the directory tree.
115
-
116
- #### Props
117
-
118
- | Prop | Type | Required | Description |
119
- |------|------|----------|-------------|
120
- | `entries` | `DirectoryEntry[]` | Yes | Array of root directory entries to display |
121
- | `expansion` | `object` | Yes | Configuration for tree expansion state and behavior |
122
- | `selection` | `object` | Yes | Configuration for item selection |
123
- | `visual` | `object` | No | Visual customization options |
124
- | `virtualScroll` | `DirectoryTreeVirtualScrollOptions` | No | Pass-through options for the underlying VirtualScroll component |
125
-
126
- #### Expansion Options (`expansion`)
127
-
128
- | Prop | Type | Required | Default | Description |
129
- |------|------|----------|---------|-------------|
130
- | `toggle` | `(path: string, relativePath: string) => void` | Yes | - | Function to toggle directory expansion state |
131
- | `isExpanded` | `(path: string) => boolean` | Yes | - | Function to check if a directory is expanded |
132
- | `expandMultiple` | `(paths: string[]) => void` | Yes | - | Function to expand multiple directories |
133
- | `collapseMultiple` | `(paths: string[]) => void` | Yes | - | Function to collapse multiple directories |
134
- | `isPending` | `boolean` | No | `false` | Whether the tree is in a pending state |
135
- | `alwaysExpanded` | `boolean` | No | `false` | If true, all directories are always expanded |
136
- | `doubleClickAction` | `'recursive' \| 'toggle'` | No | `'recursive'` | Action on double-clicking a directory |
137
-
138
- #### Selection Options (`selection`)
139
-
140
- | Prop | Type | Required | Default | Description |
141
- |------|------|----------|---------|-------------|
142
- | `onFileSelect` | `(absolutePath: string, relativePath: string) => void` | Yes | - | Callback function triggered when a file is selected |
143
- | `selectedPath` | `string \| null` | No | - | The currently selected file path |
144
- | `mode` | `'none' \| 'single' \| 'multiple'` | No | `'none'` | Selection mode for items |
145
- | `selectedItems` | `Set<string>` | No | - | Set of paths for currently selected items |
146
- | `onSelectionChange` | `(path: string, isSelected: boolean) => void` | No | - | Callback when item selection changes |
147
-
148
- #### Visual Options (`visual`)
149
-
150
- | Prop | Type | Required | Default | Description |
151
- |------|------|----------|---------|-------------|
152
- | `className` | `string` | No | - | Optional CSS class name for the container |
153
- | `style` | `React.CSSProperties` | No | - | Optional inline styles for the container |
154
- | `lineColor` | `string` | No | `'#A0AEC0'` | The color of the tree lines |
155
- | `showTreeLines` | `boolean` | No | `true` | Flag indicating whether to render tree connector lines |
156
- | `showExpandIcons` | `boolean` | No | `true` | Flag indicating whether to render directory expand icons |
157
- | `showDirectoryIcons` | `boolean` | No | `true` | Flag indicating whether to render directory type icons |
158
- | `showFileIcons` | `boolean` | No | `true` | Flag indicating whether to render file type icons |
159
- | `iconOverrides` | `DirectoryTreeIconOverrides` | No | - | Icon overrides applied globally |
160
- | `expandIconSize` | `number` | No | - | Size of the expand icon |
161
- | `removeRootIndent` | `boolean` | No | `false` | If true, removes the indentation and connector lines for root-level items |
162
-
163
- ### Virtual Scroll Options
164
-
165
- `virtualScroll` lets you customize the embedded `@aiquants/virtualscroll` instance without re-implementing list rendering. Every option is optional and mirrors the VirtualScroll API.
166
-
167
- - `overscanCount`: Adjust how many items render beyond the viewport for smoother scrolling (default: `10`).
168
- - `tapScrollCircleOptions`: Forward tap circle customization such as radius or visual renderer.
169
- - `scrollBarWidth`: Override the custom scrollbar width.
170
- - `enableThumbDrag`, `enableTrackClick`, `enableArrowButtons`, `enablePointerDrag`: Toggle individual scrollbar interactions.
171
- - `inertiaOptions`: Fine-tune inertial scrolling parameters.
172
- - `onScroll`, `onRangeChange`, `className`, `background`, `initialScrollIndex`, `initialScrollOffset`: Hook into scroll lifecycle or provide bespoke styling.
173
-
174
- Example:
175
-
176
- ```tsx
177
- <DirectoryTree
178
- {...commonProps}
179
- virtualScroll={{
180
- overscanCount: 6,
181
- enablePointerDrag: false,
182
- scrollBarWidth: 14,
183
- tapScrollCircleOptions: {
184
- radius: 32
185
- }
186
- }}
187
- />;
188
- ```
189
-
190
- ### useDirectoryTreeState Hook
191
-
192
- A hook for managing directory tree state with localStorage persistence.
193
-
194
- #### Parameters
195
-
196
- | Parameter | Type | Description |
197
- |-----------|------|-------------|
198
- | `options` | `UseDirectoryTreeStateProps` | Configuration options |
199
-
200
- #### Options
201
-
202
- | Option | Type | Description |
203
- |--------|------|-------------|
204
- | `initialExpanded` | `Set<string>` | Initially expanded directories |
205
- | `storageKey` | `string` | localStorage key for persistence (default: 'directory-tree-state') |
206
-
207
- #### Returns
208
-
209
- | Property | Type | Description |
210
- |----------|------|-------------|
211
- | `expanded` | `Set<string>` | Currently expanded directories |
212
- | `toggle` | `(path: string, relativePath: string) => void` | Toggle directory expansion |
213
- | `isExpanded` | `(path: string) => boolean` | Check if directory is expanded |
214
- | `expand` | `(path: string) => void` | Expand a directory |
215
- | `collapse` | `(path: string) => void` | Collapse a directory |
216
- | `expandMultiple` | `(paths: string[]) => void` | Expand multiple directories |
217
- | `collapseMultiple` | `(paths: string[]) => void` | Collapse multiple directories |
218
- | `collapseAll` | `() => void` | Collapse all directories |
219
- | `isPending` | `boolean` | Whether a transition is pending |
220
-
221
- ### DirectoryEntry Type
222
-
223
- ```typescript
224
- type DirectoryEntry = {
225
- name: string;
226
- absolutePath: string;
227
- relativePath: string;
228
- children: DirectoryEntry[] | null;
229
- };
230
- ```
231
59
 
232
- ## Styling
233
-
234
- The component uses Tailwind CSS for styling. Make sure you have Tailwind CSS configured in your project. The component supports both light and dark themes, but theme control is managed by the calling component.
235
-
236
- ### Theme Control
237
-
238
- The `lineColor` prop allows you to control the tree line color based on your application's theme:
239
-
240
- ```tsx
241
- import { useTheme } from './hooks/useTheme';
242
-
243
- function MyComponent() {
244
- const { theme } = useTheme();
245
-
246
- // Calculate line color based on theme
247
- const lineColor = theme === "dark" ? "#4A5568" : "#A0AEC0";
60
+ function MultiSelect() {
61
+ const [values, setValues] = useState<Option[]>([])
62
+ const workerUrls = useMemo(() => ({
63
+ indexWorker: indexWorkerUrl,
64
+ levenshteinWorker: levenshteinWorkerUrl,
65
+ }), [])
248
66
 
249
67
  return (
250
- <DirectoryTree
251
- // ... other props
252
- visual={{
253
- lineColor: lineColor
254
- }}
68
+ <SelectBox
69
+ options={options}
70
+ value={values}
71
+ onChange={(val) => setValues(Array.isArray(val) ? val : [])}
72
+ multiple
73
+ placeholder="Select multiple..."
74
+ workerUrls={workerUrls}
255
75
  />
256
- );
76
+ )
257
77
  }
258
78
  ```
259
79
 
260
- ### Custom Styling
80
+ ## CSS Setup
261
81
 
262
- You can customize the appearance by overriding the default Tailwind classes:
263
-
264
- ```tsx
265
- <DirectoryTree
266
- visual={{
267
- className: "custom-directory-tree",
268
- style: { height: '400px' }
269
- }}
270
- // ... other props
271
- />
272
- ```
82
+ Import the CSS in your application's stylesheet:
273
83
 
274
84
  ```css
275
- .custom-directory-tree {
276
- /* Your custom styles */
277
- }
85
+ @import "@aiquants/select-box/styles/select-box.css" layer(components);
278
86
  ```
279
87
 
280
- ## Advanced Usage
88
+ ## API Reference
281
89
 
282
- ### Large Datasets
90
+ ### SelectBox Props
283
91
 
284
- The component is optimized for large datasets through virtualization:
92
+ | Prop | Type | Required | Default | Description |
93
+ | --- | --- | --- | --- | --- |
94
+ | `options` | `Option[]` | Yes | - | Array of selectable options |
95
+ | `value` | `Option \| Option[]` | No | `undefined` | Current selection (controlled) |
96
+ | `onChange` | `(value: Option \| Option[] \| null) => void` | No | `undefined` | Selection change callback |
97
+ | `placeholder` | `string` | No | `"Select..."` | Placeholder text when nothing is selected |
98
+ | `multiple` | `boolean` | No | `false` | Enable multi-select mode |
99
+ | `className` | `string` | No | `""` | Additional CSS class for the container |
100
+ | `disabled` | `boolean` | No | `false` | Disable the component |
101
+ | `clearable` | `boolean` | No | `false` | Show a clear button (single-select) |
102
+ | `autoFocus` | `boolean` | No | `false` | Auto-focus the input on mount |
103
+ | `workerUrls` | `{ indexWorker?: string; levenshteinWorker?: string }` | No | `undefined` | Explicit Web Worker URLs (see below) |
104
+
105
+ ### Option Type
285
106
 
286
- ```tsx
287
- import { DirectoryTree } from '@aiquants/directory-tree';
288
-
289
- // Handle thousands of entries efficiently
290
- <DirectoryTree
291
- entries={largeDataset}
292
- // ... other required props
293
- visual={{
294
- style: { height: '600px' }
295
- }}
296
- />
107
+ ```typescript
108
+ interface Option {
109
+ label: string // Display text (search target)
110
+ value: string | number // Unique identifier
111
+ [key: string]: any // Custom metadata
112
+ }
297
113
  ```
298
114
 
299
- ### Multiple Selection Mode
115
+ ## Web Worker URLs (Important)
300
116
 
301
- Enable multiple selection for batch operations:
117
+ The fuzzy search uses Web Workers internally. In **Vite development mode**, the default `import.meta.url`-based Worker resolution may fail, causing errors like:
302
118
 
303
- ```tsx
304
- const [selectedItems, setSelectedItems] = useState(new Set<string>());
305
-
306
- const handleSelectionChange = (path: string, isSelected: boolean) => {
307
- setSelectedItems(prev => {
308
- const newSet = new Set(prev);
309
- if (isSelected) {
310
- newSet.add(path);
311
- } else {
312
- newSet.delete(path);
313
- }
314
- return newSet;
315
- });
316
- };
317
-
318
- <DirectoryTree
319
- // ... other props
320
- selection={{
321
- mode: "multiple",
322
- selectedItems: selectedItems,
323
- onSelectionChange: handleSelectionChange,
324
- onFileSelect: handleFileSelect // Required prop
325
- }}
326
- />
119
+ ```text
120
+ [fuzzy-search] Index Worker Error
121
+ [fuzzy-search] Index Worker: Ping timeout
122
+ [fuzzy-search] Failed to rebuild index: Error: Index build timeout
327
123
  ```
328
124
 
329
- ### Custom Double-Click Behavior
330
-
331
- Control how directories behave on double-click:
125
+ To fix this, pass `workerUrls` with Vite's `?url` import:
332
126
 
333
127
  ```tsx
334
- <DirectoryTree
335
- // ... other props
336
- expansion={{
337
- // ... required expansion props
338
- doubleClickAction: "toggle" // Only toggle the clicked directory
339
- // or
340
- doubleClickAction: "recursive" // Expand/collapse all children (default)
128
+ import indexWorkerUrl from "@aiquants/fuzzy-search/worker/indexWorker?url"
129
+ import levenshteinWorkerUrl from "@aiquants/fuzzy-search/worker/levenshteinWorker?url"
130
+
131
+ <SelectBox
132
+ options={options}
133
+ workerUrls={{
134
+ indexWorker: indexWorkerUrl,
135
+ levenshteinWorker: levenshteinWorkerUrl,
341
136
  }}
342
137
  />
343
138
  ```
344
139
 
345
- ### State Persistence
346
-
347
- The `useDirectoryTreeState` hook automatically persists expansion state to localStorage:
348
-
349
- ```tsx
350
- const { toggle, isExpanded, expandMultiple, collapseMultiple } = useDirectoryTreeState({
351
- storageKey: 'myapp-directory-tree',
352
- initialExpanded: new Set(['/src', '/docs'])
353
- });
354
- ```
355
-
356
- ## TypeScript Support
140
+ ## Dependencies
357
141
 
358
- This package is written in TypeScript and provides comprehensive type definitions. All components and hooks are fully typed for the best development experience.
142
+ | Package | Purpose |
143
+ | --- | --- |
144
+ | `@aiquants/fuzzy-search` | Web Worker-based fuzzy search engine |
145
+ | `@aiquants/virtualscroll` | Virtual scrolling for large lists |
146
+ | `tailwind-merge` | Tailwind CSS class conflict resolution |
359
147
 
360
- ## Contributing
148
+ ## Documentation
361
149
 
362
- We welcome contributions! Please feel free to submit issues and pull requests.
150
+ See the [docs/specs/](docs/specs/) directory for detailed specifications.
363
151
 
364
152
  ## License
365
153
 
366
- MIT License - see the [LICENSE](LICENSE) file for details.
367
-
368
- ## Author
369
-
370
- - **GitHub**: [fehde-k](https://github.com/fehde-k)
371
- - **X (formerly Twitter)**: [@fehdek](https://x.com/fehdek)
372
-
373
- ---
374
-
375
- Made with ❤️ by the AIQuants team.
154
+ MIT