@appforgeapps/uiforge 0.1.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/README.md ADDED
@@ -0,0 +1,1241 @@
1
+ # UIForge
2
+
3
+ A rich user interface library for ReactJS developers written by a seasoned user interface developer who loves working with ReactJS.
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue)](https://www.typescriptlang.org/)
7
+ [![React](https://img.shields.io/badge/React-18%2F19-61dafb)](https://reactjs.org/)
8
+
9
+ > **๐Ÿ“ฆ Package Name Change**: The package has been renamed from `@chriscase/uiforge` to `@appforgeapps/uiforge`. If you're upgrading from an older version, please see the [Migration Guide](./MIGRATION_GUIDE.md).
10
+
11
+ > **โš ๏ธ Early Stage Project**: UIForge is a very new project under active development. The API is subject to rapid changes and breaking changes may occur frequently as we refine the components and their interfaces. We recommend pinning to specific versions in production and reviewing the CHANGELOG before upgrading.
12
+
13
+ ## Features
14
+
15
+ - ๐ŸŽจ **Beautiful Components** - Carefully crafted, customizable UI components
16
+ - ๐Ÿ’ช **TypeScript First** - Full TypeScript support with type definitions
17
+ - โšก **Modern Stack** - Built with React, TypeScript, and Vite
18
+ - ๐Ÿงช **Well Tested** - Comprehensive test coverage with Vitest
19
+ - ๐Ÿ“ฆ **Tree Shakeable** - Only import what you need
20
+ - ๐ŸŽฏ **Developer Friendly** - Easy to use and customize
21
+ - ๐Ÿ†“ **Free & Open Source** - MIT licensed
22
+
23
+ ## Installation
24
+
25
+ ### From NPM (Recommended)
26
+
27
+ Install UIForge from NPM:
28
+
29
+ ```bash
30
+ npm install @appforgeapps/uiforge
31
+ ```
32
+
33
+ Or with yarn:
34
+
35
+ ```bash
36
+ yarn add @appforgeapps/uiforge
37
+ ```
38
+
39
+ Or with pnpm:
40
+
41
+ ```bash
42
+ pnpm add @appforgeapps/uiforge
43
+ ```
44
+
45
+ ### Using UIForge in Your Project
46
+
47
+ After installation, you'll need to import both the components and the CSS styles in your application.
48
+
49
+ #### Method 1: Import in your main entry file (Recommended)
50
+
51
+ This is the most common approach - import the styles once in your application's entry point:
52
+
53
+ ```tsx
54
+ // src/main.tsx or src/index.tsx
55
+ import '@appforgeapps/uiforge/styles.css'
56
+ import React from 'react'
57
+ import ReactDOM from 'react-dom/client'
58
+ import App from './App'
59
+
60
+ ReactDOM.createRoot(document.getElementById('root')!).render(
61
+ <React.StrictMode>
62
+ <App />
63
+ </React.StrictMode>
64
+ )
65
+ ```
66
+
67
+ Then import and use components in your app:
68
+
69
+ ```tsx
70
+ // src/App.tsx
71
+ import { Button, UIForgeGrid, UIForgeComboBox } from '@appforgeapps/uiforge'
72
+
73
+ function App() {
74
+ return (
75
+ <div>
76
+ <h1>My Application</h1>
77
+ <Button variant="primary" onClick={() => alert('Hello!')}>
78
+ Click Me
79
+ </Button>
80
+ </div>
81
+ )
82
+ }
83
+
84
+ export default App
85
+ ```
86
+
87
+ #### Method 2: Import styles in your component file
88
+
89
+ If you prefer, you can import the styles directly in the component file where you use UIForge components:
90
+
91
+ ```tsx
92
+ // src/components/MyComponent.tsx
93
+ import '@appforgeapps/uiforge/styles.css'
94
+ import { Button } from '@appforgeapps/uiforge'
95
+
96
+ export function MyComponent() {
97
+ return <Button variant="primary">Click Me</Button>
98
+ }
99
+ ```
100
+
101
+ #### Method 3: Import in your global CSS file
102
+
103
+ You can also import UIForge styles in your main CSS file:
104
+
105
+ ```css
106
+ /* src/index.css or src/App.css */
107
+ @import '@appforgeapps/uiforge/styles.css';
108
+
109
+ /* Your other styles */
110
+ body {
111
+ margin: 0;
112
+ font-family: sans-serif;
113
+ }
114
+ ```
115
+
116
+ ### TypeScript Configuration
117
+
118
+ UIForge is written in TypeScript and includes full type definitions. If you're using TypeScript, the types will be automatically picked up. Ensure your `tsconfig.json` includes:
119
+
120
+ ```json
121
+ {
122
+ "compilerOptions": {
123
+ "moduleResolution": "bundler", // or "node16" / "nodenext"
124
+ "jsx": "react-jsx",
125
+ "esModuleInterop": true
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Bundler Configuration
131
+
132
+ UIForge works with all modern bundlers. Here are specific notes for common setups:
133
+
134
+ #### Vite
135
+
136
+ No additional configuration needed. Just import and use:
137
+
138
+ ```tsx
139
+ import { Button } from '@appforgeapps/uiforge'
140
+ import '@appforgeapps/uiforge/styles.css'
141
+ ```
142
+
143
+ #### Next.js (App Router)
144
+
145
+ For Next.js 13+ with the App Router, import styles in your root layout:
146
+
147
+ ```tsx
148
+ // app/layout.tsx
149
+ import '@appforgeapps/uiforge/styles.css'
150
+ import type { Metadata } from 'next'
151
+
152
+ export const metadata: Metadata = {
153
+ title: 'My App',
154
+ }
155
+
156
+ export default function RootLayout({
157
+ children,
158
+ }: {
159
+ children: React.ReactNode
160
+ }) {
161
+ return (
162
+ <html lang="en">
163
+ <body>{children}</body>
164
+ </html>
165
+ )
166
+ }
167
+ ```
168
+
169
+ Then use components in your pages:
170
+
171
+ ```tsx
172
+ // app/page.tsx
173
+ import { Button } from '@appforgeapps/uiforge'
174
+
175
+ export default function Home() {
176
+ return <Button variant="primary">Click Me</Button>
177
+ }
178
+ ```
179
+
180
+ #### Next.js (Pages Router)
181
+
182
+ For Next.js with the Pages Router, import styles in `_app.tsx`:
183
+
184
+ ```tsx
185
+ // pages/_app.tsx
186
+ import '@appforgeapps/uiforge/styles.css'
187
+ import type { AppProps } from 'next/app'
188
+
189
+ export default function App({ Component, pageProps }: AppProps) {
190
+ return <Component {...pageProps} />
191
+ }
192
+ ```
193
+
194
+ #### Create React App
195
+
196
+ Import styles in your `index.tsx` or `App.tsx`:
197
+
198
+ ```tsx
199
+ // src/index.tsx
200
+ import '@appforgeapps/uiforge/styles.css'
201
+ import React from 'react'
202
+ import ReactDOM from 'react-dom/client'
203
+ import App from './App'
204
+
205
+ const root = ReactDOM.createRoot(document.getElementById('root')!)
206
+ root.render(
207
+ <React.StrictMode>
208
+ <App />
209
+ </React.StrictMode>
210
+ )
211
+ ```
212
+
213
+ #### Webpack
214
+
215
+ If using a custom Webpack setup, ensure you have CSS loaders configured:
216
+
217
+ ```bash
218
+ npm install --save-dev style-loader css-loader
219
+ ```
220
+
221
+ Then in your webpack.config.js:
222
+
223
+ ```js
224
+ module.exports = {
225
+ module: {
226
+ rules: [
227
+ {
228
+ test: /\.css$/i,
229
+ use: ['style-loader', 'css-loader'],
230
+ },
231
+ ],
232
+ },
233
+ }
234
+ ```
235
+
236
+ ### Verifying Installation
237
+
238
+ To verify UIForge is properly installed, you can check:
239
+
240
+ 1. **Package is installed:**
241
+ ```bash
242
+ npm list @appforgeapps/uiforge
243
+ ```
244
+
245
+ 2. **Types are available** (TypeScript projects):
246
+ ```tsx
247
+ import type { ButtonProps } from '@appforgeapps/uiforge'
248
+ // If this imports without errors, types are working
249
+ ```
250
+
251
+ 3. **Create a simple test component:**
252
+ ```tsx
253
+ import { Button } from '@appforgeapps/uiforge'
254
+ import '@appforgeapps/uiforge/styles.css'
255
+
256
+ export function Test() {
257
+ return <Button variant="primary">Test</Button>
258
+ }
259
+ ```
260
+
261
+ ### Troubleshooting
262
+
263
+ **Issue: "Cannot find module '@appforgeapps/uiforge'"**
264
+ - Run `npm install` to ensure dependencies are installed
265
+ - Check that `@appforgeapps/uiforge` is in your `package.json` dependencies
266
+ - Try deleting `node_modules` and `package-lock.json`, then run `npm install` again
267
+
268
+ **Issue: Styles not loading**
269
+ - Ensure you've imported the CSS: `import '@appforgeapps/uiforge/styles.css'`
270
+ - Check that your bundler supports CSS imports
271
+ - For Webpack, ensure css-loader and style-loader are configured
272
+
273
+ **Issue: TypeScript errors**
274
+ - Ensure TypeScript 4.7+ is installed
275
+ - Check that your `tsconfig.json` has proper module resolution settings
276
+ - Try running `npm install @types/react @types/react-dom` if not already installed
277
+
278
+ ### Alternative: Install from GitHub
279
+
280
+ For development or to use the latest unreleased features, you can install directly from GitHub. Note that GitHub installations require building the project after installation.
281
+
282
+ ```bash
283
+ npm install github:chriscase/UIForge
284
+ ```
285
+
286
+ Or specify a specific branch, tag, or commit:
287
+
288
+ ```bash
289
+ npm install github:chriscase/UIForge#main
290
+ npm install github:chriscase/UIForge#v0.1.0
291
+ ```
292
+
293
+ **Important for GitHub installations:**
294
+
295
+ After installing from GitHub, you'll need to build the project:
296
+
297
+ ```bash
298
+ cd node_modules/@appforgeapps/uiforge
299
+ npm install
300
+ npm run build
301
+ ```
302
+
303
+ Or use a `postinstall` script in your project to automate this:
304
+
305
+ ```json
306
+ {
307
+ "scripts": {
308
+ "postinstall": "cd node_modules/@appforgeapps/uiforge && npm install && npm run build"
309
+ }
310
+ }
311
+ ```
312
+
313
+ **Recommendation:** Use NPM installation for production projects. GitHub installation is primarily intended for:
314
+ - Contributing to UIForge development
315
+ - Testing unreleased features
316
+ - Debugging issues with the latest code
317
+
318
+ ## Usage
319
+
320
+ ### Basic Example
321
+
322
+ ```tsx
323
+ import { Button } from '@appforgeapps/uiforge'
324
+ import '@appforgeapps/uiforge/styles.css' // Import styles
325
+
326
+ function App() {
327
+ return (
328
+ <Button variant="primary" onClick={() => console.log('Clicked!')}>
329
+ Click me
330
+ </Button>
331
+ )
332
+ }
333
+ ```
334
+
335
+ ### Complete Setup Example
336
+
337
+ Here's a complete example of setting up UIForge in a React + TypeScript + Vite project:
338
+
339
+ **1. Install UIForge:**
340
+
341
+ ```bash
342
+ npm install @appforgeapps/uiforge react react-dom
343
+ ```
344
+
345
+ **2. Import components and styles in your app:**
346
+
347
+ ```tsx
348
+ // src/App.tsx
349
+ import { useState } from 'react'
350
+ import {
351
+ Button,
352
+ UIForgeGrid,
353
+ UIForgeComboBox,
354
+ UIForgeActivityStream
355
+ } from '@appforgeapps/uiforge'
356
+ import '@appforgeapps/uiforge/styles.css'
357
+
358
+ function App() {
359
+ const [selectedValue, setSelectedValue] = useState(null)
360
+
361
+ const options = [
362
+ { value: 1, label: 'Option 1', icon: '๐Ÿ ' },
363
+ { value: 2, label: 'Option 2', icon: 'โญ' },
364
+ { value: 3, label: 'Option 3', icon: 'โš™๏ธ' },
365
+ ]
366
+
367
+ return (
368
+ <div className="app">
369
+ <h1>UIForge Demo</h1>
370
+
371
+ <Button variant="primary" onClick={() => alert('Clicked!')}>
372
+ Click Me
373
+ </Button>
374
+
375
+ <UIForgeComboBox
376
+ options={options}
377
+ value={selectedValue}
378
+ onChange={(val) => setSelectedValue(val)}
379
+ placeholder="Select an option..."
380
+ clearable
381
+ />
382
+ </div>
383
+ )
384
+ }
385
+
386
+ export default App
387
+ ```
388
+
389
+ **3. Ensure peer dependencies are satisfied:**
390
+
391
+ UIForge requires React 18+ or React 19+ as peer dependencies:
392
+
393
+ ```json
394
+ {
395
+ "dependencies": {
396
+ "@appforgeapps/uiforge": "^0.1.0",
397
+ "react": "^18.0.0",
398
+ "react-dom": "^18.0.0"
399
+ }
400
+ }
401
+ ```
402
+
403
+ ### TypeScript Support
404
+
405
+ UIForge is written in TypeScript and includes full type definitions. TypeScript will automatically pick up the types when you import components:
406
+
407
+ ```tsx
408
+ import { Button, ButtonProps, UIForgeComboBox, ComboBoxOption } from '@appforgeapps/uiforge'
409
+
410
+ // Type inference works automatically
411
+ const options: ComboBoxOption[] = [
412
+ { value: 1, label: 'Item 1' },
413
+ { value: 2, label: 'Item 2' },
414
+ ]
415
+
416
+ // Component props are fully typed
417
+ const MyButton: React.FC<ButtonProps> = (props) => {
418
+ return <Button {...props} />
419
+ }
420
+ ```
421
+
422
+ ### Importing Styles
423
+
424
+ UIForge components require CSS to be imported. You have several options:
425
+
426
+ **Option 1: Import in your main entry file (recommended)**
427
+
428
+ ```tsx
429
+ // src/main.tsx or src/index.tsx
430
+ import '@appforgeapps/uiforge/styles.css'
431
+ import App from './App'
432
+ ```
433
+
434
+ **Option 2: Import in your component file**
435
+
436
+ ```tsx
437
+ // src/App.tsx
438
+ import '@appforgeapps/uiforge/styles.css'
439
+ import { Button } from '@appforgeapps/uiforge'
440
+ ```
441
+
442
+ **Option 3: Import in your global CSS file**
443
+
444
+ ```css
445
+ /* src/index.css */
446
+ @import '@appforgeapps/uiforge/styles.css';
447
+ ```
448
+
449
+ ## Components
450
+
451
+ ### Button
452
+
453
+ A customizable button component with multiple variants and sizes.
454
+
455
+ ```tsx
456
+ import { Button } from '@appforgeapps/uiforge'
457
+
458
+ // Variants
459
+ <Button variant="primary">Primary</Button>
460
+ <Button variant="secondary">Secondary</Button>
461
+ <Button variant="outline">Outline</Button>
462
+
463
+ // Sizes
464
+ <Button size="small">Small</Button>
465
+ <Button size="medium">Medium</Button>
466
+ <Button size="large">Large</Button>
467
+
468
+ // Disabled state
469
+ <Button disabled>Disabled</Button>
470
+ ```
471
+
472
+ ### UIForgeBlocksEditor
473
+
474
+ A rich, block-based content editor for flexible layouts and content creation.
475
+
476
+ **Features:**
477
+
478
+ - **Block-based editing** - Create, move, and delete content blocks (text, headings, images, quotes, code)
479
+ - **Rich formatting** - WYSIWYG controls for bold, italic, underline, inline code, and more
480
+ - **Drag-and-drop** - Intuitive reordering of content blocks
481
+ - **Multiple block types** - Paragraphs, headings, lists, quotes, code blocks, and images
482
+ - **Export capabilities** - Export content to JSON, HTML, or Markdown
483
+ - **No HTML/CSS knowledge required** - User-friendly interface for non-technical users
484
+ - **Reusable component** - Easy integration into any React application
485
+
486
+ ```tsx
487
+ import {
488
+ UIForgeBlocksEditor,
489
+ blocksToHTML,
490
+ blocksToMarkdown,
491
+ ContentBlock,
492
+ } from '@appforgeapps/uiforge'
493
+
494
+ function MyEditor() {
495
+ const [blocks, setBlocks] = useState<ContentBlock[]>([])
496
+
497
+ const handleExport = () => {
498
+ // Export to HTML
499
+ const html = blocksToHTML(blocks)
500
+ console.log(html)
501
+
502
+ // Export to Markdown
503
+ const markdown = blocksToMarkdown(blocks)
504
+ console.log(markdown)
505
+ }
506
+
507
+ return (
508
+ <>
509
+ <UIForgeBlocksEditor
510
+ initialBlocks={blocks}
511
+ onChange={setBlocks}
512
+ placeholder="Start typing..."
513
+ />
514
+ <button onClick={handleExport}>Export Content</button>
515
+ </>
516
+ )
517
+ }
518
+ ```
519
+
520
+ **Props Reference:**
521
+
522
+ | Prop | Type | Default | Description |
523
+ | --------------- | ---------------------------------- | ------------------- | ---------------------------------------- |
524
+ | `initialBlocks` | `ContentBlock[]` | `[]` | Initial blocks to display |
525
+ | `onChange` | `(blocks: ContentBlock[]) => void` | - | Callback when blocks change |
526
+ | `placeholder` | `string` | `"Start typing..."` | Placeholder text for empty editor |
527
+ | `readOnly` | `boolean` | `false` | Whether the editor is read-only |
528
+ | `className` | `string` | - | Additional CSS classes |
529
+ | `maxHeight` | `string` | - | Maximum height of the editor (CSS value) |
530
+
531
+ **Block Types:**
532
+
533
+ - `paragraph` - Standard text block
534
+ - `heading1`, `heading2`, `heading3` - Heading blocks
535
+ - `list` - List item
536
+ - `quote` - Blockquote
537
+ - `code` - Code block
538
+ - `image` - Image with URL and alt text
539
+
540
+ **Export Functions:**
541
+
542
+ - `blocksToHTML(blocks)` - Convert blocks to HTML string
543
+ - `blocksToMarkdown(blocks)` - Convert blocks to Markdown string
544
+ - `blocksToJSON(blocks)` - Convert blocks to JSON string
545
+
546
+ ### UIForgeGrid
547
+
548
+ A feature-rich data grid component with selection, editing, search, pagination, and custom actions.
549
+
550
+ ```tsx
551
+ import { UIForgeGrid, GridColumn } from '@appforgeapps/uiforge'
552
+
553
+ interface User {
554
+ id: number
555
+ name: string
556
+ email: string
557
+ role: string
558
+ }
559
+
560
+ const columns: GridColumn<User>[] = [
561
+ { key: 'name', header: 'Name', field: 'name', editable: true },
562
+ { key: 'email', header: 'Email', field: 'email' },
563
+ { key: 'role', header: 'Role', field: 'role' },
564
+ ]
565
+
566
+ const data: User[] = [
567
+ { id: 1, name: 'Alice', email: 'alice@example.com', role: 'Developer' },
568
+ { id: 2, name: 'Bob', email: 'bob@example.com', role: 'Designer' },
569
+ ]
570
+
571
+ function MyGrid() {
572
+ return (
573
+ <UIForgeGrid
574
+ columns={columns}
575
+ data={data}
576
+ selectable
577
+ searchable
578
+ pagination={{ currentPage: 0, pageSize: 10 }}
579
+ />
580
+ )
581
+ }
582
+ ```
583
+
584
+ **Key Features:**
585
+
586
+ - **Row Selection**: Enable with `selectable` prop. Includes master checkbox for select all/none.
587
+ - **Editable Cells**: Set `editable: true` on column definitions for inline editing.
588
+ - **Custom Renderers**: Use `render` function in column definitions for custom cell content.
589
+ - **Search**: Enable with `searchable` prop. Supports custom filter functions.
590
+ - **Pagination**: Supports both client-side and server-side pagination.
591
+ - **Action Buttons**: Add custom action buttons with event handlers.
592
+ - **Accessibility**: Full keyboard navigation and ARIA labels.
593
+ - **Responsive**: Mobile-friendly design that adapts to different screen sizes.
594
+
595
+ **Advanced Example:**
596
+
597
+ ```tsx
598
+ import { UIForgeGrid, GridColumn, GridActionButton } from '@appforgeapps/uiforge'
599
+
600
+ const columns: GridColumn<User>[] = [
601
+ {
602
+ key: 'name',
603
+ header: 'Name',
604
+ field: 'name',
605
+ editable: true,
606
+ width: '200px',
607
+ },
608
+ {
609
+ key: 'email',
610
+ header: 'Email',
611
+ field: 'email',
612
+ render: (value) => <a href={`mailto:${value}`}>{value}</a>,
613
+ },
614
+ {
615
+ key: 'status',
616
+ header: 'Status',
617
+ field: 'status',
618
+ render: (value) => <span className={`status-badge status-${value}`}>{value}</span>,
619
+ },
620
+ ]
621
+
622
+ const actionButtons: GridActionButton[] = [
623
+ {
624
+ label: 'Export',
625
+ variant: 'primary',
626
+ onClick: (selectedRows) => exportData(selectedRows),
627
+ requiresSelection: true,
628
+ },
629
+ {
630
+ label: 'Delete',
631
+ variant: 'secondary',
632
+ onClick: (selectedRows) => deleteRows(selectedRows),
633
+ requiresSelection: true,
634
+ },
635
+ ]
636
+
637
+ function AdvancedGrid() {
638
+ const [currentPage, setCurrentPage] = useState(0)
639
+ const [pageSize, setPageSize] = useState(25)
640
+
641
+ return (
642
+ <UIForgeGrid
643
+ columns={columns}
644
+ data={data}
645
+ selectable
646
+ onSelectionChange={(keys, rows) => console.log('Selected:', rows)}
647
+ getRowKey={(row) => row.id}
648
+ onCellEdit={(rowKey, columnKey, newValue) => {
649
+ // Handle cell edit
650
+ console.log('Edit:', rowKey, columnKey, newValue)
651
+ }}
652
+ searchable
653
+ searchPlaceholder="Search users..."
654
+ customFilter={(row, searchTerm) => {
655
+ // Custom search logic
656
+ return row.name.toLowerCase().includes(searchTerm.toLowerCase())
657
+ }}
658
+ actionButtons={actionButtons}
659
+ pagination={{ currentPage, pageSize }}
660
+ onPageChange={setCurrentPage}
661
+ onPageSizeChange={setPageSize}
662
+ pageSizeOptions={[10, 25, 50, 100]}
663
+ />
664
+ )
665
+ }
666
+ ```
667
+
668
+ **Props Reference:**
669
+
670
+ | Prop | Type | Default | Description |
671
+ | ------------------- | -------------------------------------------- | --------------------- | ---------------------------- |
672
+ | `columns` | `GridColumn<T>[]` | required | Column definitions |
673
+ | `data` | `T[]` | required | Data to display |
674
+ | `selectable` | `boolean` | `false` | Enable row selection |
675
+ | `selectedRows` | `Set<string \| number>` | - | Controlled selection state |
676
+ | `getRowKey` | `(row, index) => string \| number` | `(_, i) => i` | Function to get unique key |
677
+ | `onSelectionChange` | `(keys, rows) => void` | - | Selection change handler |
678
+ | `onCellEdit` | `(rowKey, columnKey, newValue, row) => void` | - | Cell edit handler |
679
+ | `searchable` | `boolean` | `false` | Enable search |
680
+ | `searchPlaceholder` | `string` | `"Search..."` | Search input placeholder |
681
+ | `onSearch` | `(searchTerm) => void` | - | Search change handler |
682
+ | `customFilter` | `(row, searchTerm) => boolean` | - | Custom filter function |
683
+ | `pagination` | `GridPaginationConfig` | - | Pagination configuration |
684
+ | `onPageChange` | `(page, pageSize) => void` | - | Page change handler |
685
+ | `onPageSizeChange` | `(pageSize) => void` | - | Page size change handler |
686
+ | `pageSizeOptions` | `number[]` | `[10, 25, 50, 100]` | Available page sizes |
687
+ | `actionButtons` | `GridActionButton[]` | `[]` | Action button configurations |
688
+ | `loading` | `boolean` | `false` | Show loading state |
689
+ | `emptyMessage` | `string` | `"No data available"` | Empty state message |
690
+ | `className` | `string` | - | Additional CSS classes |
691
+
692
+ ### UIForgeComboBox
693
+
694
+ A rich, powerful select/combo box component for ReactJS supporting static lists, dynamic server-backed data sources, hierarchical options, and advanced UX features.
695
+
696
+ **Features:**
697
+
698
+ - **Dynamic Suggestions** - Filter options as you type with client-side or server-side search
699
+ - **Static Data Support** - Simple dropdown selection from a fixed set of items
700
+ - **Icons per Option** - Display icons alongside option labels for better visual context
701
+ - **Hierarchical/Tree View** - Support for nested, multi-level option structures
702
+ - **Non-selectable Headers** - Group options with disabled header rows or section dividers
703
+ - **Async Callback Support** - Integrate with APIs for query-as-you-type autocomplete
704
+ - **Highly Customizable** - Custom rendering, styling, and behavior
705
+ - **Keyboard Navigation** - Full keyboard support (arrows, Enter, Escape, Tab)
706
+ - **Accessibility** - ARIA attributes and screen reader support
707
+
708
+ ```tsx
709
+ import { UIForgeComboBox, ComboBoxOption } from '@appforgeapps/uiforge'
710
+
711
+ // Static dropdown
712
+ const options: ComboBoxOption[] = [
713
+ { value: 1, label: 'Option 1' },
714
+ { value: 2, label: 'Option 2' },
715
+ { value: 3, label: 'Option 3' },
716
+ ]
717
+
718
+ function MyCombo() {
719
+ const [value, setValue] = useState(null)
720
+
721
+ return (
722
+ <UIForgeComboBox
723
+ options={options}
724
+ value={value}
725
+ onChange={(val, option) => setValue(val)}
726
+ placeholder="Select an option..."
727
+ />
728
+ )
729
+ }
730
+ ```
731
+
732
+ **With Icons:**
733
+
734
+ ```tsx
735
+ const optionsWithIcons: ComboBoxOption[] = [
736
+ { value: 'home', label: 'Home', icon: '๐Ÿ ' },
737
+ { value: 'star', label: 'Favorites', icon: 'โญ' },
738
+ { value: 'settings', label: 'Settings', icon: 'โš™๏ธ' },
739
+ ]
740
+
741
+ <UIForgeComboBox
742
+ options={optionsWithIcons}
743
+ value={value}
744
+ onChange={(val) => setValue(val)}
745
+ clearable
746
+ />
747
+ ```
748
+
749
+ **Hierarchical Options (Tree View):**
750
+
751
+ ```tsx
752
+ const hierarchicalOptions: ComboBoxOption[] = [
753
+ {
754
+ value: 'fruits',
755
+ label: 'Fruits',
756
+ disabled: true, // Non-selectable header
757
+ children: [
758
+ { value: 'apple', label: 'Apple', icon: '๐ŸŽ' },
759
+ { value: 'banana', label: 'Banana', icon: '๐ŸŒ' },
760
+ ],
761
+ },
762
+ {
763
+ value: 'vegetables',
764
+ label: 'Vegetables',
765
+ disabled: true,
766
+ children: [
767
+ { value: 'carrot', label: 'Carrot', icon: '๐Ÿฅ•' },
768
+ { value: 'broccoli', label: 'Broccoli', icon: '๐Ÿฅฆ' },
769
+ ],
770
+ },
771
+ ]
772
+
773
+ <UIForgeComboBox
774
+ options={hierarchicalOptions}
775
+ value={value}
776
+ onChange={(val) => setValue(val)}
777
+ />
778
+ ```
779
+
780
+ **Async/Dynamic Search:**
781
+
782
+ ````tsx
783
+ const handleSearch = async (searchText: string, signal?: AbortSignal) => {
784
+ // Call your API
785
+ const response = await fetch(`/api/search?q=${searchText}`)
786
+ const results = await response.json()
787
+ return results.map(item => ({
788
+ value: item.id,
789
+ label: item.name,
790
+ icon: item.iconUrl,
791
+ }))
792
+ }
793
+
794
+ <UIForgeComboBox
795
+ onSearch={handleSearch}
796
+ value={value}
797
+ onChange={(val) => setValue(val)}
798
+ searchable
799
+ debounceMs={300}
800
+ placeholder="Search..."
801
+ />
802
+
803
+ **Caching and TTL:**
804
+
805
+ ```tsx
806
+ const [clearCache, setClearCache] = useState<(() => void) | null>(null)
807
+ const [forceRefresh, setForceRefresh] = useState<(() => void) | null>(null)
808
+
809
+ <UIForgeComboBox
810
+ onSearch={handleSearch}
811
+ searchable
812
+ enableCache // default: false
813
+ cacheTTL={10000} // 10 seconds
814
+ onClearCache={(fn) => setClearCache(() => fn)}
815
+ onForceRefresh={(fn) => setForceRefresh(() => fn)}
816
+ />
817
+
818
+ // Clear or force-refresh from parent
819
+ clearCache && clearCache()
820
+ forceRefresh && forceRefresh()
821
+ ````
822
+
823
+ ````
824
+
825
+ **Custom Rendering:**
826
+
827
+ ```tsx
828
+ const renderOption = (option: ComboBoxOption) => (
829
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
830
+ {option.icon && <span>{option.icon}</span>}
831
+ <div>
832
+ <div style={{ fontWeight: 'bold' }}>{option.label}</div>
833
+ <div style={{ fontSize: '0.8em', color: '#666' }}>
834
+ {option.data?.description}
835
+ </div>
836
+ </div>
837
+ </div>
838
+ )
839
+
840
+ <UIForgeComboBox
841
+ options={optionsWithDetails}
842
+ renderOption={renderOption}
843
+ value={value}
844
+ onChange={(val) => setValue(val)}
845
+ />
846
+ ````
847
+
848
+ **Props Reference:**
849
+
850
+ | Prop | Type | Default | Description |
851
+ | ------------------ | ------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------- |
852
+ | `options` | `ComboBoxOption[]` | `[]` | Static list of options |
853
+ | `value` | `string \| number \| null` | - | Selected value |
854
+ | `onChange` | `(value, option) => void` | - | Callback when selection changes |
855
+ | `onSearch` | `(searchText) => Promise<ComboBoxOption[]>` | - | Async callback for dynamic suggestions |
856
+ | `placeholder` | `string` | `"Select an option..."` | Placeholder text |
857
+ | `disabled` | `boolean` | `false` | Whether the combo box is disabled |
858
+ | `clearable` | `boolean` | `false` | Show clear button to deselect |
859
+ | `className` | `string` | - | Custom class name |
860
+ | `renderOption` | `(option) => ReactNode` | - | Custom option renderer |
861
+ | `renderValue` | `(option) => ReactNode` | - | Custom selected value renderer |
862
+ | `loading` | `boolean` | `false` | Show loading indicator |
863
+ | `maxHeight` | `string` | `"300px"` | Maximum height for dropdown |
864
+ | `debounceMs` | `number` | `300` | Debounce delay for async search (ms) |
865
+ | `searchable` | `boolean` | `true` | Enable search/filter input |
866
+ | `noOptionsMessage` | `string` | `"No options found"` | Message when no options match |
867
+ | `ariaLabel` | `string` | - | ARIA label for accessibility |
868
+ | `enableCache` | `boolean` | `false` | Enable in-memory caching of identical search queries |
869
+ | `cacheTTL` | `number` | - | Time-to-live for cache entries in milliseconds (no expiry if omitted) |
870
+ | `refreshOnOpen` | `boolean` | `false` | Re-fetch results on dropdown open even if search text hasn't changed |
871
+ | `onClearCache` | `(clearFn) => void` | - | Receives a function to clear the internal cache from the parent component |
872
+ | `onForceRefresh` | `(forceFn) => void` | - | Receives a function to force refresh the current search results from the parent |
873
+
874
+ **ComboBoxOption Interface:**
875
+
876
+ | Field | Type | Description |
877
+ | ---------- | ------------------ | -------------------------------------------------- |
878
+ | `value` | `string \| number` | Unique value for the option |
879
+ | `label` | `string` | Display label |
880
+ | `icon` | `React.ReactNode` | Optional icon (string/emoji or React component) |
881
+ | `disabled` | `boolean` | Whether the option is non-selectable (for headers) |
882
+ | `level` | `number` | Nesting level for hierarchical display |
883
+ | `children` | `ComboBoxOption[]` | Child options for tree structures |
884
+ | `data` | `unknown` | Optional custom data |
885
+
886
+ **Keyboard Navigation:**
887
+
888
+ - `โ†“` / `โ†‘` - Navigate through options
889
+ - `Enter` - Select highlighted option / Toggle dropdown
890
+ - `Escape` - Close dropdown
891
+ - `Tab` - Close dropdown and move focus
892
+
893
+ **Accessibility:**
894
+
895
+ - Full ARIA support (`role="combobox"`, `aria-expanded`, `aria-selected`, etc.)
896
+ - Keyboard navigation
897
+ - Screen reader friendly
898
+ - Focus management
899
+
900
+ ### UIForgeActivityStream / UIForgeActivityStreamEnhanced
901
+
902
+ A GitHub-inspired activity stream component for displaying user activities, events, and notifications with intelligent grouping, timeline visualization, and theming support.
903
+
904
+ **Key Features:**
905
+
906
+ - **Smart Event Grouping** - Automatically combines consecutive events of the same type
907
+ - **Nested Hierarchical Grouping** - Sub-groups events by repository or context
908
+ - **Timeline Visualization** - Vertical line with markers showing event flow
909
+ - **Date Separators** - Month/year labels between time periods
910
+ - **Monochrome Icons** - Clean, GitHub-style SVG icons for each event type
911
+ - **Expandable Content** - Click grouped events to see individual items
912
+ - **Infinite Scroll** - "Show more" bar for progressive loading
913
+ - **Light/Dark Themes** - Seamless theme switching with CSS variables
914
+ - **Fully Accessible** - Keyboard navigation, ARIA attributes, screen reader support
915
+ - **Responsive Design** - Adapts to mobile and desktop viewports
916
+
917
+ ```tsx
918
+ import { UIForgeActivityStreamEnhanced, ActivityEvent } from '@appforgeapps/uiforge'
919
+
920
+ const events: ActivityEvent[] = [
921
+ {
922
+ id: 1,
923
+ type: 'pr',
924
+ title: 'Added dark mode support',
925
+ description: 'Implemented comprehensive dark mode theming',
926
+ timestamp: new Date(),
927
+ metadata: { repository: 'myapp/frontend' },
928
+ },
929
+ {
930
+ id: 2,
931
+ type: 'commit',
932
+ title: 'Fixed authentication bug',
933
+ timestamp: new Date(),
934
+ metadata: { repository: 'myapp/backend' },
935
+ },
936
+ // ... more events
937
+ ]
938
+
939
+ <UIForgeActivityStreamEnhanced
940
+ events={events}
941
+ theme="dark"
942
+ enableGrouping={true}
943
+ showTimeline={true}
944
+ showDateSeparators={true}
945
+ groupingThreshold={2}
946
+ onLoadMore={() => loadMoreEvents()}
947
+ pagination={{ currentPage: 0, pageSize: 20, hasMore: true }}
948
+ />
949
+ ```
950
+
951
+ **Smart Grouping Example:**
952
+
953
+ When you have consecutive events of the same type, they're automatically grouped:
954
+ - Individual: "Created pull request #123", "Created pull request #124", "Created pull request #125"
955
+ - Grouped: "Created 3 pull requests in myapp/frontend" (expandable to see individual PRs)
956
+
957
+ **Nested Grouping Example:**
958
+
959
+ When grouped events span multiple repositories:
960
+ - Top level: "Created 6 pull requests in 3 repositories"
961
+ - Sub-group: "Created 2 pull requests in myapp/frontend"
962
+ - Sub-group: "Created 1 pull request in myapp/docs"
963
+ - Sub-group: "Created 3 pull requests in myapp/backend"
964
+
965
+ **Props Reference:**
966
+
967
+ | Prop | Type | Default | Description |
968
+ | ---------------------- | --------------------------- | ------- | ---------------------------------------------- |
969
+ | `events` | `ActivityEvent[]` | - | Array of activity events to display |
970
+ | `theme` | `'light' \| 'dark'` | `light` | Theme variant |
971
+ | `enableGrouping` | `boolean` | `true` | Enable smart event grouping |
972
+ | `groupingThreshold` | `number` | `2` | Minimum consecutive events to trigger grouping |
973
+ | `showTimeline` | `boolean` | `true` | Show vertical timeline line |
974
+ | `showDateSeparators` | `boolean` | `true` | Show month/year date separators |
975
+ | `showLoadMore` | `boolean` | `true` | Show "Show more" bar |
976
+ | `loading` | `boolean` | `false` | Display loading indicator |
977
+ | `onLoadMore` | `() => void` | - | Callback when "Show more" is clicked |
978
+ | `pagination` | `ActivityStreamPagination` | - | Pagination configuration |
979
+ | `maxHeight` | `string` | - | Maximum height (CSS value) |
980
+ | `showMoreThreshold` | `number` | `100` | Distance from bottom to show "Show more" (px) |
981
+ | `initiallyExpandedAll` | `boolean` | `false` | Expand all events initially |
982
+ | `emptyMessage` | `string` | - | Empty state message |
983
+ | `onToggleExpand` | `(id, expanded) => void` | - | Callback when event is expanded/collapsed |
984
+
985
+ **ActivityEvent Interface:**
986
+
987
+ | Field | Type | Description |
988
+ | -------------- | --------------------------- | ------------------------------------------ |
989
+ | `id` | `string \| number` | Unique identifier |
990
+ | `type` | `string` | Event type (e.g., 'commit', 'pr', 'issue') |
991
+ | `title` | `string` | Event title/description |
992
+ | `description` | `string` | Optional detailed description |
993
+ | `timestamp` | `Date \| string` | Event timestamp |
994
+ | `icon` | `React.ReactNode` | Optional custom icon |
995
+ | `metadata` | `Record<string, unknown>` | Optional metadata (e.g., repository name) |
996
+
997
+ **Supported Event Types (with default icons):**
998
+
999
+ - `commit` - Code commits
1000
+ - `pr` - Pull requests
1001
+ - `issue` - Issues
1002
+ - `comment` - Comments
1003
+ - `star` - Repository stars
1004
+ - `fork` - Repository forks
1005
+ - `merge` - Merged pull requests
1006
+ - `release` - Version releases
1007
+ - `deploy` - Deployments
1008
+
1009
+ See `examples/ActivityStreamExample.tsx` for a complete interactive demo.
1010
+
1011
+ ### UIForgeVideo / UIForgeVideoPreview
1012
+
1013
+ Video components for embedding YouTube and Vimeo videos with interactive overlays and preview functionality.
1014
+
1015
+ **UIForgeVideo Features:**
1016
+
1017
+ - **Video Embedding** - Supports both YouTube and Vimeo video embeds
1018
+ - **Lazy Loading** - Video player loads only when user clicks to play
1019
+ - **Custom Thumbnails** - Use custom thumbnail images or provider defaults
1020
+ - **Overlay Interaction** - Clickable overlay with customizable play icon
1021
+ - **Event Emission** - Fires callback when video starts playing for analytics/tracking
1022
+ - **Responsive Design** - Adjustable aspect ratios and mobile-friendly
1023
+ - **Accessibility** - Proper ARIA labels and keyboard navigation
1024
+
1025
+ **UIForgeVideoPreview Features:**
1026
+
1027
+ - **Compact Display** - Small preview component with title and icon
1028
+ - **Interactive** - Optional click handler for navigation
1029
+ - **Customizable Icons** - Support for custom icons or emojis
1030
+ - **Lightweight** - Perfect for video lists and catalogs
1031
+
1032
+ ```tsx
1033
+ import { UIForgeVideo, UIForgeVideoPreview } from '@appforgeapps/uiforge'
1034
+
1035
+ // YouTube video with event tracking
1036
+ <UIForgeVideo
1037
+ title="Introduction to React"
1038
+ description="Learn React basics in this comprehensive tutorial"
1039
+ youtubeId="dQw4w9WgXcQ"
1040
+ onPlay={(videoId, provider) => {
1041
+ console.log(`Playing ${provider} video: ${videoId}`)
1042
+ trackAnalytics('video_play', { videoId, provider })
1043
+ }}
1044
+ />
1045
+
1046
+ // Vimeo video
1047
+ <UIForgeVideo
1048
+ title="Beautiful Nature Footage"
1049
+ description="Stunning visuals from around the world"
1050
+ vimeoId="76979871"
1051
+ onPlay={(videoId, provider) => {
1052
+ console.log('Video started')
1053
+ }}
1054
+ />
1055
+
1056
+ // Custom thumbnail and aspect ratio
1057
+ <UIForgeVideo
1058
+ title="Classic Format Video"
1059
+ youtubeId="abc123"
1060
+ thumbnailUrl="https://example.com/custom-thumb.jpg"
1061
+ aspectRatio="4/3"
1062
+ onPlay={handlePlay}
1063
+ />
1064
+
1065
+ // Custom overlay icon
1066
+ <UIForgeVideo
1067
+ title="Video with Custom Play Button"
1068
+ youtubeId="xyz789"
1069
+ overlayIcon={<span style={{ fontSize: '64px' }}>โ–ถ๏ธ</span>}
1070
+ onPlay={handlePlay}
1071
+ />
1072
+
1073
+ // Video preview component
1074
+ <UIForgeVideoPreview
1075
+ title="Tutorial: Getting Started"
1076
+ icon={<span>๐ŸŽ“</span>}
1077
+ onClick={() => navigateToVideo('tutorial-123')}
1078
+ />
1079
+ ```
1080
+
1081
+ **UIForgeVideo Props:**
1082
+
1083
+ | Prop | Type | Default | Description |
1084
+ | -------------- | ------------------------------------------- | ---------- | --------------------------------------------- |
1085
+ | `title` | `string` | required | Video title |
1086
+ | `description` | `string` | - | Optional video description |
1087
+ | `youtubeId` | `string` | - | YouTube video ID (required if no vimeoId) |
1088
+ | `vimeoId` | `string` | - | Vimeo video ID (required if no youtubeId) |
1089
+ | `thumbnailUrl` | `string` | - | Custom thumbnail URL (optional) |
1090
+ | `onPlay` | `(videoId, provider) => void` | - | Callback when video starts playing |
1091
+ | `className` | `string` | - | Additional CSS classes |
1092
+ | `overlayIcon` | `React.ReactNode` | Play icon | Custom overlay icon |
1093
+ | `aspectRatio` | `string` | `"16/9"` | Video aspect ratio (CSS value) |
1094
+
1095
+ **UIForgeVideoPreview Props:**
1096
+
1097
+ | Prop | Type | Default | Description |
1098
+ | ----------- | ----------------- | -------- | ------------------------------ |
1099
+ | `title` | `string` | required | Video title |
1100
+ | `icon` | `React.ReactNode` | - | Optional custom icon |
1101
+ | `className` | `string` | - | Additional CSS classes |
1102
+ | `onClick` | `() => void` | - | Click handler (makes it interactive) |
1103
+
1104
+ **Use Cases:**
1105
+
1106
+ - Video tutorials and educational content
1107
+ - Product demos and walkthroughs
1108
+ - Marketing videos and testimonials
1109
+ - Video galleries and catalogs
1110
+ - Course content and lessons
1111
+ - Conference talks and presentations
1112
+
1113
+ See `examples/VideoExample.tsx` for a complete interactive demo with multiple examples.
1114
+
1115
+ ## Theming
1116
+
1117
+ UIForge components support comprehensive theming through CSS variables. See [THEMING.md](./THEMING.md) for a complete guide on:
1118
+
1119
+ - Light and dark theme support
1120
+ - Custom theme creation
1121
+ - CSS variable reference
1122
+ - System preference detection
1123
+ - Advanced customization techniques
1124
+
1125
+ Quick example:
1126
+
1127
+ ```tsx
1128
+ import { useState } from 'react'
1129
+
1130
+ function App() {
1131
+ const [theme, setTheme] = useState<'light' | 'dark'>('light')
1132
+
1133
+ return (
1134
+ <div>
1135
+ <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
1136
+ Toggle Theme
1137
+ </button>
1138
+ <UIForgeActivityStreamEnhanced events={events} theme={theme} />
1139
+ </div>
1140
+ )
1141
+ }
1142
+ ```
1143
+
1144
+ ## Development
1145
+
1146
+ ### Prerequisites
1147
+
1148
+ - Node.js 18+ and npm
1149
+
1150
+ ### Getting Started
1151
+
1152
+ 1. Clone the repository:
1153
+
1154
+ ```bash
1155
+ git clone https://github.com/chriscase/UIForge.git
1156
+ cd UIForge
1157
+ ```
1158
+
1159
+ 2. Install dependencies:
1160
+
1161
+ ```bash
1162
+ npm install
1163
+ ```
1164
+
1165
+ 3. Start the development server:
1166
+
1167
+ ```bash
1168
+ npm run dev
1169
+ ```
1170
+
1171
+ This will start a local development server where you can see and interact with the components.
1172
+
1173
+ ### Available Scripts
1174
+
1175
+ - `npm run dev` - Start the development server
1176
+ - `npm run build` - Build the library for production
1177
+ - `npm run preview` - Preview the production build
1178
+ - `npm test` - Run tests in watch mode
1179
+ - `npm run test:ui` - Run tests with UI
1180
+ - `npm run test:coverage` - Generate test coverage report
1181
+ - `npm run lint` - Lint the codebase
1182
+ - `npm run lint:fix` - Fix linting issues
1183
+ - `npm run format` - Format code with Prettier
1184
+ - `npm run format:check` - Check code formatting
1185
+
1186
+ ### Testing
1187
+
1188
+ Tests are written using Vitest and React Testing Library:
1189
+
1190
+ ```bash
1191
+ npm test
1192
+ ```
1193
+
1194
+ ### Building
1195
+
1196
+ Build the library for production:
1197
+
1198
+ ```bash
1199
+ npm run build
1200
+ ```
1201
+
1202
+ The built files will be in the `dist` directory.
1203
+
1204
+ ### Development with GitHub Tools
1205
+
1206
+ This repository is optimized for development using:
1207
+
1208
+ - **GitHub Codespaces** - One-click cloud development environment
1209
+ - **GitHub Copilot** - AI-powered code completion
1210
+
1211
+ Simply open this repository in GitHub Codespaces to get started immediately with all dependencies pre-installed!
1212
+
1213
+ ## Contributing
1214
+
1215
+ Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for detailed guidelines.
1216
+
1217
+ Quick start:
1218
+
1219
+ 1. Fork the repository
1220
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
1221
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
1222
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
1223
+ 5. Open a Pull Request
1224
+
1225
+ ## For Maintainers
1226
+
1227
+ If you're a maintainer looking to publish releases or manage the project, please see [MAINTAINER_INSTRUCTIONS.md](./MAINTAINER_INSTRUCTIONS.md) for comprehensive publishing and maintenance workflows.
1228
+
1229
+ ## License
1230
+
1231
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
1232
+
1233
+ ## Author
1234
+
1235
+ **chriscase**
1236
+
1237
+ ## Acknowledgments
1238
+
1239
+ - Built with [Vite](https://vitejs.dev/)
1240
+ - Tested with [Vitest](https://vitest.dev/)
1241
+ - Styled with CSS